use std::sync::Arc;
use futures::future::try_join_all;
use tokio::sync::Mutex;
use tokio_postgres::Statement;
use oxisql_core::{Row, ToSqlValue};
use crate::error::PgError;
use crate::types::{pg_row_to_row, value_to_param, OwnedParam};
enum PipelineQuery {
Execute {
sql: String,
params: Vec<OwnedParam>,
},
Query {
sql: String,
params: Vec<OwnedParam>,
},
}
pub struct PipelineResult {
pub executes: Vec<u64>,
pub queries: Vec<Vec<Row>>,
}
pub struct PgPipeline {
client: Arc<Mutex<tokio_postgres::Client>>,
queries: Vec<PipelineQuery>,
}
impl PgPipeline {
pub(crate) fn new(client: Arc<Mutex<tokio_postgres::Client>>) -> Self {
Self {
client,
queries: Vec::new(),
}
}
pub fn add_execute(&mut self, sql: &str, params: &[&dyn ToSqlValue]) {
let owned: Vec<OwnedParam> = params
.iter()
.map(|p| value_to_param(&p.to_value()))
.collect();
self.queries.push(PipelineQuery::Execute {
sql: sql.to_string(),
params: owned,
});
}
pub fn add_query(&mut self, sql: &str, params: &[&dyn ToSqlValue]) {
let owned: Vec<OwnedParam> = params
.iter()
.map(|p| value_to_param(&p.to_value()))
.collect();
self.queries.push(PipelineQuery::Query {
sql: sql.to_string(),
params: owned,
});
}
pub async fn finish(self) -> Result<PipelineResult, PgError> {
if self.queries.is_empty() {
return Ok(PipelineResult {
executes: Vec::new(),
queries: Vec::new(),
});
}
let client = self.client.lock().await;
let prepare_futs: Vec<_> = self
.queries
.iter()
.map(|q| {
let sql = match q {
PipelineQuery::Execute { sql, .. } => sql.as_str(),
PipelineQuery::Query { sql, .. } => sql.as_str(),
};
client.prepare(sql)
})
.collect();
let stmts: Vec<Statement> = try_join_all(prepare_futs).await.map_err(PgError::from)?;
let mut executes: Vec<u64> = Vec::new();
let mut queries: Vec<Vec<Row>> = Vec::new();
for (q, stmt) in self.queries.into_iter().zip(stmts) {
match q {
PipelineQuery::Execute { params, .. } => {
let refs = owned_refs(¶ms);
let n = client
.execute(&stmt, refs.as_slice())
.await
.map_err(PgError::from)?;
executes.push(n);
}
PipelineQuery::Query { params, .. } => {
let refs = owned_refs(¶ms);
let pg_rows = client
.query(&stmt, refs.as_slice())
.await
.map_err(PgError::from)?;
let rows: Vec<Row> = pg_rows.into_iter().map(pg_row_to_row).collect::<Result<
Vec<_>,
PgError,
>>(
)?;
queries.push(rows);
}
}
}
Ok(PipelineResult { executes, queries })
}
}
fn owned_refs(owned: &[OwnedParam]) -> Vec<&(dyn tokio_postgres::types::ToSql + Sync)> {
owned.iter().map(|p| p as _).collect()
}