spg_sqlx/arguments.rs
1//! v7.16.0 — `sqlx::Arguments` for SPG. The bind buffer holds
2//! engine-side [`Value`]s; the per-type `Encode` impls push the
3//! converted Value into the buffer at `bind` time.
4
5use sqlx_core::arguments::Arguments;
6use sqlx_core::encode::{Encode, IsNull};
7use sqlx_core::error::BoxDynError;
8use sqlx_core::impl_into_arguments_for_arguments;
9use sqlx_core::types::Type;
10
11use spg_embedded::Value as EngineValue;
12
13use crate::database::Spg;
14use crate::type_info::SpgTypeInfo;
15
16/// One bound argument. Wraps the engine-side value plus its
17/// fixed [`SpgTypeInfo`] so the engine's executor sees a
18/// pre-coerced value (matches the engine's `Expr::Placeholder`
19/// → `params[N-1]` substitution path that v6.1.1 wired up for
20/// the pgwire extended-query protocol).
21#[derive(Debug, Clone)]
22pub struct SpgArgumentValue<'q> {
23 /// Owned engine value. `'q` lifetime is unused today but
24 /// reserved to match sqlx's generic shape — future borrowed-
25 /// bind paths (large BYTEA / TEXT[]) can flow through
26 /// without a breaking change to the Arguments contract.
27 pub value: EngineValue,
28 /// The type the bind site claims for this value. Empty
29 /// when the bind reached the buffer via `add_unchecked`.
30 pub type_info: Option<SpgTypeInfo>,
31 /// Marker for the borrow lifetime.
32 pub _phantom: core::marker::PhantomData<&'q ()>,
33}
34
35/// Buffer of bound arguments for one `Execute` call. Indexed
36/// 0..N; PG-style `$1` resolves to slot 0.
37#[derive(Debug, Clone, Default)]
38pub struct SpgArguments<'q> {
39 /// The bound values. `Vec<SpgArgumentValue>` so future
40 /// borrowed cases stay non-breaking.
41 pub values: Vec<SpgArgumentValue<'q>>,
42}
43
44impl<'q> SpgArguments<'q> {
45 /// Drain the buffer into an owned `Vec<EngineValue>` shaped
46 /// for [`spg_embedded::Database::execute_prepared`]. Called
47 /// by the connection's execute path right before the engine
48 /// dispatch.
49 #[must_use]
50 pub fn into_engine_values(self) -> Vec<EngineValue> {
51 self.values.into_iter().map(|a| a.value).collect()
52 }
53
54 /// Convenience: number of bound args. `len()` is what sqlx-
55 /// core uses to validate the placeholder count at
56 /// `Arguments::len` time.
57 #[must_use]
58 pub fn count(&self) -> usize {
59 self.values.len()
60 }
61}
62
63impl<'q> Arguments<'q> for SpgArguments<'q> {
64 type Database = Spg;
65
66 fn reserve(&mut self, additional: usize, _size: usize) {
67 self.values.reserve(additional);
68 }
69
70 fn add<T>(&mut self, value: T) -> Result<(), BoxDynError>
71 where
72 T: 'q + Encode<'q, Self::Database> + Type<Self::Database>,
73 {
74 // Push a placeholder slot so the per-type Encode can
75 // write its IsNull / engine-Value into it, mirroring
76 // sqlx-core's per-driver pattern. The Encode impls in
77 // `crate::types` resolve the slot via the back of the
78 // `values` vec.
79 self.values.push(SpgArgumentValue {
80 value: EngineValue::Null,
81 type_info: Some(T::type_info()),
82 _phantom: core::marker::PhantomData,
83 });
84 let mut buf: Vec<SpgArgumentValue<'q>> = Vec::new();
85 let is_null = value.encode_by_ref(&mut buf)?;
86 // The encode impl appended one new slot. Move it into
87 // the position we just reserved and drop the placeholder.
88 if let Some(written) = buf.pop() {
89 let idx = self.values.len() - 1;
90 self.values[idx] = written;
91 }
92 if matches!(is_null, IsNull::Yes) {
93 let idx = self.values.len() - 1;
94 self.values[idx].value = EngineValue::Null;
95 }
96 Ok(())
97 }
98
99 fn len(&self) -> usize {
100 self.values.len()
101 }
102}
103
104// sqlx-core's macro generates the trivial `IntoArguments` impl
105// that lets `query.bind(...)` flow through `query.execute(pool)`.
106impl_into_arguments_for_arguments!(SpgArguments<'q>);