Getting Started

Scyllax is a query system (and ORM kinda) for Scylla. It is a work in progress and is not ready for production use.

Installation

Although scyllax depends on scylla internally, it's recommended to have it installed if you decide to use some values from scylla. Additionally, while in alpha, there is no tracing flag, so you must also install tracing.

scylla = "0.9"
scyllax = "0.1.0"
tracing = "0.1"

Scyllax's prelude includes everything you'll need, so import it at the top of your file:

use scyllax::prelude::*;

Creating an executor

Your queries will need to be ran by an Executor. Creating one is simple. You can use the create_session function provided by scyllax, and pass it to a new Executor.

use scyllax::prelude::*;

#[tokio::main]
fn main() {
	let known_nodes = std::env::var("SCYLLA_NODES")
		.expect("SCYLLA_NODES must be set");
    let known_nodes = known_nodes.split(',').collect::<Vec<_>>();
    let default_keyspace = std::env::var("SCYLLA_DEFAULT_KEYSPACE").ok();

    let session = create_session(known_nodes, default_keyspace).await?;
    let executor = Executor::<UserQueries>::new(session).await?;
}

Creating entities

Creating a new entity is super simple. Simply use the scyllax::Entity macro on a struct, and it'll implement the necessary symbols and traits.

use scyllax::prelude::*;

#[derive(Clone, Debug, PartialEq, Entity, FromRow, ValueList)]
pub struct PersonEntity {
	#[entity(pk)]
    pub id: uuid::Uuid,
    pub email: String,
    pub created_at: i64,
}

Since id is a primary key, it must be annotated with #[entity(pk)]. Clustering columns must be treated the same way.

This is so that, when eventually using the upsert_query macro, scyllax will use the column in the where clause rather than the set clause.

You're also welcome to use the #[entity] macro instead of deriving Clone, Debug, PartialEq, Entity, FromRow, and ValueList manually. That's what'll be used in the rst of this book.

A list of possible column types can be found at scylla::frame::response::result::CqlValue.

camelCase Columns

If you have some column names you can't change to work with the Rust naming convention, you can use the rename attribute on an entity column to rename it in queries.

use scyllax::prelude::*;

#[entity]
pub struct PersonEntity {
	#[entity(pk)]
    pub id: uuid::Uuid,
    pub email: String,
	#[entity(rename = "createdAt")]
    pub created_at: i64,
}

JSON columns

If you want something similar to postgres's json type, you can use the #[json_data] attribute macro on a struct.

use scyllax::prelude::*;


/// Represents data from a person
#[json_data]
pub struct PersonData {
    #[serde(rename = "stripeId")]
    pub stripe_id: Option<String>,
}

#[entity]
pub struct PersonEntity {
	#[entity(pk)]
    pub id: uuid::Uuid,
    pub email: String,
	pub data: Option<PersonData>
	#[entity(rename = "createdAt")]
    pub created_at: i64,
}

json_data uses serde Deserialize and Serialize under the hood, so you're welcome to use any Serde macro attributes.

Counter columns

If you have a table that uses scylla's counter type, you can use the #[entity(counter)] attribute macro on an entity column along with using the scylla::frame::value::Counter type.

use scyllax::prelude::*;

#[entity]
pub struct PersonLoginEntity {
    #[entity(pk)]
    pub id: uuid::Uuid,
    #[entity(pk)]
    pub person_id: uuid::Uuid,
    #[entity(counter)]
    pub count: scylla::frame::value::Counter,
}

Similarly to #[entity(pk)], the #[entity(counter)] attribute also tells the upsert macro how to use the column in the query.

Select Queries

Writing select queries is incredibly easy with the select_query macro.

Simply create a struct with the fields you want to select, and annotate it with the #[select_query] macro.

use scyllax::prelude::*;

