declare_sql_function

Attribute Macro declare_sql_function 

Source
#[declare_sql_function]
Expand description

Declare a sql function for use in your code.

Diesel only provides support for a very small number of SQL functions. This macro enables you to add additional functions from the SQL standard, as well as any custom functions your application might have.

The syntax for this attribute macro is designed to be applied to extern "SQL" blocks with function definitions. These function typically use types from diesel::sql_types as arguments and return types. You can use such definitions to declare bindings to unsupported SQL functions.

For each function in this extern block the macro will generate two items. A function with the name that you’ve given, and a module with a helper type representing the return type of your function. For example, this invocation:

#[declare_sql_function]
extern "SQL" {
    fn lower(x: Text) -> Text
}

will generate this code:

pub fn lower<X>(x: X) -> lower<X> {
    ...
}

pub type lower<X> = ...;

Most attributes given to this macro will be put on the generated function (including doc comments).

If the generate_return_type_helpers attribute is specified, an additional module named return_type_helpers will be generated, containing all return type helpers. For more information, refer to the Helper types generation section.

§Adding Doc Comments

use diesel::sql_types::Text;

#[declare_sql_function]
extern "SQL" {
    /// Represents the `canon_crate_name` SQL function, created in
    /// migration ....
    fn canon_crate_name(a: Text) -> Text;
}

let target_name = "diesel";
crates.filter(canon_crate_name(name).eq(canon_crate_name(target_name)));
// This will generate the following SQL
// SELECT * FROM crates WHERE canon_crate_name(crates.name) = canon_crate_name($1)

§Special Attributes

There are a handful of special attributes that Diesel will recognize. They are:

  • #[aggregate]
    • Indicates that this is an aggregate function, and that NonAggregate shouldn’t be implemented.
  • #[sql_name = "name"]
    • The SQL to be generated is different from the Rust name of the function. This can be used to represent functions which can take many argument types, or to capitalize function names.
  • #[variadic(argument_count)]
    • Indicates that this is a variadic function, where argument_count is a nonnegative integer representing the number of variadic arguments the function accepts.

Functions can also be generic. Take the definition of sum, for example:

use diesel::sql_types::Foldable;

#[declare_sql_function]
extern "SQL" {
    #[aggregate]
    #[sql_name = "SUM"]
    fn sum<ST: Foldable>(expr: ST) -> ST::Sum;
}

crates.select(sum(id));

§SQL Functions without Arguments

A common example is ordering a query using the RANDOM() sql function, which can be implemented using define_sql_function! like this:

#[declare_sql_function]
extern "SQL" {
    fn random() -> Text;
}

crates.order(random());

§Use with SQLite

On most backends, the implementation of the function is defined in a migration using CREATE FUNCTION. On SQLite, the function is implemented in Rust instead. You must call register_impl or register_nondeterministic_impl (in the generated function’s _internals module) with every connection before you can use the function.

These functions will only be generated if the sqlite feature is enabled, and the function is not generic. SQLite doesn’t support generic functions and variadic functions.

use diesel::sql_types::{Double, Integer};

#[declare_sql_function]
extern "SQL" {
    fn add_mul(x: Integer, y: Integer, z: Double) -> Double;
}

let connection = &mut SqliteConnection::establish(":memory:")?;

add_mul_utils::register_impl(connection, |x: i32, y: i32, z: f64| (x + y) as f64 * z)?;

let result = select(add_mul(1, 2, 1.5)).get_result::<f64>(connection)?;
assert_eq!(4.5, result);

§Panics

If an implementation of the custom function panics and unwinding is enabled, the panic is caught and the function returns to libsqlite with an error. It can’t propagate the panics due to the FFI boundary.

This is the same for custom aggregate functions.

§Custom Aggregate Functions

Custom aggregate functions can be created in SQLite by adding an #[aggregate] attribute inside define_sql_function. register_impl (in the generated function’s _utils module) needs to be called with a type implementing the SqliteAggregateFunction trait as a type parameter as shown in the examples below.

use diesel::sql_types::Integer;
use diesel::sqlite::SqliteAggregateFunction;

#[declare_sql_function]
extern "SQL" {
    #[aggregate]
    fn my_sum(x: Integer) -> Integer;
}

#[derive(Default)]
struct MySum { sum: i32 }

impl SqliteAggregateFunction<i32> for MySum {
    type Output = i32;

    fn step(&mut self, expr: i32) {
        self.sum += expr;
    }

    fn finalize(aggregator: Option<Self>) -> Self::Output {
        aggregator.map(|a| a.sum).unwrap_or_default()
    }
}

fn run() -> Result<(), Box<dyn (::std::error::Error)>> {
    let connection = &mut SqliteConnection::establish(":memory:")?;

    my_sum_utils::register_impl::<MySum, _>(connection)?;

    let total_score = players.select(my_sum(score))
        .get_result::<i32>(connection)?;

    println!("The total score of all the players is: {}", total_score);

    Ok(())
}

