Skip to main content

spg_sqlx/
statement.rs

1//! v7.16.0 — `sqlx::Statement` for SPG. Wraps the
2//! [`spg_embedded::Statement`] handle returned by the embedded
3//! `prepare` API so prepared-statement reuse flows through.
4
5use std::borrow::Cow;
6use std::sync::Arc;
7
8use either::Either;
9use sqlx_core::HashMap;
10use sqlx_core::column::ColumnIndex;
11use sqlx_core::error::Error;
12use sqlx_core::statement::Statement;
13
14use crate::column::SpgColumn;
15use crate::database::Spg;
16use crate::type_info::SpgTypeInfo;
17
18/// Prepared-statement handle. Holds:
19///   * the source SQL (for [`Statement::sql`]),
20///   * the embedded `Statement` AST handle the engine cached
21///     during prepare,
22///   * the expected output columns (populated on first execute
23///     that returns rows; empty until then),
24///   * an empty type-name → column-ordinal map shared with
25///     `SpgRow::ColumnIndex<&str>`.
26#[derive(Debug, Clone)]
27pub struct SpgStatement<'q> {
28    pub(crate) sql: Cow<'q, str>,
29    /// `Some` when the engine has produced a planned AST; `None`
30    /// for `Statement::to_owned` rebuilds where the cached AST
31    /// is not in hand.
32    pub(crate) inner: Option<Arc<spg_embedded::Statement>>,
33    pub(crate) columns: Arc<Vec<SpgColumn>>,
34    pub(crate) by_name: Arc<HashMap<String, usize>>,
35}
36
37impl<'q> SpgStatement<'q> {
38    /// Build a statement handle with no parsed-AST attached
39    /// (e.g. for `sqlx::query("...").execute(&pool)` where the
40    /// adapter prepares on-the-fly inside the connection).
41    #[must_use]
42    pub fn unparsed(sql: impl Into<Cow<'q, str>>) -> Self {
43        Self {
44            sql: sql.into(),
45            inner: None,
46            columns: Arc::new(Vec::new()),
47            by_name: Arc::new(HashMap::new()),
48        }
49    }
50}
51
52impl<'q> Statement<'q> for SpgStatement<'q> {
53    type Database = Spg;
54
55    fn to_owned(&self) -> SpgStatement<'static> {
56        SpgStatement {
57            sql: Cow::Owned(self.sql.clone().into_owned()),
58            inner: self.inner.clone(),
59            columns: Arc::clone(&self.columns),
60            by_name: Arc::clone(&self.by_name),
61        }
62    }
63
64    fn sql(&self) -> &str {
65        &self.sql
66    }
67
68    fn parameters(&self) -> Option<Either<&[SpgTypeInfo], usize>> {
69        // v7.16.0 — SPG-engine plan doesn't surface per-
70        // placeholder type info today; we hand sqlx the count
71        // shape (Right(n)). v7.16.x will plumb the Engine's
72        // existing Placeholder type-inference pass through.
73        None
74    }
75
76    fn columns(&self) -> &[SpgColumn] {
77        &self.columns
78    }
79
80    fn query(&self) -> sqlx_core::query::Query<'_, Spg, crate::SpgArguments<'_>> {
81        sqlx_core::query::query_statement(self)
82    }
83
84    fn query_with<'s, A>(&'s self, arguments: A) -> sqlx_core::query::Query<'s, Spg, A>
85    where
86        A: sqlx_core::arguments::IntoArguments<'s, Spg>,
87    {
88        sqlx_core::query::query_statement_with(self, arguments)
89    }
90
91    fn query_as<O>(&self) -> sqlx_core::query_as::QueryAs<'_, Spg, O, crate::SpgArguments<'_>>
92    where
93        O: for<'r> sqlx_core::from_row::FromRow<'r, crate::SpgRow>,
94    {
95        sqlx_core::query_as::query_statement_as(self)
96    }
97
98    fn query_as_with<'s, O, A>(
99        &'s self,
100        arguments: A,
101    ) -> sqlx_core::query_as::QueryAs<'s, Spg, O, A>
102    where
103        O: for<'r> sqlx_core::from_row::FromRow<'r, crate::SpgRow>,
104        A: sqlx_core::arguments::IntoArguments<'s, Spg>,
105    {
106        sqlx_core::query_as::query_statement_as_with(self, arguments)
107    }
108
109    fn query_scalar<O>(
110        &self,
111    ) -> sqlx_core::query_scalar::QueryScalar<'_, Spg, O, crate::SpgArguments<'_>>
112    where
113        (O,): for<'r> sqlx_core::from_row::FromRow<'r, crate::SpgRow>,
114    {
115        sqlx_core::query_scalar::query_statement_scalar(self)
116    }
117
118    fn query_scalar_with<'s, O, A>(
119        &'s self,
120        arguments: A,
121    ) -> sqlx_core::query_scalar::QueryScalar<'s, Spg, O, A>
122    where
123        (O,): for<'r> sqlx_core::from_row::FromRow<'r, crate::SpgRow>,
124        A: sqlx_core::arguments::IntoArguments<'s, Spg>,
125    {
126        sqlx_core::query_scalar::query_statement_scalar_with(self, arguments)
127    }
128}
129
130impl ColumnIndex<SpgStatement<'_>> for &str {
131    fn index(&self, stmt: &SpgStatement<'_>) -> Result<usize, Error> {
132        stmt.by_name
133            .get(*self)
134            .copied()
135            .ok_or_else(|| Error::ColumnNotFound((*self).to_string()))
136    }
137}
138
139impl ColumnIndex<SpgStatement<'_>> for usize {
140    fn index(&self, stmt: &SpgStatement<'_>) -> Result<usize, Error> {
141        if *self >= stmt.columns.len() {
142            return Err(Error::ColumnIndexOutOfBounds {
143                index: *self,
144                len: stmt.columns.len(),
145            });
146        }
147        Ok(*self)
148    }
149}