use crate::connection::ConnectionPool;
use crate::error::{QueryError, Result};
use crate::query::SpatialQuery;
use crate::types::FeatureBuilder;
use futures::stream::{Stream, StreamExt};
use oxigdal_core::vector::feature::Feature;
use std::pin::Pin;
use tracing::debug;
pub struct PostGisReader {
pool: ConnectionPool,
table_name: String,
geometry_column: String,
id_column: Option<String>,
batch_size: usize,
buffer: Vec<Feature>,
}
impl PostGisReader {
pub fn new(pool: ConnectionPool, table_name: impl Into<String>) -> Self {
Self {
pool,
table_name: table_name.into(),
geometry_column: "geom".to_string(),
id_column: Some("id".to_string()),
batch_size: 1000,
buffer: Vec::new(),
}
}
pub fn geometry_column(mut self, column: impl Into<String>) -> Self {
self.geometry_column = column.into();
self
}
pub fn id_column(mut self, column: impl Into<String>) -> Self {
self.id_column = Some(column.into());
self
}
pub const fn batch_size(mut self, size: usize) -> Self {
self.batch_size = size;
self
}
pub async fn read_all(&mut self) -> Result<Vec<Feature>> {
let query = SpatialQuery::new(&self.table_name)?.geometry_column(&self.geometry_column);
query.execute(&self.pool).await
}
pub async fn read_with_query(&mut self, query: SpatialQuery) -> Result<Vec<Feature>> {
query.execute(&self.pool).await
}
pub async fn stream(&self) -> Result<Pin<Box<dyn Stream<Item = Result<Feature>> + Send + '_>>> {
let client = self.pool.get().await?;
let query = format!("SELECT * FROM \"{}\"", self.table_name);
debug!("Streaming features with query: {query}");
let row_stream = client
.query_raw(&query, std::iter::empty::<i32>())
.await
.map_err(|e| QueryError::ExecutionFailed {
message: e.to_string(),
})?;
let geometry_column = self.geometry_column.clone();
let feature_stream = row_stream.map(move |result| {
let row = result.map_err(|e| QueryError::ExecutionFailed {
message: e.to_string(),
})?;
FeatureBuilder::new()
.geometry_column(&geometry_column)
.build_from_row(&row)
});
Ok(Box::pin(feature_stream))
}
pub async fn count(&self) -> Result<i64> {
let query = SpatialQuery::new(&self.table_name)?;
query.count(&self.pool).await
}
pub async fn extent(&self) -> Result<Option<(f64, f64, f64, f64)>> {
let client = self.pool.get().await?;
let query = format!(
"SELECT ST_Extent(\"{}\")::text FROM \"{}\"",
self.geometry_column, self.table_name
);
let row = client
.query_one(&query, &[])
.await
.map_err(|e| QueryError::ExecutionFailed {
message: e.to_string(),
})?;
let extent_str: Option<String> = row.get(0);
if let Some(extent) = extent_str {
let extent = extent.trim_start_matches("BOX(").trim_end_matches(')');
let parts: Vec<&str> = extent.split(',').collect();
if parts.len() == 2 {
let min_parts: Vec<f64> = parts[0]
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
let max_parts: Vec<f64> = parts[1]
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
if min_parts.len() == 2 && max_parts.len() == 2 {
return Ok(Some((
min_parts[0],
min_parts[1],
max_parts[0],
max_parts[1],
)));
}
}
}
Ok(None)
}
pub async fn srid(&self) -> Result<Option<i32>> {
let client = self.pool.get().await?;
let query = format!(
"SELECT Find_SRID('public', '{}', '{}')",
self.table_name, self.geometry_column
);
let row = client
.query_one(&query, &[])
.await
.map_err(|e| QueryError::ExecutionFailed {
message: e.to_string(),
})?;
let srid: i32 = row.get(0);
if srid == 0 { Ok(None) } else { Ok(Some(srid)) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::connection::ConnectionConfig;
#[test]
fn test_reader_creation() {
let config = ConnectionConfig::default();
let pool = ConnectionPool::new(config).ok();
assert!(pool.is_some());
let pool = pool.expect("pool creation failed");
let reader = PostGisReader::new(pool, "test_table");
assert_eq!(reader.table_name, "test_table");
assert_eq!(reader.geometry_column, "geom");
assert_eq!(reader.batch_size, 1000);
}
#[test]
fn test_reader_configuration() {
let config = ConnectionConfig::default();
let pool = ConnectionPool::new(config).ok();
assert!(pool.is_some());
let pool = pool.expect("pool creation failed");
let reader = PostGisReader::new(pool, "test_table")
.geometry_column("the_geom")
.id_column("fid")
.batch_size(500);
assert_eq!(reader.geometry_column, "the_geom");
assert_eq!(reader.id_column, Some("fid".to_string()));
assert_eq!(reader.batch_size, 500);
}
}