feat(macros): add where clause
This commit is contained in:
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -794,6 +794,15 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
@@ -1214,6 +1223,7 @@ name = "sqlx-utils-macros"
|
||||
version = "0.8.6"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro-crate",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1316,6 +1326,23 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@@ -1571,6 +1598,15 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.1"
|
||||
|
||||
@@ -10,6 +10,7 @@ proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
darling = "0.21.1"
|
||||
proc-macro-crate = "3.3.0"
|
||||
proc-macro-error2 = "2.0.1"
|
||||
proc-macro2 = "1.0.96"
|
||||
quote = "1.0.40"
|
||||
|
||||
@@ -1,8 +1,29 @@
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod push_to_builder;
|
||||
mod where_clause;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::Ident;
|
||||
|
||||
#[proc_macro_derive(PushToBuilder)]
|
||||
pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
push_to_builder::push_to_builder(input.into()).into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(WhereClause, attributes(sqlxu))]
|
||||
pub fn where_clause(input: TokenStream) -> TokenStream {
|
||||
where_clause::where_clause(input.into()).into()
|
||||
}
|
||||
|
||||
fn crate_name() -> proc_macro2::TokenStream {
|
||||
match proc_macro_crate::crate_name("sqlx_utils") {
|
||||
Err(_) => quote! {::sqlx_utils},
|
||||
Ok(proc_macro_crate::FoundCrate::Itself) => quote! {crate},
|
||||
Ok(proc_macro_crate::FoundCrate::Name(e)) => {
|
||||
let ident = Ident::new(&e, Span::call_site());
|
||||
|
||||
quote! {::#ident}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{DeriveInput, Field, Ident, parse2};
|
||||
|
||||
use crate::crate_name;
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
struct PushToBuilderArgs {
|
||||
ident: Ident,
|
||||
@@ -23,6 +25,8 @@ pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
|
||||
let struct_name = args.ident;
|
||||
|
||||
let crate_name = crate_name();
|
||||
|
||||
let push_statements = args
|
||||
.data
|
||||
.take_struct()
|
||||
@@ -35,7 +39,7 @@ pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
.unwrap_or_else(|| Ident::new(&format!("{idx}"), Span::call_site()));
|
||||
|
||||
quote! {
|
||||
::sqlx_utils::builder::PushToBuilder::push_to(self.#ident, builder);
|
||||
#crate_name::builder::PushToBuilder::push_to(self.#ident, builder);
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
@@ -43,7 +47,7 @@ pub fn push_to_builder(input: TokenStream) -> TokenStream {
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
impl<DB> ::sqlx_utils::builder::PushToBuilder<DB> for #struct_name
|
||||
impl<DB> #crate_name::builder::PushToBuilder<DB> for #struct_name
|
||||
where
|
||||
DB: ::sqlx::Database
|
||||
{
|
||||
|
||||
131
macros/src/where_clause.rs
Normal file
131
macros/src/where_clause.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use darling::{FromDeriveInput, FromField, ast::Data};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
DeriveInput, Ident, Path, PathSegment, Token, Type, TypePath, parse2, punctuated::Punctuated,
|
||||
token::PathSep,
|
||||
};
|
||||
|
||||
use crate::crate_name;
|
||||
|
||||
#[derive(FromField)]
|
||||
#[darling(attributes(sqlxu))]
|
||||
struct WhereClauseField {
|
||||
ident: Option<Ident>,
|
||||
op: Option<Type>,
|
||||
rename: Option<String>,
|
||||
ty: Type,
|
||||
}
|
||||
|
||||
#[derive(FromDeriveInput)]
|
||||
#[darling(attributes(sqlxu), supports(struct_named))]
|
||||
struct WhereClauseArgs {
|
||||
ident: Ident,
|
||||
data: Data<(), WhereClauseField>,
|
||||
}
|
||||
|
||||
pub fn where_clause(input: TokenStream) -> TokenStream {
|
||||
let input: DeriveInput = match parse2(input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.into_compile_error(),
|
||||
};
|
||||
|
||||
let args = match WhereClauseArgs::from_derive_input(&input) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return e.write_errors(),
|
||||
};
|
||||
|
||||
let struct_name = args.ident;
|
||||
|
||||
let crate_name = crate_name();
|
||||
|
||||
let push_clauses = args.data.take_struct().expect("Should never be `None`").into_iter().map(|e| {
|
||||
let ident = e.ident.expect("Should never be `None`");
|
||||
|
||||
let col_name = e.rename.unwrap_or_else(|| ident.to_string());
|
||||
|
||||
let op = e.op.unwrap_or_else(|| {
|
||||
let mut segments: Punctuated<PathSegment, Token![::]> = Punctuated::new();
|
||||
segments.push_value(PathSegment::from(Ident::new("sqlx_utils", Span::call_site())));
|
||||
segments.push_punct(PathSep { spans: [Span::call_site(); 2] });
|
||||
segments.push_value(PathSegment::from(Ident::new("builder", Span::call_site())));
|
||||
segments.push_punct(PathSep { spans: [Span::call_site(); 2] });
|
||||
segments.push_value(PathSegment::from(Ident::new("expr", Span::call_site())));
|
||||
segments.push_punct(PathSep { spans: [Span::call_site(); 2] });
|
||||
segments.push_value(PathSegment::from(Ident::new("Equals", Span::call_site())));
|
||||
|
||||
Type::Path(TypePath {
|
||||
qself: None,
|
||||
path: Path {
|
||||
leading_colon: Some(PathSep {
|
||||
spans: [Span::call_site(); 2],
|
||||
}),
|
||||
segments,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
let is_option = if let Type::Path(ty) = &e.ty {
|
||||
ty.path.segments.first().is_some_and(|e| e.ident.eq("Option"))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let push_quote = quote! {
|
||||
let expr = #crate_name::builder::BracketsExpr::new(
|
||||
#crate_name::builder::BinaryExpr::new(#col_name, #crate_name::builder::expr::ensure_where(#op), ident.clone())
|
||||
);
|
||||
list.push(expr);
|
||||
};
|
||||
|
||||
if is_option {
|
||||
quote! {
|
||||
if let Some(ident) = &self.#ident {
|
||||
#push_quote
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
{
|
||||
let ident = &self.#ident;
|
||||
#push_quote;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl<DB> #crate_name::builder::PushToBuilder<DB> for #struct_name where DB: ::sqlx::Database {
|
||||
fn push_to(&self, builder: &mut ::sqlx::QueryBuilder<'_, DB>) {
|
||||
let mut list = #crate_name::builder::ExprList::<DB, #crate_name::builder::expr::And>::new(::std::vec::Vec::new());
|
||||
|
||||
#(
|
||||
#push_clauses
|
||||
)*
|
||||
|
||||
#crate_name::builder::PushToBuilder::push_to(list, builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::print_stdout)]
|
||||
mod test {
|
||||
use crate::where_clause::where_clause;
|
||||
|
||||
#[test]
|
||||
fn derive() {
|
||||
let input = r#"
|
||||
#[derive(WhereClause)]
|
||||
struct NagelTest {
|
||||
#[sqlxu(rename = "fusgesicht")]
|
||||
name: String
|
||||
}
|
||||
"#;
|
||||
|
||||
let process = where_clause(input.parse().expect("Failed parse"));
|
||||
|
||||
println!("{process}");
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,24 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> PushToBuilder<DB> for String
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
builder.push(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> PushToBuilder<DB> for &str
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
builder.push(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB, T> PushToBuilder<DB> for Option<T>
|
||||
where
|
||||
T: PushToBuilder<DB>,
|
||||
@@ -194,8 +212,12 @@ where
|
||||
{
|
||||
fn push_to(&self, builder: &mut QueryBuilder<'_, DB>) {
|
||||
match self {
|
||||
None => {}
|
||||
Some(e) => e.push_to(builder),
|
||||
None => {
|
||||
builder.push("NULL");
|
||||
}
|
||||
Some(e) => {
|
||||
e.push_to(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,10 @@ pub const fn get_where_expr<T: WhereOp>() -> &'static str {
|
||||
<T as SqlOperand>::EXPR
|
||||
}
|
||||
|
||||
pub const fn ensure_where<T: WhereOp>(t: T) -> T {
|
||||
t
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use crate::builder::expr::{
|
||||
And, Comma, Equals, Ilike, In, Is, IsNot, Like, NotEquals, NotIlike, NotIn, NotLike, Or,
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
pub mod builder;
|
||||
|
||||
pub use sqlx_utils_macros::*;
|
||||
|
||||
Reference in New Issue
Block a user