Skip to content

Commit

Permalink
feat: multi-row select query
Browse files Browse the repository at this point in the history
  • Loading branch information
Fyko committed Sep 5, 2023
1 parent 152d618 commit b4e088c
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 12 deletions.
12 changes: 12 additions & 0 deletions example/src/entities/person/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ pub struct GetPersonById {
pub id: Uuid,
}

/// Get many [`super::model::PersonEntity`] by many [`uuid::Uuid`]
#[select_query(
query = "select * from person where id in ? limit ?",
entity_type = "Vec<super::model::PersonEntity>"
)]
pub struct GetPeopleByIds {
/// The [`uuid::Uuid`]s of the [`super::model::PersonEntity`]s to get
pub ids: Vec<Uuid>,
/// The maximum number of [`super::model::PersonEntity`]s to get
pub limit: i32,
}

/// Get a [`super::model::PersonEntity`] by its email address
#[select_query(
query = "select * from person_by_email where email = ? limit 1",
Expand Down
32 changes: 23 additions & 9 deletions example/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Example
use entities::person::{
model::UpsertPerson,
queries::{GetPersonByEmail, GetPersonById},
queries::{GetPeopleByIds, GetPersonByEmail, GetPersonById},
};
use scyllax::prelude::*;
use scyllax::{executor::create_session, util::v1_uuid};
use tracing_subscriber::prelude::*;
use uuid::Uuid;

pub mod entities;

Expand All @@ -23,32 +24,45 @@ async fn main() -> anyhow::Result<()> {
let session = create_session(known_nodes, default_keyspace).await?;
let executor = Executor::with_session(session);

let by_email = GetPersonByEmail {
let query = GetPersonByEmail {
email: "[email protected]".to_string(),
};
let res_one = executor
.execute_select(by_email)
.execute_select(query)
.await?
.expect("person not found");
tracing::debug!("query 1: {:?}", res_one);

let by_id = GetPersonById { id: res_one.id };
let query = GetPersonById { id: res_one.id };
let res_two = executor
.execute_select(by_id)
.execute_select(query)
.await?
.expect("person not found");
tracing::debug!("query 2: {:?}", res_two);

assert_eq!(res_one, res_two);

let create = UpsertPerson {
let ids = vec![

Check failure on line 44 in example/src/main.rs

View workflow job for this annotation

GitHub Actions / Check Suite

useless use of `vec!`
"e01e84d6-414c-11ee-be56-0242ac120002",
"e01e880a-414c-11ee-be56-0242ac120002",
]
.iter()
.map(|s| Uuid::parse_str(s).unwrap())
.collect::<Vec<_>>();
let query = GetPeopleByIds {
limit: ids.len() as i32,
ids,
};
let res = executor.execute_select(query).await?;
tracing::debug!("query 3: {:?}", res);

let query = UpsertPerson {
id: v1_uuid(),
email: MaybeUnset::Set("[email protected]".to_string()),
age: MaybeUnset::Set(Some(21)),
created_at: MaybeUnset::Unset,
};
let res_three = executor.execute_upsert(create).await?;
tracing::debug!("query 3: {:?}", res_three);
let res = executor.execute_upsert(query).await?;
tracing::debug!("query 4: {:?}", res);

Ok(())
}
16 changes: 16 additions & 0 deletions scyllax-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) fn token_stream_with_error(mut tokens: TokenStream2, error: syn::Erro
}

/// Apply this attribute to a struct to generate a select query.
/// ## Single result
/// ```rust,ignore
/// #[select_query(
/// query = "select * from person where id = ? limit 1",
Expand All @@ -19,6 +20,21 @@ pub(crate) fn token_stream_with_error(mut tokens: TokenStream2, error: syn::Erro
/// pub struct GetPersonById {
/// pub id: Uuid,
/// }
/// executor.execute_select(GetPersonById { id }).await?;
/// // -> Option<PersonEntity>
/// ```
/// ## Multiple results
/// ```rust,ignore
/// #[select_query(
/// query = "select * from person where id in ? limit ?",
/// entity_type = "Vec<PersonEntity>"
/// )]
/// pub struct GetPeopleByIds {
/// pub ids: Vec<Uuid>,
/// pub limit: i32,
/// }
/// executor.execute_select(GetPeopleByIds { ids, limit }).await?;
/// // -> Vec<PersonEntity>
/// ```
#[proc_macro_attribute]
pub fn select_query(args: TokenStream, input: TokenStream) -> TokenStream {
Expand Down
57 changes: 54 additions & 3 deletions scyllax-macros/src/queries/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,57 @@ pub fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
};
let struct_ident = &input.ident;

// trimmed entity_type
// eg: Vec<OrgEntity> -> OrgEntity
// eg: OrgEntity -> OrgEntity
let inner_entity_type = if let syn::Type::Path(path) = entity_type.clone() {
let last_segment = path.path.segments.last().unwrap();
let ident = &last_segment.ident;

if ident == "Vec" {
let args = &last_segment.arguments;
if let syn::PathArguments::AngleBracketed(args) = args {
let args = &args.args;
if args.len() != 1 {
return token_stream_with_error(
item,
syn::Error::new_spanned(
entity_type,
"entity_type must be a path with one generic argument",
),
);
}

if let syn::GenericArgument::Type(ty) = args.first().unwrap() {
ty.clone()
} else {
return token_stream_with_error(
item,
syn::Error::new_spanned(
entity_type,
"entity_type must be a path with one generic argument",
),
);
}
} else {
return token_stream_with_error(
item,
syn::Error::new_spanned(
entity_type,
"entity_type must be a path with one generic argument",
),
);
}
} else {
entity_type.clone()
}
} else {
return token_stream_with_error(
item,
syn::Error::new_spanned(entity_type, "entity_type must be a path"),
);
};

// if entity_type is a Vec, return type is Vec<entity_type>
// if entity_type is not a Vec, return type is Option<entity_type>
let return_type = if let syn::Type::Path(path) = entity_type.clone() {
Expand Down Expand Up @@ -63,7 +114,7 @@ pub fn expand(args: TokenStream, item: TokenStream) -> TokenStream {

if ident == "Vec" {
quote! {
scyllax::match_rows!(res, #path)
scyllax::match_rows!(res, #inner_entity_type)
}
} else {
quote! {
Expand All @@ -82,9 +133,9 @@ pub fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
#input

#[scyllax::async_trait]
impl scyllax::SelectQuery<#entity_type, #return_type> for #struct_ident {
impl scyllax::SelectQuery<#inner_entity_type, #return_type> for #struct_ident {
fn query() -> String {
#query.replace("*", &#entity_type::keys().join(", "))
#query.replace("*", &#inner_entity_type::keys().join(", "))
}

async fn prepare(db: &Executor) -> Result<scylla::prepared_statement::PreparedStatement, scylla::transport::errors::QueryError> {
Expand Down
17 changes: 17 additions & 0 deletions scyllax/src/maybe_unset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,20 @@ impl<V: Value> Value for MaybeUnset<V> {
}
}
}

// implement From<V> for MaybeUnset<V>
impl<V: Value> From<V> for MaybeUnset<V> {
fn from(v: V) -> Self {
MaybeUnset::Set(v)
}
}

// implement From<Option<V>> for MaybeUnset<V>
impl<V: Value> From<Option<V>> for MaybeUnset<V> {
fn from(v: Option<V>) -> Self {
match v {
Some(v) => MaybeUnset::Set(v),
None => MaybeUnset::Unset,
}
}
}

0 comments on commit b4e088c

Please sign in to comment.