gRPC Rust Client

Rust set-up

Set up a Rust project

Use the following command to initialize a Rust project in an empty directory:

cargo init

Two files will be created, Cargo.toml and src/main.rs.

Add dependencies

  1. Add required dependencies to the Cargo.toml file in your Rust project. You’ll need at least stargate-grpc and an async framework, such as tokio:

    [dependencies]
    stargate-grpc = "0.3"
    tokio = { version = "1", features = ["full"]}
  2. Build the project with cargo build and fetch and compile the dependencies.

  3. Add the following line to the includes in the source code of your app, such as main.rs:

    use stargate_grpc::*;

This set-up will make all the Stargate gRPC functionality available.

The next sections explain the parts of a script to use the Stargate functionality. A full working script is included below.

Rust connecting

Authentication

This example assumes that you’re running Stargate locally with the default credentials of cassandra/cassandra. For more information regarding authentication please see the Stargate authentication and authorization docs.

You’ll need to generate a token to insert into the client connection code:

curl -L -X POST 'https://localhost:8081/v1/auth' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "username": "cassandra",
    "password": "cassandra"
}'

Set up client

The main structure that provides the interface to Stargate is StargateClient. The simplest way to obtain an instance is to use the provided builder:

use stargate_grpc::*;
use std::str::FromStr;

// Set the Stargate OSS configuration for a locally running docker container:
let sg_uri = "https://localhost:8090/";
let auth_token = "06251024-5aeb-4200-a132-5336e73e5b6e";

// For Stargate OSS: create a client
let mut client = StargateClient::builder()
.uri(sg_uri)?
.auth_token(AuthToken::from_str(auth_token)?)
.connect()
.await?;

println!("created client {:?}", client);

Rust querying

Use QueryBuilder to create a query, bind query values and pass query parameters. The query is followed by the execute commands that actually run the command and return the results.

  • Rust query builder command (/v2)

  • Rust execute command

// For Stargate OSS: SELECT the data to read from the table
// Select/query some data from the keyspace.table
let query = Query::builder()
  // Set the keyspace for the the query
  .keyspace("test")
  // Set consistency level
  .consistency(Consistency::One)
  .query("SELECT firstname, lastname FROM test.users;")
  // Build the query
  .build();

println!("select executed");
use std::convert::TryInto;

// Send the query and wait for gRPC response
let response = client.execute_query(query).await?;

// Convert the response into a ResultSet
let result_set: ResultSet = response.try_into()?;

It is also possible to use a bind statement to insert values:

.query("SELECT login, emails FROM users WHERE id = :id")
.bind_value("id", 1000)

Data definition language (DDL) queries are supported in the same manner:

// For Stargate OSS only: create a keyspace
let create_keyspace = Query::builder()
    .query("CREATE KEYSPACE IF NOT EXISTS test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':1};")
    .build();
client.execute_query(create_keyspace).await?;

println!("created keyspace");

// For Stargate OSS: create a table
let create_table = Query::builder()
    .query(
        "CREATE TABLE IF NOT EXISTS test.users \
            (firstname text, lastname text, PRIMARY KEY (firstname, lastname));",
    )
    .build();
 client.execute_query(create_table).await?;

 println!("created table");

In general, users will create a keyspace and table first.

The ExecuteQuery function can be used to execute a single query.

If you need to group several commands together as a batch statement, the client also provides an ExecuteBatch() function to execute a batch query:

// For Stargate OSS: INSERT two rows/records
//  Two queries will be run in a batch statement
let batch = Batch::builder()
    .keyspace("test")                   // set the keyspace the query applies to
    .consistency(Consistency::One)      // set consistency level
    .query("INSERT INTO test.users (firstname, lastname) VALUES ('Jane', 'Doe');")
    .query("INSERT INTO test.users (firstname, lastname) VALUES ('Serge', 'Provencio');")
    .build();
client.execute_batch(batch).await?;

println!("insert data");

This example inserts two values into the keyspace table test.users. Only INSERT, UPDATE, and DELETE operations can be used in a batch query.

Rust processing result set

The result set comes back as a collection of rows. A row can be easily unpacked:

// This for loop to get the results
   for row in result_set.rows {
       let (firstname, lastname): (String, String) = row.try_into()?;
       println!("{} {}", firstname, lastname);
   }

Rust full sample script

To put all the pieces together, here is a sample script that combines all the pieces shown above:

  • Sample script

  • Result

