How do I conditionally order by a column based on a dynamic parameter with Diesel?

那年仲夏 提交于 2021-02-10 14:18:12

问题


I'm trying to specify different columns for order_by depending on an external parameter.

This works, but is ugly:

#[macro_use]
extern crate diesel;

use crate::diesel::prelude::*;
use diesel::pg::PgConnection;

mod schema {
    table! {
        items (id) {
            id -> Int4,
            name -> Text,
        }
    }
}

#[derive(Queryable, Debug)]
pub struct Item {
    pub id: i32,
    pub name: String,
}

fn load_items(conn: PgConnection, sort_prop: String, sort_dir: String) -> Vec<Item> {
    use schema::items::dsl::*;

    let mut query = items.into_boxed();

    // ugly: duplicating condition by sort_dir and query.order_by() calls
    query = match sort_prop.as_str() {
        "name" => {
            if sort_dir == "asc" {
                query.order_by(name.asc())
            } else {
                query.order_by(name.desc())
            }
        }
        _ => {
            if sort_dir == "asc" {
                query.order_by(id.asc())
            } else {
                query.order_by(id.desc())
            }
        }
    };

    query.load::<Item>(&conn).expect("Failed to load items")
}

fn main() {}

My Cargo.toml has this:

[dependencies]
diesel = { version = "1.4.3", features = ["postgres"] }

I want to condition only by column, and not entire query, something like:

use schema::items::dsl::*;

let mut column = match sort_prop.as_str() {
    "name" => name,
    _ => id // error: match arms have incompatible types
}

column = if sort_dir == "asc" {
    column.asc()
} else {
    column.desc()
}

let results = items
    .order_by(column)
    .load::<Item>(connection)
    .expect("Failed to load items");

Is this possible? Is there any other way to refactor this?

I've read Querying a Diesel table with dynamic parameters, but it's basically about conditioning by the entire query, which is what I'm trying to avoid.

I've also read Creating Diesel.rs queries with a dynamic number of .and()'s, which is about conditioning by filter. This might be close to what I need with order_by, but it's hard for me to apply the BoxableExpression weirdness to my case because there's a lack of good examples for my exact case in the docs and a lack of RLS support for showing any schema::items::dsl::* types in my IDE, so I could scramble through myself.


回答1:


Given the following schema:

table! {
    flights (id) {
        id -> Int4,
        name -> Text,
        country -> Text,
        launch_date -> Timestamptz,
    }
}

And the following struct containing the sorting options:

pub struct SearchFlight {
    pub sort: Option<String>,
    pub sort_dir: Option<String>
}

It is possible to implement the following:

let mut query = flights.into_boxed();
match search.sort.as_ref().map(String::as_str)  {
    Some("name") => sort_by_column(query, flights_schema::name, search.sort_dir),
    Some("country") => sort_by_column(query, flights_schema::country, search.sort_dir),
    Some("launch_date") => sort_by_column(query, flights_schema::launch_date, search.sort_dir),
    _ => query
}
query.load_page::<Flight>(con)

where sort_by_column if the following function

fn sort_by_column<U: 'static>(mut query: BoxedQuery<'static, Pg>,
                     column: U,
                     sort_dir: Option<String>) -> BoxedQuery<'static, Pg>
    where U: ExpressionMethods + QueryFragment<Pg> + AppearsOnTable<flights_schema::table>{
    match sort_dir.as_ref().map(String::as_str) {
        Some("asc") => query.order_by(column.asc()),
        Some("desc") => query.order_by(column.desc()),
        _ => query
    }
}


来源:https://stackoverflow.com/questions/59291037/how-do-i-conditionally-order-by-a-column-based-on-a-dynamic-parameter-with-diese

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!