\#[entity]
pub struct PersonEntity {
	#[entity(pk)]
   pub id: uuid::Uuid,
   pub email: String,
	#[entity(rename = "createdAt")]
   pub created_at: i64,
}

#[read_query(
    query = "select * from person where id = ? limit 1",
    entity_type = "PersonEntity"
)]
pub struct GetPersonById {
    pub id: Uuid,
}

Then, you can pass it to the executor you made in Introduction.

let query = GetPersonById {
    id: Uuid::from_str("00000000-0000-0000-0000-000000000000")?,
};

let res = executor
    .execute_select(query)
    .await?

Delete Queries

Writing delete queries, which is pretty much the same as Select Queries, is incredibly easy with the delete_query macro.

Simply create a struct with the fields you want to select, and annotate it with the #[write_query] macro.

use scyllax::prelude::*;

\#[entity]
pub struct PersonEntity {
	#[entity(pk)]
   pub id: uuid::Uuid,
   pub email: String,
	#[entity(rename = "createdAt")]
   pub created_at: i64,
}

#[write_query(
    query = "delete from person where id = ?",
    entity_type = "PersonEntity"
)]
pub struct DeletePersonById {
    pub id: Uuid,
}

Then, you can pass it to the executor you made in Introduction.

let query = DeletePersonById {
    id: Uuid::from_str("00000000-0000-0000-0000-000000000000")?,
};

let res = executor
    .execute_delete(query)
    .await?

Upsert Queries

Now listen up. Upsert queries with scyllax are special. You don't have to hand write anything. In conjunction with #[pk], #[counter], and #[json], the upsert_query macro will generate the query for you. Simply apply the #[upsert_query] macro to tne entity struct.

use scyllax::prelude::*;

#[entity]
#[upsert_query(table = "person", name = UpsertPerson)]
pub struct PersonEntity {
	#[entity(pk)]
    pub id: uuid::Uuid,
    pub email: String,
	#[entity(rename = "createdAt")]
    pub created_at: i64,
}

The structure will look a little like:

pub struct UpsertPerson {
	///The id of the PersonEntity
	pub id: uuid::Uuid,
	///The email of the PersonEntity
	pub email: scyllax::prelude::MaybeUnset<String>,
	///The created_at of the PersonEntity
	pub created_at: scyllax::prelude::MaybeUnset<i64>,
}

and the generated query will look a little like:

update person set email = :email, age = :age, data = :data, \"createdAt\" = :created_at where id = :id;

MaybeUnset is used to tell scylla if the field is not provided to the query, it should be ignored, and not overwritten. Every Value can be used with MaybeUnset.

Once you've built your query, you can pass it to an Executor.

let id = v1_uuid();

let query = UpsertPerson {
	id,
	email: "foo@scyllax.local".to_string().into(),
	created_at: MaybeUnset::Unset,
};

let res = executor.execute_upsert(query).await?;

Query Collections

Once you've made all your queries, you'll have to make a Query Collection. This is a struct that contains all your prepared queries. Scyllax provides the create_query_collection macro to make this easy.

use scyllax::prelude::*;
use super::model::UpsertPerson;

create_query_collection!(
    PersonQueries,
    [
        GetPersonById,
        GetPeopleByIds,
        GetPersonByEmail,
        DeletePersonById,
        UpsertPerson,
    ]
);

Then, you use the Query Collection when you instantiate your Executor.

let executor = Executor::<PersonQueries>::new(session).await?;

Finally, you can run your queries.

let person = executor
	.execute_read(&GetPersonById {
		id: Uuid::new_v4(),
	})
	.await?;

CLI

The CLI is still in devleopment!

Installation

$ cargo binstall -y scyllax-cli@0.1.0

cargo install

$ cargo install scyllax-cli@0.1.0

Example

Are you looking for a real-life example of scyllax? You're in luck! The example directory in our GitHub repository contains a full example of scyllax in action -- it's actually used to run tests!

https://github.com/fyko/scyllax/tree/main/example