Simplify species list filtering with IS NULL pattern
Replaced dynamic WHERE clause builder + macro with static SQL using ($N::type IS NULL OR column = $N) pattern. Always binds all 9 filter params + limit/offset. Supports: family, search, plant_layer, nitrogen_fixer, dynamic_accumulator, drought_tolerance, native_status, min_nectar, min_bees.
This commit is contained in:
@@ -10,6 +10,13 @@ pub struct SpeciesListParams {
|
||||
pub per_page: Option<i64>,
|
||||
pub search: Option<String>,
|
||||
pub family: Option<String>,
|
||||
pub plant_layer: Option<String>,
|
||||
pub nitrogen_fixer: Option<bool>,
|
||||
pub dynamic_accumulator: Option<bool>,
|
||||
pub drought_tolerance: Option<String>,
|
||||
pub native_status: Option<String>,
|
||||
pub min_nectar: Option<i16>,
|
||||
pub min_bees: Option<i32>,
|
||||
}
|
||||
|
||||
impl SpeciesListParams {
|
||||
@@ -21,74 +28,57 @@ pub async fn list(pool: &PgPool, params: &SpeciesListParams) -> Result<Paginated
|
||||
let limit = params.limit();
|
||||
let offset = params.offset();
|
||||
|
||||
let (rows, total) = match (¶ms.family, ¶ms.search) {
|
||||
(Some(family_slug), Some(search)) => {
|
||||
let tsquery = search.split_whitespace().collect::<Vec<_>>().join(" & ");
|
||||
let rows = sqlx::query_as::<_, Species>(
|
||||
"SELECT s.* FROM species s JOIN families f ON s.family_id = f.id
|
||||
WHERE f.slug = $1
|
||||
AND to_tsvector('english', coalesce(s.name_scientific,'') || ' ' || coalesce(s.name_en,'') || ' ' || coalesce(s.name_de,'') || ' ' || coalesce(s.description,''))
|
||||
@@ to_tsquery('english', $2)
|
||||
ORDER BY s.name_scientific LIMIT $3 OFFSET $4"
|
||||
)
|
||||
.bind(family_slug).bind(&tsquery).bind(limit).bind(offset)
|
||||
.fetch_all(pool).await?;
|
||||
// Always bind all 11 params ($1-$11) using IS NULL pattern for inactive filters.
|
||||
// $1=family, $2=search, $3=plant_layer, $4=nitrogen_fixer, $5=dynamic_accumulator,
|
||||
// $6=drought_tolerance, $7=native_status, $8=min_nectar, $9=min_bees, $10=limit, $11=offset
|
||||
let tsquery = params.search.as_ref().map(|s| s.split_whitespace().collect::<Vec<_>>().join(" & "));
|
||||
let family = params.family.clone();
|
||||
let search = tsquery.clone();
|
||||
let layer = params.plant_layer.clone();
|
||||
let nfixer = params.nitrogen_fixer;
|
||||
let dynacc = params.dynamic_accumulator;
|
||||
let drought = params.drought_tolerance.clone();
|
||||
let native = params.native_status.clone();
|
||||
let nectar = params.min_nectar;
|
||||
let bees = params.min_bees;
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as(
|
||||
"SELECT COUNT(*) FROM species s JOIN families f ON s.family_id = f.id
|
||||
WHERE f.slug = $1
|
||||
AND to_tsvector('english', coalesce(s.name_scientific,'') || ' ' || coalesce(s.name_en,'') || ' ' || coalesce(s.name_de,'') || ' ' || coalesce(s.description,''))
|
||||
@@ to_tsquery('english', $2)"
|
||||
)
|
||||
.bind(family_slug).bind(&tsquery)
|
||||
.fetch_one(pool).await?;
|
||||
(rows, count)
|
||||
}
|
||||
(Some(family_slug), None) => {
|
||||
let rows = sqlx::query_as::<_, Species>(
|
||||
"SELECT s.* FROM species s JOIN families f ON s.family_id = f.id
|
||||
WHERE f.slug = $1 ORDER BY s.name_scientific LIMIT $2 OFFSET $3"
|
||||
)
|
||||
.bind(family_slug).bind(limit).bind(offset)
|
||||
.fetch_all(pool).await?;
|
||||
let data_sql = "SELECT s.* FROM species s LEFT JOIN families f ON s.family_id = f.id WHERE \
|
||||
($1::text IS NULL OR f.slug = $1) AND \
|
||||
($2::text IS NULL OR to_tsvector('english', coalesce(s.name_scientific,'') || ' ' || coalesce(s.name_en,'') || ' ' || coalesce(s.name_de,'') || ' ' || coalesce(s.description,'')) @@ to_tsquery('english', $2)) AND \
|
||||
($3::text IS NULL OR s.plant_layer = $3) AND \
|
||||
($4::bool IS NULL OR s.nitrogen_fixer = $4) AND \
|
||||
($5::bool IS NULL OR s.dynamic_accumulator = $5) AND \
|
||||
($6::text IS NULL OR s.drought_tolerance = $6) AND \
|
||||
($7::text IS NULL OR s.native_status = $7) AND \
|
||||
($8::smallint IS NULL OR s.nectar_value >= $8) AND \
|
||||
($9::integer IS NULL OR s.wild_bee_count >= $9) \
|
||||
ORDER BY s.name_scientific LIMIT $10 OFFSET $11";
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as(
|
||||
"SELECT COUNT(*) FROM species s JOIN families f ON s.family_id = f.id WHERE f.slug = $1"
|
||||
)
|
||||
.bind(family_slug).fetch_one(pool).await?;
|
||||
(rows, count)
|
||||
}
|
||||
(None, Some(search)) => {
|
||||
let tsquery = search.split_whitespace().collect::<Vec<_>>().join(" & ");
|
||||
let rows = sqlx::query_as::<_, Species>(
|
||||
"SELECT * FROM species
|
||||
WHERE to_tsvector('english', coalesce(name_scientific,'') || ' ' || coalesce(name_en,'') || ' ' || coalesce(name_de,'') || ' ' || coalesce(description,''))
|
||||
@@ to_tsquery('english', $1)
|
||||
ORDER BY name_scientific LIMIT $2 OFFSET $3"
|
||||
)
|
||||
.bind(&tsquery).bind(limit).bind(offset)
|
||||
.fetch_all(pool).await?;
|
||||
let count_sql = "SELECT COUNT(*) FROM species s LEFT JOIN families f ON s.family_id = f.id WHERE \
|
||||
($1::text IS NULL OR f.slug = $1) AND \
|
||||
($2::text IS NULL OR to_tsvector('english', coalesce(s.name_scientific,'') || ' ' || coalesce(s.name_en,'') || ' ' || coalesce(s.name_de,'') || ' ' || coalesce(s.description,'')) @@ to_tsquery('english', $2)) AND \
|
||||
($3::text IS NULL OR s.plant_layer = $3) AND \
|
||||
($4::bool IS NULL OR s.nitrogen_fixer = $4) AND \
|
||||
($5::bool IS NULL OR s.dynamic_accumulator = $5) AND \
|
||||
($6::text IS NULL OR s.drought_tolerance = $6) AND \
|
||||
($7::text IS NULL OR s.native_status = $7) AND \
|
||||
($8::smallint IS NULL OR s.nectar_value >= $8) AND \
|
||||
($9::integer IS NULL OR s.wild_bee_count >= $9)";
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as(
|
||||
"SELECT COUNT(*) FROM species
|
||||
WHERE to_tsvector('english', coalesce(name_scientific,'') || ' ' || coalesce(name_en,'') || ' ' || coalesce(name_de,'') || ' ' || coalesce(description,''))
|
||||
@@ to_tsquery('english', $1)"
|
||||
)
|
||||
.bind(&tsquery).fetch_one(pool).await?;
|
||||
(rows, count)
|
||||
}
|
||||
(None, None) => {
|
||||
let rows = sqlx::query_as::<_, Species>(
|
||||
"SELECT * FROM species ORDER BY name_scientific LIMIT $1 OFFSET $2"
|
||||
)
|
||||
.bind(limit).bind(offset)
|
||||
.fetch_all(pool).await?;
|
||||
let rows: Vec<Species> = sqlx::query_as(data_sql)
|
||||
.bind(family.as_deref()).bind(search.as_deref())
|
||||
.bind(layer.as_deref()).bind(nfixer).bind(dynacc)
|
||||
.bind(drought.as_deref()).bind(native.as_deref())
|
||||
.bind(nectar).bind(bees)
|
||||
.bind(limit).bind(offset)
|
||||
.fetch_all(pool).await?;
|
||||
|
||||
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM species")
|
||||
.fetch_one(pool).await?;
|
||||
(rows, count)
|
||||
}
|
||||
};
|
||||
let (total,): (i64,) = sqlx::query_as(count_sql)
|
||||
.bind(family.as_deref()).bind(search.as_deref())
|
||||
.bind(layer.as_deref()).bind(nfixer).bind(dynacc)
|
||||
.bind(drought.as_deref()).bind(native.as_deref())
|
||||
.bind(nectar).bind(bees)
|
||||
.fetch_one(pool).await?;
|
||||
|
||||
Ok(PaginatedResponse {
|
||||
data: rows,
|
||||
|
||||
Reference in New Issue
Block a user