Skip to main content

oxisql_postgres/
prepared.rs

1//! [`PgPrepared`] — a compiled PostgreSQL prepared statement.
2//!
3//! Wraps a `tokio_postgres::Statement` (a server-side handle) together with
4//! a shared reference to the underlying client so the statement can be
5//! re-executed with different parameters without re-parsing on every call.
6//!
7//! # Cache
8//!
9//! [`StmtCache`] is a `std::sync::Mutex`-guarded `HashMap` keyed on the SQL
10//! text.  Using `std::sync::Mutex` (not `tokio::sync::Mutex`) is deliberate:
11//! the lock is always acquired and released *synchronously* — it is never held
12//! across an `await` point, so no deadlock / blocking risk.
13//!
14//! `tokio_postgres::Statement` is cheaply `Clone`-able; it holds a
15//! reference-counted handle to the server-side plan.
16
17use std::collections::HashMap;
18use std::sync::{Arc, Mutex};
19
20use async_trait::async_trait;
21use tokio_postgres::Statement;
22
23use oxisql_core::{OxiSqlError, PreparedStatement, Row, ToSqlValue};
24
25use crate::types::{pg_row_to_row, value_to_param, OwnedParam};
26
27// ── StmtCache ──────────────────────────────────────────────────────────────────
28
29/// A shared, synchronously-locked cache mapping SQL text → compiled
30/// `tokio_postgres::Statement`.
31pub(crate) type StmtCache = Arc<Mutex<HashMap<String, Statement>>>;
32
33// ── helper ─────────────────────────────────────────────────────────────────────
34
35fn build_owned(params: &[&dyn ToSqlValue]) -> Vec<OwnedParam> {
36    params
37        .iter()
38        .map(|p| value_to_param(&p.to_value()))
39        .collect()
40}
41
42fn owned_refs(owned: &[OwnedParam]) -> Vec<&(dyn tokio_postgres::types::ToSql + Sync)> {
43    owned.iter().map(|p| p as _).collect()
44}
45
46// ── PgPrepared ─────────────────────────────────────────────────────────────────
47
48/// A compiled PostgreSQL prepared statement.
49///
50/// Obtain via [`oxisql_core::Connection::prepare`] on a [`crate::PgConnection`].
51///
52/// The statement is compiled once by the server and re-used for every
53/// subsequent execution, avoiding repeated parse/plan cycles.
54pub struct PgPrepared {
55    pub(crate) client: Arc<tokio::sync::Mutex<tokio_postgres::Client>>,
56    pub(crate) stmt: Statement,
57    pub(crate) sql_text: String,
58}
59
60#[async_trait]
61impl PreparedStatement for PgPrepared {
62    /// Execute the prepared statement (DML/DDL) and return the row count.
63    async fn execute(&mut self, params: &[&dyn ToSqlValue]) -> Result<u64, OxiSqlError> {
64        let owned = build_owned(params);
65        let refs = owned_refs(&owned);
66        let client = self.client.lock().await;
67        client
68            .execute(&self.stmt, refs.as_slice())
69            .await
70            .map_err(|e| OxiSqlError::Execution(e.to_string()))
71    }
72
73    /// Execute the prepared statement as a `SELECT` and return all rows.
74    async fn query(&mut self, params: &[&dyn ToSqlValue]) -> Result<Vec<Row>, OxiSqlError> {
75        let owned = build_owned(params);
76        let refs = owned_refs(&owned);
77        let client = self.client.lock().await;
78        let pg_rows = client
79            .query(&self.stmt, refs.as_slice())
80            .await
81            .map_err(|e| OxiSqlError::Execution(e.to_string()))?;
82        pg_rows
83            .into_iter()
84            .map(|r| pg_row_to_row(r).map_err(OxiSqlError::from))
85            .collect()
86    }
87
88    /// Return the original SQL text this statement was compiled from.
89    fn sql(&self) -> &str {
90        &self.sql_text
91    }
92}