use prax_query::dialect::Postgres;
use prax_query::filter::{Filter, FilterValue};
use prax_query::operations::FindManyOperation;
use prax_query::projection::ScalarProjection;
use prax_query::traits::{BoxFuture, Model, QueryEngine};
struct Users;
impl Model for Users {
const MODEL_NAME: &'static str = "Users";
const TABLE_NAME: &'static str = "users";
const PRIMARY_KEY: &'static [&'static str] = &["id"];
const COLUMNS: &'static [&'static str] = &["id", "name"];
}
impl prax_query::row::FromRow for Users {
fn from_row(_row: &impl prax_query::row::RowRef) -> Result<Self, prax_query::row::RowError> {
Ok(Users)
}
}
#[derive(Clone)]
struct NoopEngine;
impl QueryEngine for NoopEngine {
fn query_many<T: Model + prax_query::row::FromRow + Send + 'static>(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<Vec<T>>> {
Box::pin(async { Ok(Vec::new()) })
}
fn query_one<T: Model + prax_query::row::FromRow + Send + 'static>(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<T>> {
Box::pin(async { Err(prax_query::error::QueryError::not_found("t")) })
}
fn query_optional<T: Model + prax_query::row::FromRow + Send + 'static>(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<Option<T>>> {
Box::pin(async { Ok(None) })
}
fn execute_insert<T: Model + prax_query::row::FromRow + Send + 'static>(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<T>> {
Box::pin(async { Err(prax_query::error::QueryError::not_found("t")) })
}
fn execute_update<T: Model + prax_query::row::FromRow + Send + 'static>(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<Vec<T>>> {
Box::pin(async { Ok(Vec::new()) })
}
fn execute_delete(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<u64>> {
Box::pin(async { Ok(0) })
}
fn execute_raw(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<u64>> {
Box::pin(async { Ok(0) })
}
fn count(
&self,
_: &str,
_: Vec<FilterValue>,
) -> BoxFuture<'_, prax_query::error::QueryResult<u64>> {
Box::pin(async { Ok(0) })
}
}
fn build_test_select_postgres(projections: Vec<ScalarProjection>) -> String {
let mut op = FindManyOperation::<NoopEngine, Users>::new(NoopEngine);
op.extra_projections = projections;
let (sql, _params) = op.build_sql(&Postgres);
sql
}
fn build_test_select_with_where(
proj: ScalarProjection,
where_val: FilterValue,
) -> (String, Vec<FilterValue>) {
let mut op = FindManyOperation::<NoopEngine, Users>::new(NoopEngine)
.r#where(Filter::Equals("id".into(), where_val));
op.extra_projections = vec![proj];
op.build_sql(&Postgres)
}
fn build_test_select_postgres_with_params(
projections: Vec<ScalarProjection>,
) -> (String, Vec<FilterValue>) {
let mut op = FindManyOperation::<NoopEngine, Users>::new(NoopEngine);
op.extra_projections = projections;
op.build_sql(&Postgres)
}
#[test]
fn single_projection_appends_alias_to_select() {
let proj = ScalarProjection::new(
"SELECT COUNT(*) FROM \"posts\" WHERE \"posts\".\"author_id\" = \"users\".\"id\"",
vec![],
"_count_posts",
);
let sql = build_test_select_postgres(vec![proj]);
assert!(
sql.contains(
", (SELECT COUNT(*) FROM \"posts\" WHERE \"posts\".\"author_id\" = \"users\".\"id\") AS \"_count_posts\""
),
"got: {sql}"
);
}
#[test]
fn placeholder_renumbers_against_running_counter() {
let proj = ScalarProjection::new(
"SELECT COUNT(*) FROM \"posts\" WHERE \"posts\".\"views\" > {0}",
vec![FilterValue::Int(100)],
"_high_view_count",
);
let (sql, params) = build_test_select_with_where(proj, FilterValue::Int(7));
assert!(sql.contains("> $1)"), "projection placeholder wrong: {sql}");
assert!(
sql.contains("WHERE") && sql.contains("$2"),
"where placeholder wrong: {sql}"
);
assert_eq!(params, vec![FilterValue::Int(100), FilterValue::Int(7)]);
}
#[test]
fn multiple_projections_emit_in_order_with_correct_placeholders() {
let p1 = ScalarProjection::new(
"SELECT COUNT(*) FROM \"posts\" WHERE \"posts\".\"views\" > {0}",
vec![FilterValue::Int(50)],
"_views_above_50",
);
let p2 = ScalarProjection::new(
"SELECT COUNT(*) FROM \"comments\" WHERE \"comments\".\"author_id\" = \"users\".\"id\" AND \"comments\".\"score\" > {0}",
vec![FilterValue::Int(10)],
"_high_score_comment_count",
);
let (sql, params) = build_test_select_postgres_with_params(vec![p1, p2]);
assert!(
sql.contains("> $1)"),
"first projection placeholder wrong: {sql}"
);
assert!(
sql.contains("> $2)"),
"second projection placeholder wrong: {sql}"
);
assert!(
sql.find("_views_above_50").unwrap() < sql.find("_high_score_comment_count").unwrap(),
"projections out of order: {sql}"
);
assert_eq!(params, vec![FilterValue::Int(50), FilterValue::Int(10)]);
}
#[test]
fn no_extra_projections_emits_plain_select() {
let op = FindManyOperation::<NoopEngine, Users>::new(NoopEngine);
let (sql, params) = op.build_sql(&Postgres);
assert_eq!(sql, "SELECT * FROM users");
assert!(params.is_empty());
}