one query for pois

This commit is contained in:
Shautvast 2026-04-03 17:47:31 +02:00
parent 08ea09a347
commit 2e326b8cd7
2 changed files with 56 additions and 40 deletions

View file

@ -16,10 +16,21 @@ pub struct PoiRow {
pub wheelchair: Option<String>, pub wheelchair: Option<String>,
} }
/// Count row for total matching POIs. /// POI row with window-function total (replaces separate COUNT query).
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
pub struct CountRow { pub struct PoiRowWithTotal {
pub count: Option<i64>, pub osm_id: i64,
pub osm_type: String,
pub name: String,
pub category: String,
pub geometry: serde_json::Value,
pub address: Option<serde_json::Value>,
pub tags: Option<serde_json::Value>,
pub opening_hours: Option<String>,
pub phone: Option<String>,
pub website: Option<String>,
pub wheelchair: Option<String>,
pub total: Option<i64>,
} }
/// Address sub-object in the API response. /// Address sub-object in the API response.
@ -86,6 +97,39 @@ pub struct PaginationMetadata {
} }
/// Converts a database row into a GeoJSON Feature. /// Converts a database row into a GeoJSON Feature.
impl PoiRowWithTotal {
pub fn into_feature(self) -> PoiFeature {
let address: Option<PoiAddress> = self
.address
.as_ref()
.and_then(|v| serde_json::from_value(v.clone()).ok());
let opening_hours_parsed = self.opening_hours.as_ref().map(|_oh| OpeningHoursParsed {
is_open: true,
today: None,
next_change: None,
});
PoiFeature {
r#type: "Feature".into(),
geometry: self.geometry,
properties: PoiProperties {
osm_id: self.osm_id,
osm_type: self.osm_type,
name: self.name,
category: self.category,
address,
opening_hours: self.opening_hours,
opening_hours_parsed,
phone: self.phone,
website: self.website,
wheelchair: self.wheelchair,
tags: self.tags,
},
}
}
}
impl PoiRow { impl PoiRow {
pub fn into_feature(self) -> PoiFeature { pub fn into_feature(self) -> PoiFeature {
let address: Option<PoiAddress> = self let address: Option<PoiAddress> = self

View file

@ -72,43 +72,13 @@ pub async fn list_pois(
None None
}; };
// Build and execute the count query // Single query: window function computes total within the same spatial scan.
let total = if let Some(ref cats) = categories { let rows: Vec<PoiRowWithTotal> = if let Some(ref cats) = categories {
sqlx::query_as::<_, CountRow>( sqlx::query_as::<_, PoiRowWithTotal>(
"SELECT COUNT(*) as count FROM pois \
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
AND category = ANY($5)",
)
.bind(min_lon)
.bind(min_lat)
.bind(max_lon)
.bind(max_lat)
.bind(cats)
.fetch_one(pool.get_ref())
.await?
.count
.unwrap_or(0)
} else {
sqlx::query_as::<_, CountRow>(
"SELECT COUNT(*) as count FROM pois \
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326)",
)
.bind(min_lon)
.bind(min_lat)
.bind(max_lon)
.bind(max_lat)
.fetch_one(pool.get_ref())
.await?
.count
.unwrap_or(0)
};
// Build and execute the data query
let rows: Vec<PoiRow> = if let Some(ref cats) = categories {
sqlx::query_as::<_, PoiRow>(
"SELECT osm_id, osm_type, name, category, \ "SELECT osm_id, osm_type, name, category, \
ST_AsGeoJSON(geometry)::json AS geometry, \ ST_AsGeoJSON(geometry)::json AS geometry, \
address, tags, opening_hours, phone, website, wheelchair \ address, tags, opening_hours, phone, website, wheelchair, \
COUNT(*) OVER() AS total \
FROM pois \ FROM pois \
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \ WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
AND category = ANY($5) \ AND category = ANY($5) \
@ -125,10 +95,11 @@ pub async fn list_pois(
.fetch_all(pool.get_ref()) .fetch_all(pool.get_ref())
.await? .await?
} else { } else {
sqlx::query_as::<_, PoiRow>( sqlx::query_as::<_, PoiRowWithTotal>(
"SELECT osm_id, osm_type, name, category, \ "SELECT osm_id, osm_type, name, category, \
ST_AsGeoJSON(geometry)::json AS geometry, \ ST_AsGeoJSON(geometry)::json AS geometry, \
address, tags, opening_hours, phone, website, wheelchair \ address, tags, opening_hours, phone, website, wheelchair, \
COUNT(*) OVER() AS total \
FROM pois \ FROM pois \
WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \ WHERE geometry && ST_MakeEnvelope($1, $2, $3, $4, 4326) \
ORDER BY name \ ORDER BY name \
@ -144,6 +115,7 @@ pub async fn list_pois(
.await? .await?
}; };
let total = rows.first().and_then(|r| r.total).unwrap_or(0);
let features: Vec<PoiFeature> = rows.into_iter().map(|r| r.into_feature()).collect(); let features: Vec<PoiFeature> = rows.into_iter().map(|r| r.into_feature()).collect();
let response = PoiFeatureCollection { let response = PoiFeatureCollection {