Attribute Macro diesel_derives::auto_type

source ·
#[auto_type]
Expand description

Automatically annotates return type of a query fragment function

This may be useful when factoring out common query fragments into functions. If not using this, it would typically involve explicitly writing the full type of the query fragment function, which depending on the length of said query fragment can be quite difficult (especially to maintain) and verbose.

§Example

use diesel::dsl;

#[dsl::auto_type]
fn user_has_post() -> _ {
    dsl::exists(posts::table.filter(posts::user_id.eq(users::id)))
}

let users_with_posts: Vec<String> = users::table
    .filter(user_has_post())
    .select(users::name)
    .load(conn)?;

assert_eq!(
    &["Sean", "Tess"] as &[_],
    users_with_posts
        .iter()
        .map(|s| s.as_str())
        .collect::<Vec<_>>()
);

§Limitations

While this attribute tries to support as much of diesels built-in DSL as possible it’s unfortunately not possible to support everything. Notable unsupported types are:

  • Update statements
  • Insert from select statements
  • Query constructed by diesel::sql_query
  • Expressions using diesel::dsl::sql

For these cases a manual type annotation is required. See the “Annotating Types” section below for details.

§Advanced usage

By default, the macro will:

  • Generate a type alias for the return type of the function, named the exact same way as the function itself.
  • Assume that functions, unless otherwise annotated, have a type alias for their return type available at the same path as the function itself (including case). (e.g. for the dsl::not(x) call, it expects that there is a dsl::not<X> type alias available)
  • Assume that methods, unless otherwise annotated, have a type alias available as diesel::dsl::PascalCaseOfMethodName (e.g. for the x.and(y) call, it expects that there is a diesel::dsl::And<X, Y> type alias available)

The defaults can be changed by passing the following attributes to the macro:

  • #[auto_type(no_type_alias)] to disable the generation of the type alias.
  • #[auto_type(dsl_path = "path::to::dsl")] to change the path where the macro will look for type aliases for methods. This is required if you mix your own custom query dsl extensions with diesel types. In that case, you may use this argument to reference a module defined like so:
    mod dsl {
        /// export all of diesel dsl
        pub use diesel::dsl::*;
      
        /// Export your extension types here
        pub use crate::your_extension::dsl::YourType;
     }
  • #[auto_type(method_type_case = "snake_case")] to change the case of the method type alias.
  • #[auto_type(function_type_case = "snake_case")] to change the case of the function type alias (if you don’t want the exact same path but want to change the case of the last element of the path).

The dsl_path attribute in particular may be used to declare an intermediate module where you would define the few additional needed type aliases that can’t be inferred automatically.

§Annotating types

Sometimes the macro can’t infer the type of a particular sub-expression. In that case, you can annotate the type of the sub-expression:

use diesel::dsl;

// This will generate a `user_has_post_with_id_greater_than` type alias
#[dsl::auto_type]
fn user_has_post_with_id_greater_than(id_greater_than: i32) -> _ {
    dsl::exists(
        posts::table
            .filter(posts::user_id.eq(users::id))
            .filter(posts::id.gt(id_greater_than)),
    )
}

#[dsl::auto_type]
fn users_with_posts_with_id_greater_than(id_greater_than: i32) -> _ {
    // If we didn't specify the type for this query fragment, the macro would infer it as
    // `user_has_post_with_id_greater_than<i32>`, which would be incorrect because there is
    // no generic parameter.
    let filter: user_has_post_with_id_greater_than =
        user_has_post_with_id_greater_than(id_greater_than);
    // The macro inferring that it has to pass generic parameters is still the convention
    // because it's the most general case, as well as the common case within Diesel itself,
    // and because annotating this way is reasonably simple, while the other way around
    // would be hard.

    users::table.filter(filter).select(users::name)
}

let users_with_posts: Vec<String> = users_with_posts_with_id_greater_than(2).load(conn)?;

assert_eq!(
    &["Tess"] as &[_],
    users_with_posts
        .iter()
        .map(|s| s.as_str())
        .collect::<Vec<_>>()
);