With multiple function arguments, the arguments are passed as a tuple to SqliteAggregateFunction

use diesel::sql_types::{Float, Nullable};
use diesel::sqlite::SqliteAggregateFunction;

#[declare_sql_function]
extern "SQL" {
    #[aggregate]
    fn range_max(x0: Float, x1: Float) -> Nullable<Float>;
}

#[derive(Default)]
struct RangeMax<T> { max_value: Option<T> }

impl<T: Default + PartialOrd + Copy + Clone> SqliteAggregateFunction<(T, T)> for RangeMax<T> {
    type Output = Option<T>;

    fn step(&mut self, (x0, x1): (T, T)) {
        // Compare self.max_value to x0 and x1
    }

    fn finalize(aggregator: Option<Self>) -> Self::Output {
        aggregator?.max_value
    }
}

fn run() -> Result<(), Box<dyn (::std::error::Error)>> {
    let connection = &mut SqliteConnection::establish(":memory:")?;

    range_max_utils::register_impl::<RangeMax<f32>, _, _>(connection)?;

    let result = student_avgs.select(range_max(s1_avg, s2_avg))
        .get_result::<Option<f32>>(connection)?;

    if let Some(max_semester_avg) = result {
        println!("The largest semester average is: {}", max_semester_avg);
    }

    Ok(())
}

§Variadic functions

Since Rust does not support variadic functions, the SQL variadic functions are handled differently. For example, consider the variadic function json_array. To add support for it, you can use the #[variadic] attribute:


#[declare_sql_function]
extern "SQL" {
    #[variadic(1)]
    fn json_array<V: SqlType + SingleValue>(value: V) -> Json;
}

This will generate multiple implementations, one for each possible argument count (up to a predefined limit). For instance, it will generate functions like json_array_0, json_array_1, and so on, which are equivalent to:


#[declare_sql_function]
extern "SQL" {
    #[sql_name = "json_array"]
    fn json_array_0() -> Json;

    #[sql_name = "json_array"]
    fn json_array_1<V1: SqlType + SingleValue>(value_1: V1) -> Json;

    #[sql_name = "json_array"]
    fn json_array_2<V1: SqlType + SingleValue, V2: SqlType + SingleValue>(
        value_1: V1,
        value_2: V2,
    ) -> Json;

    // ...
}

The argument to the variadic attribute specifies the number of trailing arguments to repeat. For example, if you have a variadic function foo(a: A, b: B, c: C) and want b: B and c: C to repeat, you would write:

#[declare_sql_function]
extern "SQL" {
    #[variadic(2)]
    fn foo<A, B, C>(a: A, b: B, c: C) -> Text;
}

Which will be equivalent to

#[declare_sql_function]
extern "SQL" {
    #[sql_name = "foo"]
    fn foo_0<A>(a: A) -> Text;

    #[sql_name = "foo"]
    fn foo_1<A, B1, C1>(a: A, b_1: B1, c_1: C1) -> Text;

    #[sql_name = "foo"]
    fn foo_2<A, B1, C1, B2, C2>(a: A, b_1: B1, c_1: C1, b_2: B2, c_2: C2) -> Text;

    ...
}

§Controlling the generation of variadic function variants

By default, only variants with 0, 1, and 2 repetitions of variadic arguments are generated. To generate more variants, set the DIESEL_VARIADIC_FUNCTION_ARGS environment variable to the desired number of variants.

For a greater convenience this environment variable can also be set in a .cargo/config.toml file as described in the cargo documentation.

§Helper types generation

When the generate_return_type_helpers attribute is specified, for each function defined inside an extern "SQL" block, a return type alias with the same name as the function is created and placed in the return_type_helpers module:

#[declare_sql_function(generate_return_type_helpers = true)]
extern "SQL" {
    fn f<V: SqlType + SingleValue>(arg: V);
}

type return_type_helper_for_f<V> = return_type_helpers::f<V>;

If you want to skip generating a type alias for a specific function, you can use the #[skip_return_type_helper] attribute, like this:

#[declare_sql_function(generate_return_type_helpers = true)]
extern "SQL" {
    #[skip_return_type_helper]
    fn f();
}

§Expanded Code

Expanded Code
§Input
#[diesel::declare_sql_function]
extern "SQL" {
    fn lower(input: Text) -> Text;
}
§Expanded Code
Expanded code might use diesel internal API's and is only shown for educational purpose

The macro expands the input to the following Rust code:

#[allow(non_camel_case_types)]
pub fn lower<input>(input: input) -> lower<input>
where
    input: diesel::expression::AsExpression<Text>,
{
    lower_utils::lower {
        input: input.as_expression(),
    }
}
#[allow(non_camel_case_types, non_snake_case)]
///The return type of [`lower()`](super::fn_name)
pub type lower<input> = lower_utils::lower<
    <input as diesel::expression::AsExpression<Text>>::Expression,
