use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::format_ident;
use quote::quote;
use quote::ToTokens;
use quote::TokenStreamExt;
use std::iter;
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Pair;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
parenthesized, parse_quote, Attribute, GenericArgument, Generics, Ident, ImplGenerics, LitStr,
PathArguments, Token, Type, TypeGenerics,
};
use syn::{GenericParam, Meta};
use syn::{LitBool, Path};
use syn::{LitInt, MetaNameValue};
use crate::attrs::{AttributeSpanWrapper, MySpanned};
use crate::util::parse_eq;
const VARIADIC_VARIANTS_DEFAULT: usize = 2;
const VARIADIC_ARG_COUNT_ENV: Option<&str> = option_env!("DIESEL_VARIADIC_FUNCTION_ARGS");
pub(crate) fn expand(
input: Vec<SqlFunctionDecl>,
legacy_helper_type_and_module: bool,
generate_return_type_helpers: bool,
) -> TokenStream {
let mut result = TokenStream::new();
let mut return_type_helper_module_paths = vec![];
for decl in input {
let expanded = expand_one(
decl,
legacy_helper_type_and_module,
generate_return_type_helpers,
);
let expanded = match expanded {
Err(err) => err.into_compile_error(),
Ok(expanded) => {
if let Some(return_type_helper_module_path) =
expanded.return_type_helper_module_path
{
return_type_helper_module_paths.push(return_type_helper_module_path);
}
expanded.tokens
}
};
result.append_all(expanded);
}
if !generate_return_type_helpers {
return result;
}
quote! {
#result
#[allow(unused_imports)]
#[doc(hidden)]
mod return_type_helpers {
#(
#[doc(inline)]
pub use super:: #return_type_helper_module_paths ::*;
)*
}
}
}
struct ExpandedSqlFunction {
tokens: TokenStream,
return_type_helper_module_path: Option<Path>,
}
fn expand_one(
mut input: SqlFunctionDecl,
legacy_helper_type_and_module: bool,
generate_return_type_helpers: bool,
) -> syn::Result<ExpandedSqlFunction> {
let attributes = &mut input.attributes;
let variadic_argument_count = attributes.iter().find_map(|attr| {
if let SqlFunctionAttribute::Variadic(_, c) = &attr.item {
Some((c.base10_parse(), c.span()))
} else {
None
}
});
let Some((variadic_argument_count, variadic_span)) = variadic_argument_count else {
let sql_name = parse_sql_name_attr(&mut input);
return expand_nonvariadic(
input,
sql_name,
legacy_helper_type_and_module,
generate_return_type_helpers,
);
};
let variadic_argument_count = variadic_argument_count?;
let variadic_variants = VARIADIC_ARG_COUNT_ENV
.and_then(|arg_count| arg_count.parse::<usize>().ok())
.unwrap_or(VARIADIC_VARIANTS_DEFAULT);
let mut result = TokenStream::new();
let mut helper_type_modules = vec![];
for variant_no in 0..=variadic_variants {
let expanded = expand_variadic(
input.clone(),
legacy_helper_type_and_module,
generate_return_type_helpers,
variadic_argument_count,
variant_no,
variadic_span,
)?;
if let Some(return_type_helper_module_path) = expanded.return_type_helper_module_path {
helper_type_modules.push(return_type_helper_module_path);
}
result.append_all(expanded.tokens);
}
if generate_return_type_helpers {
let return_types_module_name = Ident::new(
&format!("__{}_return_types", input.fn_name),
input.fn_name.span(),
);
let result = quote! {
#result
#[allow(unused_imports)]
#[doc(inline)]
mod #return_types_module_name {
#(
#[doc(inline)]
pub use super:: #helper_type_modules ::*;
)*
}
};
let return_type_helper_module_path = Some(parse_quote! {
#return_types_module_name
});
Ok(ExpandedSqlFunction {
tokens: result,
return_type_helper_module_path,
})
} else {
Ok(ExpandedSqlFunction {
tokens: result,
return_type_helper_module_path: None,
})
}
}
fn expand_variadic(
mut input: SqlFunctionDecl,
legacy_helper_type_and_module: bool,
generate_return_type_helpers: bool,
variadic_argument_count: usize,
variant_no: usize,
variadic_span: Span,
) -> syn::Result<ExpandedSqlFunction> {
add_variadic_doc_comments(&mut input.attributes, &input.fn_name.to_string());
let sql_name = parse_sql_name_attr(&mut input);
input.fn_name = format_ident!("{}_{}", input.fn_name, variant_no);
let nonvariadic_args_count = input
.args
.len()
.checked_sub(variadic_argument_count)
.ok_or_else(|| {
syn::Error::new(
variadic_span,
"invalid variadic argument count: not enough function arguments",
)
})?;
let mut variadic_generic_indexes = vec![];
let mut arguments_with_generic_types = vec![];
for (arg_idx, arg) in input.args.iter().skip(nonvariadic_args_count).enumerate() {
let Type::Path(ty_path) = arg.ty.clone() else {
continue;
};
let Ok(ty_ident) = ty_path.path.require_ident() else {
continue;
};
let idx = input.generics.params.iter().position(|param| match param {
GenericParam::Type(type_param) => type_param.ident == *ty_ident,
_ => false,
});
if let Some(idx) = idx {
variadic_generic_indexes.push(idx);
arguments_with_generic_types.push(arg_idx);
}
}
let mut args: Vec<_> = input.args.into_pairs().collect();
let variadic_args = args.split_off(nonvariadic_args_count);
let nonvariadic_args = args;
let variadic_args: Vec<_> = iter::repeat_n(variadic_args, variant_no)
.enumerate()
.flat_map(|(arg_group_idx, arg_group)| {
let mut resulting_args = vec![];
for (arg_idx, arg) in arg_group.into_iter().enumerate() {
let mut arg = arg.into_value();
arg.name = format_ident!("{}_{}", arg.name, arg_group_idx + 1);
if arguments_with_generic_types.contains(&arg_idx) {
let Type::Path(mut ty_path) = arg.ty.clone() else {
unreachable!("This argument should have path type as checked earlier")
};
let Ok(ident) = ty_path.path.require_ident() else {
unreachable!("This argument should have ident type as checked earlier")
};
ty_path.path.segments[0].ident =
format_ident!("{}{}", ident, arg_group_idx + 1);
arg.ty = Type::Path(ty_path);
}
let pair = Pair::new(arg, Some(Token])));
resulting_args.push(pair);
}
resulting_args
})
.collect();
input.args = nonvariadic_args.into_iter().chain(variadic_args).collect();
let generics: Vec<_> = input.generics.params.into_pairs().collect();
input.generics.params = if variant_no == 0 {
generics
.into_iter()
.enumerate()
.filter_map(|(generic_idx, generic)| {
(!variadic_generic_indexes.contains(&generic_idx)).then_some(generic)
})
.collect()
} else {
iter::repeat_n(generics, variant_no)
.enumerate()
.flat_map(|(generic_group_idx, generic_group)| {
let mut resulting_generics = vec![];
for (generic_idx, generic) in generic_group.into_iter().enumerate() {
if !variadic_generic_indexes.contains(&generic_idx) {
if generic_group_idx == 0 {
resulting_generics.push(generic);
}
continue;
}
let mut generic = generic.into_value();
if let GenericParam::Type(type_param) = &mut generic {
type_param.ident =
format_ident!("{}{}", type_param.ident, generic_group_idx + 1);
} else {
unreachable!("This generic should be a type param as checked earlier")
}
let pair = Pair::new(generic, Some(Token])));
resulting_generics.push(pair);
}
resulting_generics
})
.collect()
};
expand_nonvariadic(
input,
sql_name,
legacy_helper_type_and_module,
generate_return_type_helpers,
)
}
fn add_variadic_doc_comments(
attributes: &mut Vec<AttributeSpanWrapper<SqlFunctionAttribute>>,
fn_name: &str,
) {
let doc_comments_end = attributes.len()
- attributes
.iter()
.rev()
.position(|attr| matches!(&attr.item, SqlFunctionAttribute::Other(syn::Attribute{ meta: Meta::NameValue(MetaNameValue { path, .. }), ..}) if path.is_ident("doc")))
.unwrap_or(attributes.len());
let fn_family = format!("`{fn_name}_0`, `{fn_name}_1`, ... `{fn_name}_n`");
let doc_comments: Vec<Attribute> = parse_quote! {
#[doc = #fn_family]
#[doc(alias = #fn_name)]
};
for (doc_comments_end, new_attribute) in
(attributes.len() - doc_comments_end..).zip(doc_comments)
{
attributes.insert(
doc_comments_end,
AttributeSpanWrapper {
item: SqlFunctionAttribute::Other(new_attribute),
attribute_span: Span::mixed_site(),
ident_span: Span::mixed_site(),
},
);
}
}
fn parse_sql_name_attr(input: &mut SqlFunctionDecl) -> String {
let result = input
.attributes
.iter()
.find_map(|attr| match attr.item {
SqlFunctionAttribute::SqlName(_, ref value) => Some(value.value()),
_ => None,
})
.unwrap_or_else(|| input.fn_name.to_string());
result
}
fn expand_nonvariadic(
input: SqlFunctionDecl,
sql_name: String,
legacy_helper_type_and_module: bool,
generate_return_type_helpers: bool,
) -> syn::Result<ExpandedSqlFunction> {
let SqlFunctionDecl {
attributes,
fn_token,
fn_name,
mut generics,
args,
return_type,
} = input;
let is_aggregate = attributes
.iter()
.any(|attr| matches!(attr.item, SqlFunctionAttribute::Aggregate(..)));
let can_be_called_directly = !function_cannot_be_called_directly(&attributes);
let skip_return_type_helper = attributes
.iter()
.any(|attr| matches!(attr.item, SqlFunctionAttribute::SkipReturnTypeHelper(..)));
let window_attrs = attributes
.iter()
.filter(|a| matches!(a.item, SqlFunctionAttribute::Window { .. }))
.cloned()
.collect::<Vec<_>>();
let restrictions = attributes
.iter()
.find_map(|a| match a.item {
SqlFunctionAttribute::Restriction(ref r) => Some(r.clone()),
_ => None,
})
.unwrap_or_default();
let attributes = attributes
.into_iter()
.filter_map(|a| match a.item {
SqlFunctionAttribute::Other(a) => Some(a),
_ => None,
})
.collect::<Vec<_>>();
let (ref arg_name, ref arg_type): (Vec<_>, Vec<_>) = args
.iter()
.map(|StrictFnArg { name, ty, .. }| (name, ty))
.unzip();
let arg_struct_assign = args.iter().map(
|StrictFnArg {
name, colon_token, ..
}| {
let name2 = name.clone();
quote!(#name #colon_token #name2.as_expression())
},
);
let type_args = &generics
.type_params()
.map(|type_param| type_param.ident.clone())
.collect::<Vec<_>>();
for StrictFnArg { name, .. } in &args {
generics.params.push(parse_quote!(#name));
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let where_clause = where_clause
.map(|w| quote!(#w))
.unwrap_or_else(|| quote!(where));
let mut generics_with_internal = generics.clone();
generics_with_internal
.params
.push(parse_quote!(__DieselInternal));
let (impl_generics_internal, _, _) = generics_with_internal.split_for_impl();
let sql_type;
let numeric_derive;
if arg_name.is_empty() {
sql_type = None;
numeric_derive = None;
} else {
sql_type = Some(quote!((#(#arg_name),*): Expression,));
numeric_derive = Some(quote!(#[derive(diesel::sql_types::DieselNumericOps)]));
}
let helper_type_doc = format!("The return type of [`{fn_name}()`](fn@{fn_name})");
let query_fragment_impl =
can_be_called_directly.then_some(restrictions.generate_all_queryfragment_impls(
generics.clone(),
&ty_generics,
arg_name,
&fn_name,
));
let args_iter = args.iter();
let mut tokens = quote! {
use diesel::{self, QueryResult};
use diesel::expression::{AsExpression, Expression, SelectableExpression, AppearsOnTable, ValidGrouping};
use diesel::query_builder::{QueryFragment, AstPass};
use diesel::sql_types::*;
use diesel::internal::sql_functions::*;
use super::*;
#[derive(Debug, Clone, Copy, diesel::query_builder::QueryId)]
#numeric_derive
pub struct #fn_name #ty_generics {
#(pub(in super) #args_iter,)*
#(pub(in super) #type_args: ::std::marker::PhantomData<#type_args>,)*
}
#[doc = #helper_type_doc]
pub type HelperType #ty_generics = #fn_name <
#(#type_args,)*
#(<#arg_name as AsExpression<#arg_type>>::Expression,)*
>;
impl #impl_generics Expression for #fn_name #ty_generics
#where_clause
#sql_type
{
type SqlType = #return_type;
}
impl #impl_generics_internal SelectableExpression<__DieselInternal>
for #fn_name #ty_generics
#where_clause
#(#arg_name: SelectableExpression<__DieselInternal>,)*
Self: AppearsOnTable<__DieselInternal>,
{
}
impl #impl_generics_internal AppearsOnTable<__DieselInternal>
for #fn_name #ty_generics
#where_clause
#(#arg_name: AppearsOnTable<__DieselInternal>,)*
Self: Expression,
{
}
impl #impl_generics_internal FunctionFragment<__DieselInternal>
for #fn_name #ty_generics
where
__DieselInternal: diesel::backend::Backend,
#(#arg_name: QueryFragment<__DieselInternal>,)*
{
const FUNCTION_NAME: &'static str = #sql_name;
#[allow(unused_assignments)]
fn walk_arguments<'__b>(&'__b self, mut out: AstPass<'_, '__b, __DieselInternal>) -> QueryResult<()> {
let mut needs_comma = false;
#(
if !self.#arg_name.is_noop(out.backend())? {
if needs_comma {
out.push_sql(", ");
}
self.#arg_name.walk_ast(out.reborrow())?;
needs_comma = true;
}
)*
Ok(())
}
}
#query_fragment_impl
};
let is_supported_on_sqlite = cfg!(feature = "sqlite")
&& type_args.is_empty()
&& is_sqlite_type(&return_type)
&& arg_type.iter().all(|a| is_sqlite_type(a));
for window in &window_attrs {
tokens.extend(generate_window_function_tokens(
window,
generics.clone(),
&ty_generics,
&fn_name,
));
}
if !window_attrs.is_empty() {
tokens.extend(quote::quote! {
impl #impl_generics IsWindowFunction for #fn_name #ty_generics {
type ArgTypes = (#(#arg_name,)*);
}
});
}
if is_aggregate {
tokens = generate_tokens_for_aggregate_functions(
tokens,
&impl_generics_internal,
&impl_generics,
&fn_name,
&ty_generics,
arg_name,
arg_type,
is_supported_on_sqlite,
!window_attrs.is_empty(),
&return_type,
&sql_name,
);
} else if window_attrs.is_empty() {
tokens = generate_tokens_for_non_aggregate_functions(
tokens,
&impl_generics_internal,
&fn_name,
&ty_generics,
arg_name,
arg_type,
is_supported_on_sqlite,
&return_type,
&sql_name,
);
}
let args_iter = args.iter();
let (outside_of_module_helper_type, return_type_path, internals_module_name) =
if legacy_helper_type_and_module {
(None, quote! { #fn_name::HelperType }, fn_name.clone())
} else {
let internals_module_name = Ident::new(&format!("{fn_name}_utils"), fn_name.span());
(
Some(quote! {
#[allow(non_camel_case_types, non_snake_case)]
#[doc = #helper_type_doc]
pub type #fn_name #ty_generics = #internals_module_name::#fn_name <
#(#type_args,)*
#(<#arg_name as diesel::expression::AsExpression<#arg_type>>::Expression,)*
>;
}),
quote! { #fn_name },
internals_module_name,
)
};
let (return_type_helper_module, return_type_helper_module_path) =
if !generate_return_type_helpers || skip_return_type_helper {
(None, None)
} else {
let auto_derived_types = type_args
.iter()
.map(|type_arg| {
for arg in &args {
let Type::Path(path) = &arg.ty else {
continue;
};
let Some(path_ident) = path.path.get_ident() else {
continue;
};
if path_ident == type_arg {
return Ok(arg.name.clone());
}
}
Err(syn::Error::new(
type_arg.span(),
"cannot find argument corresponding to the generic",
))
})
.collect::<Result<Vec<_>>>()?;
let arg_names_iter: Vec<_> = args.iter().map(|arg| arg.name.clone()).collect();
let return_type_module_name =
Ident::new(&format!("__{fn_name}_return_type"), fn_name.span());
let doc =
format!("Return type of the [`{fn_name}()`](fn@super::{fn_name}) SQL function.");
let return_type_helper_module = quote! {
#[allow(non_camel_case_types, non_snake_case, unused_imports)]
#[doc(inline)]
mod #return_type_module_name {
#[doc = #doc]
pub type #fn_name<
#(#arg_names_iter,)*
> = super::#fn_name<
#( <#auto_derived_types as diesel::expression::Expression>::SqlType, )*
#(#arg_names_iter,)*
>;
}
};
let module_path = parse_quote!(
#return_type_module_name
);
(Some(return_type_helper_module), Some(module_path))
};
let tokens = quote! {
#(#attributes)*
#[allow(non_camel_case_types)]
pub #fn_token #fn_name #impl_generics (#(#args_iter,)*)
-> #return_type_path #ty_generics
#where_clause
#(#arg_name: diesel::expression::AsExpression<#arg_type>,)*
{
#internals_module_name::#fn_name {
#(#arg_struct_assign,)*
#(#type_args: ::std::marker::PhantomData,)*
}
}
#outside_of_module_helper_type
#return_type_helper_module
#[doc(hidden)]
#[allow(non_camel_case_types, non_snake_case, unused_imports)]
pub(crate) mod #internals_module_name {
#tokens
}
};
Ok(ExpandedSqlFunction {
tokens,
return_type_helper_module_path,
})
}
fn generate_window_function_tokens(
window: &AttributeSpanWrapper<SqlFunctionAttribute>,
generics: Generics,
ty_generics: &TypeGenerics<'_>,
fn_name: &Ident,
) -> TokenStream {
let SqlFunctionAttribute::Window {
restrictions,
require_order,
..
} = &window.item
else {
unreachable!("We filtered for window attributes above")
};
restrictions.generate_all_window_fragment_impls(
generics,
ty_generics,
fn_name,
require_order.unwrap_or_default(),
)
}
#[allow(clippy::too_many_arguments)]
fn generate_tokens_for_non_aggregate_functions(
mut tokens: TokenStream,
impl_generics_internal: &syn::ImplGenerics<'_>,
fn_name: &syn::Ident,
ty_generics: &syn::TypeGenerics<'_>,
arg_name: &[&syn::Ident],
arg_type: &[&syn::Type],
is_supported_on_sqlite: bool,
return_type: &syn::Type,
sql_name: &str,
) -> TokenStream {
tokens = quote! {
#tokens
#[derive(ValidGrouping)]
pub struct __Derived<#(#arg_name,)*>(#(#arg_name,)*);
impl #impl_generics_internal ValidGrouping<__DieselInternal>
for #fn_name #ty_generics
where
__Derived<#(#arg_name,)*>: ValidGrouping<__DieselInternal>,
{
type IsAggregate = <__Derived<#(#arg_name,)*> as ValidGrouping<__DieselInternal>>::IsAggregate;
}
};
if is_supported_on_sqlite && !arg_name.is_empty() {
tokens = quote! {
#tokens
use diesel::sqlite::{Sqlite, SqliteConnection};
use diesel::serialize::ToSql;
use diesel::deserialize::{FromSqlRow, StaticallySizedRow};
#[allow(dead_code)]
pub fn register_impl<F, Ret, #(#arg_name,)*>(
conn: &mut SqliteConnection,
f: F,
) -> QueryResult<()>
where
F: Fn(#(#arg_name,)*) -> Ret + std::panic::UnwindSafe + Send + 'static,
(#(#arg_name,)*): FromSqlRow<(#(#arg_type,)*), Sqlite> +
StaticallySizedRow<(#(#arg_type,)*), Sqlite>,
Ret: ToSql<#return_type, Sqlite>,
{
conn.register_sql_function::<(#(#arg_type,)*), #return_type, _, _, _>(
#sql_name,
true,
move |(#(#arg_name,)*)| f(#(#arg_name,)*),
)
}
#[allow(dead_code)]
pub fn register_nondeterministic_impl<F, Ret, #(#arg_name,)*>(
conn: &mut SqliteConnection,
mut f: F,
) -> QueryResult<()>
where
F: FnMut(#(#arg_name,)*) -> Ret + std::panic::UnwindSafe + Send + 'static,
(#(#arg_name,)*): FromSqlRow<(#(#arg_type,)*), Sqlite> +
StaticallySizedRow<(#(#arg_type,)*), Sqlite>,
Ret: ToSql<#return_type, Sqlite>,
{
conn.register_sql_function::<(#(#arg_type,)*), #return_type, _, _, _>(
#sql_name,
false,
move |(#(#arg_name,)*)| f(#(#arg_name,)*),
)
}
};
}
if is_supported_on_sqlite && arg_name.is_empty() {
tokens = quote! {
#tokens
use diesel::sqlite::{Sqlite, SqliteConnection};
use diesel::serialize::ToSql;
#[allow(dead_code)]
pub fn register_impl<F, Ret>(
conn: &SqliteConnection,
f: F,
) -> QueryResult<()>
where
F: Fn() -> Ret + std::panic::UnwindSafe + Send + 'static,
Ret: ToSql<#return_type, Sqlite>,
{
conn.register_noarg_sql_function::<#return_type, _, _>(
#sql_name,
true,
f,
)
}
#[allow(dead_code)]
pub fn register_nondeterministic_impl<F, Ret>(
conn: &SqliteConnection,
mut f: F,
) -> QueryResult<()>
where
F: FnMut() -> Ret + std::panic::UnwindSafe + Send + 'static,
Ret: ToSql<#return_type, Sqlite>,
{
conn.register_noarg_sql_function::<#return_type, _, _>(
#sql_name,
false,
f,
)
}
};
}
tokens
}
#[allow(clippy::too_many_arguments)]
fn generate_tokens_for_aggregate_functions(
mut tokens: TokenStream,
impl_generics_internal: &syn::ImplGenerics<'_>,
impl_generics: &syn::ImplGenerics<'_>,
fn_name: &syn::Ident,
ty_generics: &syn::TypeGenerics<'_>,
arg_name: &[&syn::Ident],
arg_type: &[&syn::Type],
is_supported_on_sqlite: bool,
is_window: bool,
return_type: &syn::Type,
sql_name: &str,
) -> TokenStream {
tokens = quote! {
#tokens
impl #impl_generics_internal ValidGrouping<__DieselInternal>
for #fn_name #ty_generics
{
type IsAggregate = diesel::expression::is_aggregate::Yes;
}
impl #impl_generics IsAggregateFunction for #fn_name #ty_generics {}
};
if is_supported_on_sqlite && !is_window {
tokens = quote! {
#tokens
use diesel::sqlite::{Sqlite, SqliteConnection};
use diesel::serialize::ToSql;
use diesel::deserialize::{FromSqlRow, StaticallySizedRow};
use diesel::sqlite::SqliteAggregateFunction;
use diesel::sql_types::IntoNullable;
};
match arg_name.len() {
x if x > 1 => {
tokens = quote! {
#tokens
#[allow(dead_code)]
pub fn register_impl<A, #(#arg_name,)*>(
conn: &mut SqliteConnection
) -> QueryResult<()>
where
A: SqliteAggregateFunction<(#(#arg_name,)*)>
+ Send
+ 'static
+ ::std::panic::UnwindSafe
+ ::std::panic::RefUnwindSafe,
A::Output: ToSql<#return_type, Sqlite>,
(#(#arg_name,)*): FromSqlRow<(#(#arg_type,)*), Sqlite> +
StaticallySizedRow<(#(#arg_type,)*), Sqlite> +
::std::panic::UnwindSafe,
{
conn.register_aggregate_function::<(#(#arg_type,)*), #return_type, _, _, A>(#sql_name)
}
};
}
1 => {
let arg_name = arg_name[0];
let arg_type = arg_type[0];
tokens = quote! {
#tokens
#[allow(dead_code)]
pub fn register_impl<A, #arg_name>(
conn: &mut SqliteConnection
) -> QueryResult<()>
where
A: SqliteAggregateFunction<#arg_name>
+ Send
+ 'static
+ std::panic::UnwindSafe
+ std::panic::RefUnwindSafe,
A::Output: ToSql<#return_type, Sqlite>,
#arg_name: FromSqlRow<#arg_type, Sqlite> +
StaticallySizedRow<#arg_type, Sqlite> +
::std::panic::UnwindSafe,
{
conn.register_aggregate_function::<#arg_type, #return_type, _, _, A>(#sql_name)
}
};
}
_ => (),
}
}
tokens
}
fn function_cannot_be_called_directly(
attributes: &[AttributeSpanWrapper<SqlFunctionAttribute>],
) -> bool {
let mut has_aggregate = false;
let mut has_window = false;
for attr in attributes {
has_aggregate = has_aggregate || matches!(attr.item, SqlFunctionAttribute::Aggregate(..));
has_window = has_window || matches!(attr.item, SqlFunctionAttribute::Window { .. });
}
has_window && !has_aggregate
}
pub(crate) struct ExternSqlBlock {
pub(crate) function_decls: Vec<SqlFunctionDecl>,
}
impl Parse for ExternSqlBlock {
fn parse(input: ParseStream) -> Result<Self> {
let mut error = None::<syn::Error>;
let mut combine_error = |e: syn::Error| {
error = Some(
error
.take()
.map(|mut o| {
o.combine(e.clone());
o
})
.unwrap_or(e),
)
};
let block = syn::ItemForeignMod::parse(input)?;
if block.abi.name.as_ref().map(|n| n.value()) != Some("SQL".into()) {
return Err(syn::Error::new(block.abi.span(), "expect `SQL` as ABI"));
}
if let Some(unsafety) = block.unsafety {
return Err(syn::Error::new(
unsafety.span(),
"expect `SQL` function blocks to be safe",
));
}
let parsed_block_attrs = parse_attributes(&mut combine_error, block.attrs);
let item_count = block.items.len();
let function_decls_input = block
.items
.into_iter()
.map(|i| syn::parse2::<SqlFunctionDecl>(quote! { #i }));
let mut function_decls = Vec::with_capacity(item_count);
for decl in function_decls_input {
match decl {
Ok(mut decl) => {
decl.attributes = merge_attributes(&parsed_block_attrs, decl.attributes);
function_decls.push(decl)
}
Err(e) => {
error = Some(
error
.take()
.map(|mut o| {
o.combine(e.clone());
o
})
.unwrap_or(e),
);
}
}
}
error
.map(Err)
.unwrap_or(Ok(ExternSqlBlock { function_decls }))
}
}
fn merge_attributes(
parsed_block_attrs: &[AttributeSpanWrapper<SqlFunctionAttribute>],
mut attributes: Vec<AttributeSpanWrapper<SqlFunctionAttribute>>,
) -> Vec<AttributeSpanWrapper<SqlFunctionAttribute>> {
for attr in parsed_block_attrs {
if attributes.iter().all(|a| match (&a.item, &attr.item) {
(SqlFunctionAttribute::Aggregate(_), SqlFunctionAttribute::Aggregate(_)) => todo!(),
(SqlFunctionAttribute::Window { .. }, SqlFunctionAttribute::Window { .. })
| (SqlFunctionAttribute::SqlName(_, _), SqlFunctionAttribute::SqlName(_, _))
| (SqlFunctionAttribute::Restriction(_), SqlFunctionAttribute::Restriction(_))
| (SqlFunctionAttribute::Variadic(_, _), SqlFunctionAttribute::Variadic(_, _))
| (
SqlFunctionAttribute::SkipReturnTypeHelper(_),
SqlFunctionAttribute::SkipReturnTypeHelper(_),
) => false,
_ => true,
}) {
attributes.push(attr.clone());
}
}
attributes
}
#[derive(Clone)]
pub(crate) struct SqlFunctionDecl {
attributes: Vec<AttributeSpanWrapper<SqlFunctionAttribute>>,
fn_token: Token![fn],
fn_name: Ident,
generics: Generics,
args: Punctuated<StrictFnArg, Token![,]>,
return_type: Type,
}
impl Parse for SqlFunctionDecl {
fn parse(input: ParseStream) -> Result<Self> {
let mut error = None::<syn::Error>;
let mut combine_error = |e: syn::Error| {
error = Some(
error
.take()
.map(|mut o| {
o.combine(e.clone());
o
})
.unwrap_or(e),
)
};
let attributes = Attribute::parse_outer(input).unwrap_or_else(|e| {
combine_error(e);
Vec::new()
});
let attributes_collected = parse_attributes(&mut combine_error, attributes);
let fn_token: Token![fn] = input.parse().unwrap_or_else(|e| {
combine_error(e);
Default::default()
});
let fn_name = Ident::parse(input).unwrap_or_else(|e| {
combine_error(e);
Ident::new("dummy", Span::call_site())
});
let generics = Generics::parse(input).unwrap_or_else(|e| {
combine_error(e);
Generics {
lt_token: None,
params: Punctuated::new(),
gt_token: None,
where_clause: None,
}
});
let args;
let _paren = parenthesized!(args in input);
let args = args
.parse_terminated(StrictFnArg::parse, Token![,])
.unwrap_or_else(|e| {
combine_error(e);
Punctuated::new()
});
let rarrow = Option::<Token![->]>::parse(input).unwrap_or_else(|e| {
combine_error(e);
None
});
let return_type = if rarrow.is_some() {
Type::parse(input).unwrap_or_else(|e| {
combine_error(e);
Type::Never(syn::TypeNever {
bang_token: Default::default(),
})
})
} else {
parse_quote!(diesel::expression::expression_types::NotSelectable)
};
let _semi = Option::<Token![;]>::parse(input).unwrap_or_else(|e| {
combine_error(e);
None
});
error.map(Err).unwrap_or(Ok(Self {
attributes: attributes_collected,
fn_token,
fn_name,
generics,
args,
return_type,
}))
}
}
fn parse_attribute(
attr: syn::Attribute,
) -> Result<Option<AttributeSpanWrapper<SqlFunctionAttribute>>> {
match &attr.meta {
syn::Meta::NameValue(syn::MetaNameValue {
path,
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(sql_name),
..
}),
..
}) if path.is_ident("sql_name") => Ok(Some(AttributeSpanWrapper {
attribute_span: attr.span(),
ident_span: sql_name.span(),
item: SqlFunctionAttribute::SqlName(path.require_ident()?.clone(), sql_name.clone()),
})),
syn::Meta::Path(path) if path.is_ident("aggregate") => Ok(Some(AttributeSpanWrapper {
attribute_span: attr.span(),
ident_span: path.span(),
item: SqlFunctionAttribute::Aggregate(
path.require_ident()
.map_err(|e| {
syn::Error::new(
e.span(),
format!("{e}, the correct format is `#[aggregate]`"),
)
})?
.clone(),
),
})),
syn::Meta::Path(path) if path.is_ident("skip_return_type_helper") => {
Ok(Some(AttributeSpanWrapper {
ident_span: attr.span(),
attribute_span: path.span(),
item: SqlFunctionAttribute::SkipReturnTypeHelper(
path.require_ident()
.map_err(|e| {
syn::Error::new(
e.span(),
format!("{e}, the correct format is `#[skip_return_type_helper]`"),
)
})?
.clone(),
),
}))
}
syn::Meta::Path(path) if path.is_ident("window") => Ok(Some(AttributeSpanWrapper {
attribute_span: attr.span(),
ident_span: path.span(),
item: SqlFunctionAttribute::Window {
ident: path
.require_ident()
.map_err(|e| {
syn::Error::new(e.span(), format!("{e}, the correct format is `#[window]`"))
})?
.clone(),
restrictions: BackendRestriction::None,
require_order: None,
},
})),
syn::Meta::List(syn::MetaList {
path,
delimiter: syn::MacroDelimiter::Paren(_),
tokens,
}) if path.is_ident("variadic") => {
let count: syn::LitInt = syn::parse2(tokens.clone()).map_err(|e| {
syn::Error::new(
e.span(),
format!("{e}, the correct format is `#[variadic(3)]`"),
)
})?;
Ok(Some(AttributeSpanWrapper {
item: SqlFunctionAttribute::Variadic(
path.require_ident()
.map_err(|e| {
syn::Error::new(
e.span(),
format!("{e}, the correct format is `#[variadic(3)]`"),
)
})?
.clone(),
count.clone(),
),
attribute_span: attr.span(),
ident_span: path.require_ident()?.span(),
}))
}
syn::Meta::NameValue(_) | syn::Meta::Path(_) => Ok(Some(AttributeSpanWrapper {
attribute_span: attr.span(),
ident_span: attr.span(),
item: SqlFunctionAttribute::Other(attr),
})),
syn::Meta::List(_) => {
let name = attr.meta.path().require_ident()?;
let attribute_span = attr.meta.span();
attr.clone()
.parse_args_with(|input: &syn::parse::ParseBuffer| {
SqlFunctionAttribute::parse_attr(
name.clone(),
input,
attr.clone(),
attribute_span,
)
})
}
}
}
fn parse_attributes(
combine_error: &mut impl FnMut(syn::Error),
attributes: Vec<Attribute>,
) -> Vec<AttributeSpanWrapper<SqlFunctionAttribute>> {
let attribute_count = attributes.len();
let attributes = attributes
.into_iter()
.filter_map(|attr| parse_attribute(attr).transpose());
let mut attributes_collected = Vec::with_capacity(attribute_count);
for attr in attributes {
match attr {
Ok(attr) => attributes_collected.push(attr),
Err(e) => {
combine_error(e);
}
}
}
attributes_collected
}
#[derive(Clone)]
struct StrictFnArg {
name: Ident,
colon_token: Token![:],
ty: Type,
}
impl Parse for StrictFnArg {
fn parse(input: ParseStream) -> Result<Self> {
let name = input.parse()?;
let colon_token = input.parse()?;
let ty = input.parse()?;
Ok(Self {
name,
colon_token,
ty,
})
}
}
impl ToTokens for StrictFnArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.name.to_tokens(tokens);
self.colon_token.to_tokens(tokens);
self.name.to_tokens(tokens);
}
}
fn is_sqlite_type(ty: &Type) -> bool {
let last_segment = if let Type::Path(tp) = ty {
if let Some(segment) = tp.path.segments.last() {
segment
} else {
return false;
}
} else {
return false;
};
let ident = last_segment.ident.to_string();
if ident == "Nullable" {
if let PathArguments::AngleBracketed(ref ab) = last_segment.arguments {
if let Some(GenericArgument::Type(ty)) = ab.args.first() {
return is_sqlite_type(ty);
}
}
return false;
}
[
"BigInt",
"Binary",
"Bool",
"Date",
"Double",
"Float",
"Integer",
"Numeric",
"SmallInt",
"Text",
"Time",
"Timestamp",
]
.contains(&ident.as_str())
}
#[derive(Default, Clone, Debug)]
enum BackendRestriction {
#[default]
None,
SqlDialect(syn::Ident, syn::Ident, syn::Path),
BackendBound(
syn::Ident,
syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>,
),
Backends(
syn::Ident,
syn::punctuated::Punctuated<syn::Path, syn::Token![,]>,
),
}
impl BackendRestriction {
fn parse_from(input: &syn::parse::ParseBuffer<'_>) -> Result<Self> {
if input.is_empty() {
return Ok(Self::None);
}
Self::parse(input)
}
fn parse_backends(
input: &syn::parse::ParseBuffer<'_>,
name: Ident,
) -> Result<BackendRestriction> {
let backends = Punctuated::parse_terminated(input)?;
Ok(Self::Backends(name, backends))
}
fn parse_sql_dialect(
content: &syn::parse::ParseBuffer<'_>,
name: Ident,
) -> Result<BackendRestriction> {
let dialect = content.parse()?;
let _del: syn::Token![,] = content.parse()?;
let dialect_variant = content.parse()?;
Ok(Self::SqlDialect(name, dialect, dialect_variant))
}
fn parse_backend_bounds(
input: &syn::parse::ParseBuffer<'_>,
name: Ident,
) -> Result<BackendRestriction> {
let restrictions = Punctuated::parse_terminated(input)?;
Ok(Self::BackendBound(name, restrictions))
}
fn generate_all_window_fragment_impls(
&self,
mut generics: Generics,
ty_generics: &TypeGenerics<'_>,
fn_name: &syn::Ident,
require_order: bool,
) -> TokenStream {
generics.params.push(parse_quote!(__P));
generics.params.push(parse_quote!(__O));
generics.params.push(parse_quote!(__F));
let order = if require_order {
quote::quote! {
diesel::internal::sql_functions::Order<__O, true>
}
} else {
quote::quote! {__O}
};
match *self {
BackendRestriction::None => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
Self::generate_window_fragment_impl(
parse_quote!(__DieselInternal),
Some(parse_quote!(__DieselInternal: diesel::backend::Backend,)),
&impl_generics,
ty_generics,
fn_name,
None,
&order,
)
}
BackendRestriction::SqlDialect(_, ref dialect, ref dialect_type) => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
let mut out = quote::quote! {
impl #impl_generics WindowFunctionFragment<#fn_name #ty_generics, __DieselInternal>
for OverClause<__P, #order, __F>
where
Self: WindowFunctionFragment<#fn_name #ty_generics, __DieselInternal, <__DieselInternal as diesel::backend::SqlDialect>::#dialect>,
__DieselInternal: diesel::backend::Backend,
{
}
};
let specific_impl = Self::generate_window_fragment_impl(
parse_quote!(__DieselInternal),
Some(
parse_quote!(__DieselInternal: diesel::backend::Backend + diesel::backend::SqlDialect<#dialect = #dialect_type>,),
),
&impl_generics,
ty_generics,
fn_name,
Some(dialect_type),
&order,
);
out.extend(specific_impl);
out
}
BackendRestriction::BackendBound(_, ref restriction) => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
Self::generate_window_fragment_impl(
parse_quote!(__DieselInternal),
Some(parse_quote!(__DieselInternal: diesel::backend::Backend + #restriction,)),
&impl_generics,
ty_generics,
fn_name,
None,
&order,
)
}
BackendRestriction::Backends(_, ref backends) => {
let (impl_generics, _, _) = generics.split_for_impl();
let backends = backends.iter().map(|b| {
Self::generate_window_fragment_impl(
quote! {#b},
None,
&impl_generics,
ty_generics,
fn_name,
None,
&order,
)
});
parse_quote!(#(#backends)*)
}
}
}
fn generate_window_fragment_impl(
backend: TokenStream,
backend_bound: Option<proc_macro2::TokenStream>,
impl_generics: &ImplGenerics<'_>,
ty_generics: &TypeGenerics<'_>,
fn_name: &syn::Ident,
dialect: Option<&syn::Path>,
order: &TokenStream,
) -> TokenStream {
quote::quote! {
impl #impl_generics WindowFunctionFragment<#fn_name #ty_generics, #backend, #dialect> for OverClause<__P, #order, __F>
where #backend_bound
{
}
}
}
fn generate_all_queryfragment_impls(
&self,
mut generics: Generics,
ty_generics: &TypeGenerics<'_>,
arg_name: &[&syn::Ident],
fn_name: &syn::Ident,
) -> proc_macro2::TokenStream {
match *self {
BackendRestriction::None => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
Self::generate_queryfragment_impl(
parse_quote!(__DieselInternal),
Some(parse_quote!(__DieselInternal: diesel::backend::Backend,)),
&impl_generics,
ty_generics,
arg_name,
fn_name,
None,
)
}
BackendRestriction::BackendBound(_, ref restriction) => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
Self::generate_queryfragment_impl(
parse_quote!(__DieselInternal),
Some(parse_quote!(__DieselInternal: diesel::backend::Backend + #restriction,)),
&impl_generics,
ty_generics,
arg_name,
fn_name,
None,
)
}
BackendRestriction::SqlDialect(_, ref dialect, ref dialect_type) => {
generics.params.push(parse_quote!(__DieselInternal));
let (impl_generics, _, _) = generics.split_for_impl();
let specific_impl = Self::generate_queryfragment_impl(
parse_quote!(__DieselInternal),
Some(
parse_quote!(__DieselInternal: diesel::backend::Backend + diesel::backend::SqlDialect<#dialect = #dialect_type>,),
),
&impl_generics,
ty_generics,
arg_name,
fn_name,
Some(dialect_type),
);
quote::quote! {
impl #impl_generics QueryFragment<__DieselInternal>
for #fn_name #ty_generics
where
Self: QueryFragment<__DieselInternal, <__DieselInternal as diesel::backend::SqlDialect>::#dialect>,
__DieselInternal: diesel::backend::Backend,
{
fn walk_ast<'__b>(&'__b self, mut out: AstPass<'_, '__b, __DieselInternal>) -> QueryResult<()> {
<Self as QueryFragment<__DieselInternal, <__DieselInternal as diesel::backend::SqlDialect>::#dialect>>::walk_ast(self, out)
}
}
#specific_impl
}
}
BackendRestriction::Backends(_, ref backends) => {
let (impl_generics, _, _) = generics.split_for_impl();
let backends = backends.iter().map(|b| {
Self::generate_queryfragment_impl(
quote! {#b},
None,
&impl_generics,
ty_generics,
arg_name,
fn_name,
None,
)
});
parse_quote!(#(#backends)*)
}
}
}
fn generate_queryfragment_impl(
backend: proc_macro2::TokenStream,
backend_bound: Option<proc_macro2::TokenStream>,
impl_generics: &ImplGenerics<'_>,
ty_generics: &TypeGenerics<'_>,
arg_name: &[&syn::Ident],
fn_name: &syn::Ident,
dialect: Option<&syn::Path>,
) -> proc_macro2::TokenStream {
quote::quote! {
impl #impl_generics QueryFragment<#backend, #dialect>
for #fn_name #ty_generics
where
#backend_bound
#(#arg_name: QueryFragment<#backend>,)*
{
fn walk_ast<'__b>(&'__b self, mut out: AstPass<'_, '__b, #backend>) -> QueryResult<()>{
out.push_sql(<Self as FunctionFragment<#backend>>::FUNCTION_NAME);
out.push_sql("(");
self.walk_arguments(out.reborrow())?;
out.push_sql(")");
Ok(())
}
}
}
}
}
impl Parse for BackendRestriction {
fn parse(input: ParseStream) -> Result<Self> {
let name: syn::Ident = input.parse()?;
let name_str = name.to_string();
let content;
parenthesized!(content in input);
match &*name_str {
"backends" => Self::parse_backends(&content, name),
"dialect" => Self::parse_sql_dialect(&content, name),
"backend_bounds" => Self::parse_backend_bounds(&content, name),
_ => Err(syn::Error::new(
name.span(),
format!("unexpected option `{name_str}`"),
)),
}
}
}
#[derive(Debug, Clone)]
enum SqlFunctionAttribute {
Aggregate(Ident),
Window {
ident: Ident,
restrictions: BackendRestriction,
require_order: Option<bool>,
},
SqlName(Ident, LitStr),
Restriction(BackendRestriction),
Variadic(Ident, LitInt),
SkipReturnTypeHelper(Ident),
Other(Attribute),
}
impl MySpanned for SqlFunctionAttribute {
fn span(&self) -> proc_macro2::Span {
match self {
SqlFunctionAttribute::Restriction(BackendRestriction::Backends(ref ident, ..))
| SqlFunctionAttribute::Restriction(BackendRestriction::SqlDialect(ref ident, ..))
| SqlFunctionAttribute::Restriction(BackendRestriction::BackendBound(ref ident, ..))
| SqlFunctionAttribute::Aggregate(ref ident, ..)
| SqlFunctionAttribute::Window { ref ident, .. }
| SqlFunctionAttribute::Variadic(ref ident, ..)
| SqlFunctionAttribute::SkipReturnTypeHelper(ref ident)
| SqlFunctionAttribute::SqlName(ref ident, ..) => ident.span(),
SqlFunctionAttribute::Restriction(BackendRestriction::None) => {
unreachable!("We do not construct that")
}
SqlFunctionAttribute::Other(ref attribute) => attribute.span(),
}
}
}
fn parse_require_order(input: &syn::parse::ParseBuffer<'_>) -> Result<bool> {
let ident = input.parse::<Ident>()?;
if ident == "require_order" {
let _ = input.parse::<Token![=]>()?;
let value = input.parse::<LitBool>()?;
Ok(value.value)
} else {
Err(syn::Error::new(
ident.span(),
format!("Expected `require_order` but got `{ident}`"),
))
}
}
impl SqlFunctionAttribute {
fn parse_attr(
name: Ident,
input: &syn::parse::ParseBuffer<'_>,
attr: Attribute,
attribute_span: proc_macro2::Span,
) -> Result<Option<AttributeSpanWrapper<Self>>> {
if name == "cfg_attr" {
let ident = input.parse::<Ident>()?;
if ident != "feature" {
return Err(syn::Error::new(
ident.span(),
format!(
"only single feature `cfg_attr` attributes are supported. \
Got `{ident}` but expected `feature = \"foo\"`"
),
));
}
let _ = input.parse::<Token![=]>()?;
let feature = input.parse::<LitStr>()?;
let feature_value = feature.value();
let _ = input.parse::<Token![,]>()?;
let ignore = match feature_value.as_str() {
"postgres_backend" => !cfg!(feature = "postgres"),
"sqlite" => !cfg!(feature = "sqlite"),
"mysql_backend" => !cfg!(feature = "mysql"),
feature => {
return Err(syn::Error::new(
feature.span(),
format!(
"only `mysql_backend`, `postgres_backend` and `sqlite` \
are supported features, but got `{feature}`"
),
));
}
};
let name = input.parse::<Ident>()?;
let inner;
let _paren = parenthesized!(inner in input);
let ret = SqlFunctionAttribute::parse_attr(name, &inner, attr, attribute_span)?;
if ignore {
Ok(None)
} else {
Ok(ret)
}
} else {
let name_str = name.to_string();
let parsed_attr = match &*name_str {
"window" => {
let restrictions = if BackendRestriction::parse_from(&input.fork()).is_ok() {
BackendRestriction::parse_from(input).map(Ok).ok()
} else {
None
};
if input.fork().parse::<Token![,]>().is_ok() {
let _ = input.parse::<Token![,]>()?;
}
let require_order = if parse_require_order(&input.fork()).is_ok() {
Some(parse_require_order(input)?)
} else {
None
};
if input.fork().parse::<Token![,]>().is_ok() {
let _ = input.parse::<Token![,]>()?;
}
let restrictions =
restrictions.unwrap_or_else(|| BackendRestriction::parse_from(input))?;
Self::Window {
ident: name,
restrictions,
require_order,
}
}
"sql_name" => {
parse_eq(input, "sql_name = \"SUM\"").map(|v| Self::SqlName(name, v))?
}
"backends" => {
BackendRestriction::parse_backends(input, name).map(Self::Restriction)?
}
"dialect" => {
BackendRestriction::parse_sql_dialect(input, name).map(Self::Restriction)?
}
"backend_bounds" => {
BackendRestriction::parse_backend_bounds(input, name).map(Self::Restriction)?
}
"variadic" => Self::Variadic(name, input.parse()?),
_ => {
let _ = input.step(|cursor| {
let mut rest = *cursor;
while let Some((_, next)) = rest.token_tree() {
rest = next;
}
Ok(((), rest))
});
SqlFunctionAttribute::Other(attr)
}
};
Ok(Some(AttributeSpanWrapper {
ident_span: parsed_attr.span(),
item: parsed_attr,
attribute_span,
}))
}
}
}
#[derive(Default)]
pub(crate) struct DeclareSqlFunctionArgs {
pub(crate) generate_return_type_helpers: bool,
}
impl DeclareSqlFunctionArgs {
pub(crate) fn parse_from_macro_input(input: TokenStream) -> syn::Result<Self> {
if input.is_empty() {
return Ok(Self::default());
}
let input_span = input.span();
let parsed: syn::MetaNameValue = syn::parse2(input).map_err(|e| {
let span = e.span();
syn::Error::new(
span,
format!("{e}, the correct format is `generate_return_type_helpers = true/false`"),
)
})?;
match parsed {
syn::MetaNameValue {
path,
value:
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Bool(b),
..
}),
..
} if path.is_ident("generate_return_type_helpers") => Ok(Self {
generate_return_type_helpers: b.value,
}),
_ => Err(syn::Error::new(input_span, "Invalid config")),
}
}
}