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 (recommended)
$ 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!