>;
#[doc(hidden)]
#[allow(non_camel_case_types, non_snake_case, unused_imports)]
pub(crate) mod lower_utils {
    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)]
    #[derive(diesel::sql_types::DieselNumericOps)]
    pub struct lower<input> {
        pub(super) input: input,
    }
    ///The return type of [`lower()`](super::fn_name)
    pub type HelperType<input> = lower<<input as AsExpression<Text>>::Expression>;
    impl<input> Expression for lower<input>
    where
        (input): Expression,
    {
        type SqlType = Text;
    }
    impl<input, __DieselInternal> SelectableExpression<__DieselInternal> for lower<input>
    where
        input: SelectableExpression<__DieselInternal>,
        Self: AppearsOnTable<__DieselInternal>,
    {}
    impl<input, __DieselInternal> AppearsOnTable<__DieselInternal> for lower<input>
    where
        input: AppearsOnTable<__DieselInternal>,
        Self: Expression,
    {}
    impl<input, __DieselInternal> FunctionFragment<__DieselInternal> for lower<input>
    where
        __DieselInternal: diesel::backend::Backend,
        input: QueryFragment<__DieselInternal>,
    {
        const FUNCTION_NAME: &'static str = "lower";
        #[allow(unused_assignments)]
        fn walk_arguments<'__b>(
            &'__b self,
            mut out: AstPass<'_, '__b, __DieselInternal>,
        ) -> QueryResult<()> {
            let mut needs_comma = false;
            if !self.input.is_noop(out.backend())? {
                if needs_comma {
                    out.push_sql(", ");
                }
                self.input.walk_ast(out.reborrow())?;
                needs_comma = true;
            }
            Ok(())
        }
    }
    impl<input, __DieselInternal> QueryFragment<__DieselInternal> for lower<input>
    where
        __DieselInternal: diesel::backend::Backend,
        input: QueryFragment<__DieselInternal>,
    {
        fn walk_ast<'__b>(
            &'__b self,
            mut out: AstPass<'_, '__b, __DieselInternal>,
        ) -> QueryResult<()> {
            out.push_sql(<Self as FunctionFragment<__DieselInternal>>::FUNCTION_NAME);
            out.push_sql("(");
            self.walk_arguments(out.reborrow())?;
            out.push_sql(")");
            Ok(())
        }
    }
    #[derive(ValidGrouping)]
    pub struct __Derived<input>(input);
    impl<input, __DieselInternal> ValidGrouping<__DieselInternal> for lower<input>
    where
        __Derived<input>: ValidGrouping<__DieselInternal>,
    {
        type IsAggregate = <__Derived<
            input,
        > as ValidGrouping<__DieselInternal>>::IsAggregate;
    }
    use diesel::sqlite::{Sqlite, SqliteConnection};
    use diesel::serialize::ToSql;
    use diesel::deserialize::{FromSqlRow, StaticallySizedRow};
    #[allow(dead_code)]
    /// Registers an implementation for this function on the given connection
    ///
    /// This function must be called for every `SqliteConnection` before
    /// this SQL function can be used on SQLite. The implementation must be
    /// deterministic (returns the same result given the same arguments). If
    /// the function is nondeterministic, call
    /// `register_nondeterministic_impl` instead.
    pub fn register_impl<F, Ret, input>(
        conn: &mut SqliteConnection,
        f: F,
    ) -> QueryResult<()>
    where
        F: Fn(input) -> Ret + std::panic::UnwindSafe + Send + 'static,
        (input,): FromSqlRow<(Text,), Sqlite> + StaticallySizedRow<(Text,), Sqlite>,
        Ret: ToSql<Text, Sqlite>,
    {
        conn.register_sql_function::<
                (Text,),
                Text,
                _,
                _,
                _,
            >("lower", true, move |(input,)| f(input))
    }
    #[allow(dead_code)]
    /// Registers an implementation for this function on the given connection
    ///
    /// This function must be called for every `SqliteConnection` before
    /// this SQL function can be used on SQLite.
    /// `register_nondeterministic_impl` should only be used if your
    /// function can return different results with the same arguments (e.g.
    /// `random`). If your function is deterministic, you should call
    /// `register_impl` instead.
    pub fn register_nondeterministic_impl<F, Ret, input>(
        conn: &mut SqliteConnection,
        mut f: F,
    ) -> QueryResult<()>
    where
        F: FnMut(input) -> Ret + std::panic::UnwindSafe + Send + 'static,
        (input,): FromSqlRow<(Text,), Sqlite> + StaticallySizedRow<(Text,), Sqlite>,
        Ret: ToSql<Text, Sqlite>,
    {
        conn.register_sql_function::<
                (Text,),
                Text,
                _,
                _,
                _,
            >("lower", false, move |(input,)| f(input))
    }
}