use stargate_grpc::*;
use std::str::FromStr;
use std::error::Error;
use std::convert::TryInto;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {

    // For Stargate OSS: create a client
    let mut client = StargateClient::builder()

    // For Stargate OSS running locally in docker container, set connect information:
    .uri("https://localhost:8090/")?                           // replace with a proper address
    .auth_token(AuthToken::from_str("721e9c04-e121-4bf4-b9a6-887ebeae2bc5")?)    // replace with a proper token
    .connect()
    .await?;

    println!("created client {:?}", client);

    // For Stargate OSS only: create a keyspace
    let create_keyspace = Query::builder()
        .query("CREATE KEYSPACE IF NOT EXISTS test WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':1};")
        .build();
    client.execute_query(create_keyspace).await?;

    println!("created keyspace");

    // For Stargate OSS: create a table
    let create_table = Query::builder()
        // .keyspace("test")
        .query(
            "CREATE TABLE IF NOT EXISTS test.users \
                (firstname text, lastname text, PRIMARY KEY (firstname, lastname));",
        )
        .build();
     client.execute_query(create_table).await?;

     println!("created table");

    // For Stargate OSS: INSERT two rows/records
	//  Two queries will be run in a batch statement
    let batch = Batch::builder()
        .keyspace("test")                   // set the keyspace the query applies to
        .consistency(Consistency::One)      // set consistency level
        .query("INSERT INTO test.users (firstname, lastname) VALUES ('Lorina', 'Poland');")
        .query("INSERT INTO test.users (firstname, lastname) VALUES ('Doug', 'Wettlaufer');")
        .build();
    client.execute_batch(batch).await?;

    println!("insert data");

    // For Stargate OSS: SELECT the data to read from the table
    // Select/query some data from the keyspace.table
    let query = Query::builder()
        .keyspace("test")
        .consistency(Consistency::One)
        .query("SELECT firstname, lastname FROM test.users;")
        .build();

     println!("select executed");

    // Get the results from the execute query statement and convert into a ResultSet
    let response = client.execute_query(query).await?;
    let result_set: ResultSet = response.try_into()?;

    // This for loop to get the results
    for row in result_set.rows {
        let (firstname, lastname): (String, String) = row.try_into()?;
        println!("{} {}", firstname, lastname);
    }
    println!("everything worked!");
    Ok(())
}
created client StargateClient { inner: Grpc { inner: InterceptedService { inner: Channel, f: stargate_grpc::client::AuthToken } } }
created keyspace
created table
insert data
select executed
Jane Doe
Serge Provencio
everything worked!

Rust developing

Building

Install Rust toolchain:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Run build in the root directory of the project:
git clone https://github.com/stargate/stargate-grpc-rust-client stargate-grpc
cd stargate-grpc
cargo build

Running the examples

For your convenience, this project contains a bunch of examples located in the examples directory, which demonstrate connecting, creating schemas, inserting data and querying.

Each example program accepts an URL of the Stargate coordinator, the authentication token and the keyspace name:

cargo run --example <example> [-- [--keyspace <keyspace>] [--token <token>] [--tls] [<url>]]

The authentication token value can be also defined in the SG_TOKEN environment variable.

You’ll need to start a Stargate instance.

Start Stargate

If you don’t already have access to a Stargate deployment, one can be started quickly for testing in developer mode. Developer mode removes the need to set up a separate Cassandra instance and is meant for development and testing only. This docker run command also exposes port 8090 for gRPC connections.

docker run --name stargate \
  -p 8080:8080 \
  -p 8081:8081 \
  -p 8082:8082 \
  -p 8090:8090 \
  -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

Ensure the local instance of Stargate is running properly by tailing the logs for the container, and looking for the message that indicates Stargate is ready for traffic.

  • shell command (/v2)

  • Result

docker logs -f stargate | grep "Finished starting bundles."
Finished starting bundles.

Run examples

Run the keyspace example to test the connection and create the test keyspace with the default keyspace name of stargate_examples:

  • Run test

  • Result

cargo run --example keyspace
Connected to https://127.0.0.2:8090
Created keyspace stargate_examples

Run the query example to create a keyspace and table, insert some data, and select data to check the insertion:

  • Run test

  • Result

cargo run --example query
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/examples/basic`
Connected to https://127.0.0.2:8090
Created schema
Inserted data. Now querying.
All rows:
2 user_2 ["user_2@example.net", "user_2@mail.example.net"]
3 user_3 ["user_3@example.net", "user_3@mail.example.net"]
7 user_7 ["user_7@example.net", "user_7@mail.example.net"]
9 user_9 ["user_9@example.net", "user_9@mail.example.net"]
4 user_4 ["user_4@example.net", "user_4@mail.example.net"]
0 user_0 ["user_0@example.net", "user_0@mail.example.net"]
8 user_8 ["user_8@example.net", "user_8@mail.example.net"]
5 user_5 ["user_5@example.net", "user_5@mail.example.net"]
6 user_6 ["user_6@example.net", "user_6@mail.example.net"]
1 user_1 ["user_1@example.net", "user_1@mail.example.net"]
Row with id = 1:
1 user_1 ["user_1@example.net", "user_1@mail.example.net"]

The Stargate gRPC Rust Client repository is located at https://github.com/stargate/stargate-grpc-rust-client.