sift-query-sqlx 0.1.0

PostgreSQL renderer and sqlx executor for SiftQuery
Documentation
extern crate sift_query_core as sift_query;
use sift_query::{ParamParser, QueryPlan, QueryPolicy, QueryTarget, RawParams, ServerScope};
use sift_query_derive::SearchEntity;
use sift_query_sqlx::PgRenderer;

#[derive(Debug, SearchEntity)]
#[allow(dead_code)]
#[search_entity(from = "app_user u", default_order = "id:asc", stable_order = "id:asc")]
struct User {
    #[search_field(column = "u.id", sortable)]
    id: i64,
    #[search_field(column = "u.name", sortable)]
    name: String,
}

#[derive(Debug, SearchEntity)]
#[allow(dead_code)]
#[search_entity(
    from = "student_score s",
    default_order = "courseId:asc",
    stable_order = "courseId:asc"
)]
struct StudentCourse {
    #[search_field(
        name = "courseId",
        column = "s.course_id",
        kind = GroupKey,
        sortable
    )]
    course_id: i64,
    #[search_field(
        name = "avgScore",
        column = "avg(s.score::double precision)",
        kind = Aggregate,
        operators = [Eq, Gt]
    )]
    avg_score: f64,
}

fn plan<E: sift_query::SearchEntity>(query: &str, target: QueryTarget) -> QueryPlan<E> {
    let raw = RawParams::parse(query).unwrap();
    let policy = QueryPolicy::default();
    let query = ParamParser::new(&policy).unwrap().parse::<E>(&raw).unwrap();
    QueryPlan::new(&query, &ServerScope::unrestricted(), target).unwrap()
}

#[test]
fn preserves_multi_field_ordering_semantics() {
    let rendered = PgRenderer
        .render_list(&plan::<User>(
            "orderBy=id:asc,name:desc",
            QueryTarget::TypedPage,
        ))
        .unwrap();
    assert!(
        rendered
            .sql
            .contains("ORDER BY u.id ASC NULLS LAST, u.name DESC NULLS FIRST")
    );
}

#[test]
fn routes_group_keys_to_where_and_aggregates_to_having() {
    let rendered = PgRenderer
        .render_list(&plan::<StudentCourse>(
            "courseId=5&avgScore=80&avgScore-op=gt",
            QueryTarget::TypedPage,
        ))
        .unwrap();
    assert!(rendered.sql.contains("WHERE s.course_id = $1"));
    assert!(rendered.sql.contains("GROUP BY s.course_id"));
    assert!(
        rendered
            .sql
            .contains("HAVING avg(s.score::double precision) > $2")
    );
}

#[test]
fn applies_local_absorption_without_expanding_the_expression() {
    let rendered = PgRenderer
        .render_list(&plan::<User>(
            "A.id=1&B.name=Alice&gexpr=A%7C%28A%26B%29",
            QueryTarget::TypedPage,
        ))
        .unwrap();
    assert!(rendered.sql.contains("WHERE u.id = $1"));
    assert!(!rendered.sql.contains("u.name ="));
    assert_eq!(rendered.binds.len(), 3);
}