grafbase_sdk/host_io/postgres/
query.rs

1use std::fmt::{self, Write};
2
3use serde::Deserialize;
4use zerocopy::TryFromBytes;
5
6use crate::{SdkError, wit};
7
8use super::{
9    ConnectionLike,
10    types::{DatabaseType, DatabaseValue},
11};
12
13/// A query builder for constructing and executing SQL queries.
14///
15/// This struct provides a fluent interface for building SQL queries with
16/// parameter binding, and methods to execute those queries or fetch their results.
17#[derive(Clone, Debug)]
18pub struct Query {
19    pub(crate) query: String,
20    pub(crate) values: Vec<wit::PgBoundValue>,
21    pub(crate) value_tree: wit::PgValueTree,
22}
23
24impl fmt::Display for Query {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.write_str(&self.query)
27    }
28}
29
30impl Query {
31    /// Creates a new `QueryBuilder` instance.
32    ///
33    /// This is the entry point for constructing a new query using the builder pattern.
34    pub fn builder() -> QueryBuilder {
35        QueryBuilder::default()
36    }
37
38    /// Executes the SQL query with the bound parameters.
39    ///
40    /// This method sends the query to the database server and returns the number of rows affected.
41    /// For INSERT, UPDATE, or DELETE statements, this represents the number of rows modified.
42    /// For other statements, the meaning of the return value depends on the specific operation.
43    ///
44    /// # Returns
45    /// The number of rows affected by the query, or an error message if the execution failed
46    pub fn execute<'a>(self, connection: impl Into<ConnectionLike<'a>>) -> Result<u64, SdkError> {
47        connection
48            .into()
49            .execute(&self.query, (self.values.as_slice(), &self.value_tree))
50    }
51
52    /// Executes the SQL query and fetches all rows from the result.
53    ///
54    /// This method sends the query to the database and returns all rows in the result set.
55    /// It's useful for SELECT queries where you want to process multiple results.
56    ///
57    /// # Returns
58    /// A vector containing all rows in the result set, or an error message if the execution failed
59    pub fn fetch<'a>(
60        self,
61        connection: impl Into<ConnectionLike<'a>>,
62    ) -> Result<impl Iterator<Item = ColumnIterator>, SdkError> {
63        let rows = connection
64            .into()
65            .query(&self.query, (self.values.as_slice(), &self.value_tree))?;
66
67        let rows = rows.into_iter().map(|row| ColumnIterator {
68            position: 0,
69            length: row.len() as usize,
70            row,
71        });
72
73        Ok(rows)
74    }
75}
76
77/// An iterator over the columns in a database row.
78///
79/// This iterator yields each column value in the row as a `RowValue`
80/// which contains both the column name and the value data.
81pub struct ColumnIterator {
82    position: usize,
83    length: usize,
84    row: wit::PgRow,
85}
86
87impl Iterator for ColumnIterator {
88    type Item = Result<RowValue, SdkError>;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        if self.position < self.length {
92            let value = match self.row.as_bytes(self.position as u64) {
93                Ok(value) => value,
94                Err(err) => return Some(Err(SdkError::from(err))),
95            };
96
97            self.position += 1;
98
99            Some(Ok(RowValue { value }))
100        } else {
101            None
102        }
103    }
104}
105
106/// A value from a database row.
107///
108/// This struct represents a single column value from a database row. It includes
109/// both the column name and the raw binary data for the value, which can be
110/// accessed or converted to other types as needed.
111pub struct RowValue {
112    value: Option<Vec<u8>>,
113}
114
115impl RowValue {
116    /// Returns the raw binary data for this value.
117    ///
118    /// # Returns
119    /// A slice of bytes if the value is not NULL, or None if the value is NULL.
120    pub fn bytes(&self) -> Option<&[u8]> {
121        self.value.as_deref()
122    }
123
124    /// This method attempts to interpret the binary data as a UTF-8 encoded string.
125    ///
126    /// # Returns
127    /// * `Ok(Some(str))` if the value is not NULL and was successfully converted to a string
128    /// * `Ok(None)` if the value is NULL
129    /// * `Err` with a message if the value is not valid UTF-8
130    pub fn as_str(&self) -> Result<Option<&str>, SdkError> {
131        self.value
132            .as_deref()
133            .map(|value| {
134                std::str::from_utf8(value)
135                    .map_err(|e| SdkError::from(format!("Failed to convert bytes to string: {}", e)))
136            })
137            .transpose()
138    }
139
140    /// Converts the binary data to a value that implements `TryFromBytes`.
141    ///
142    /// This method attempts to interpret the binary data as a value of the specified type.
143    /// It's particularly useful for converting database values to Rust values.
144    ///
145    /// # Type Parameters
146    /// * `T` - The type to convert the binary data to, must implement `TryFromBytes`
147    ///
148    /// # Returns
149    /// * `Ok(Some(T))` if the value is not NULL and was successfully converted
150    /// * `Ok(None)` if the value is NULL
151    /// * `Err` with a message if conversion failed
152    pub fn as_value<T>(&self) -> Result<Option<T>, SdkError>
153    where
154        T: TryFromBytes,
155    {
156        self.value
157            .as_deref()
158            .map(|value| {
159                T::try_read_from_bytes(value)
160                    .map_err(|e| SdkError::from(format!("Failed to convert bytes to primitive: {e:?}")))
161            })
162            .transpose()
163    }
164
165    /// Deserializes this value as JSON into the specified type.
166    ///
167    /// This method parses the binary data as JSON and converts it to the
168    /// requested type.
169    ///
170    /// # Type Parameters
171    /// * `T` - The type to deserialize the JSON into
172    ///
173    /// # Returns
174    /// * `Ok(Some(T))` if the value is not NULL and was successfully deserialized
175    /// * `Ok(None)` if the value is NULL
176    /// * `Err` with a message if deserialization failed
177    pub fn as_json<T>(&self) -> Result<Option<T>, SdkError>
178    where
179        T: for<'a> Deserialize<'a>,
180    {
181        match self.value {
182            Some(ref value) => serde_json::from_slice(value).map_err(SdkError::from),
183            None => Ok(None),
184        }
185    }
186}
187
188/// A builder for constructing SQL queries with bound parameters.
189///
190/// This struct facilitates the creation of `Query` objects by allowing
191/// the gradual binding of parameters before finalizing the query for execution.
192#[derive(Debug, Default)]
193pub struct QueryBuilder {
194    /// The SQL query string being built.
195    query: String,
196    /// A list of bound parameter values for the query.
197    values: Vec<wit::PgBoundValue>,
198    /// A tree structure holding nested values, primarily used for arrays.
199    value_tree: wit::PgValueTree,
200}
201
202impl QueryBuilder {
203    /// Binds a value to the query as a parameter.
204    ///
205    /// This method adds a value to be used as a parameter in the SQL query.
206    /// The value will be properly escaped and converted to the appropriate PostgreSQL type.
207    ///
208    /// # Parameters
209    /// * `value` - Any value that implements the `DatabaseType` trait
210    ///
211    /// # Returns
212    /// The query builder for method chaining
213    pub fn bind(&mut self, value: impl DatabaseType) {
214        let value = value.into_bound_value(self.value_tree.len() as u64);
215        self.bind_value(value);
216    }
217
218    /// Binds a pre-constructed `DatabaseValue` to the query as a parameter.
219    ///
220    /// This method is similar to `bind()` but accepts a `DatabaseValue` that has already been
221    /// created, which can be useful when you need more control over how values are bound.
222    ///
223    /// # Parameters
224    /// * `value` - A `DatabaseValue` instance to bind to the query
225    pub fn bind_value(&mut self, value: DatabaseValue) {
226        let DatabaseValue { value, array_values } = value;
227
228        self.values.push(value);
229
230        if let Some(array_values) = array_values {
231            self.value_tree.extend(array_values);
232        }
233    }
234
235    /// Finalizes the query construction process.
236    ///
237    /// This method takes the built query string and bound parameters from the `QueryBuilder`
238    /// and combines them with a database connection or transaction to create a `Query` object.
239    /// The returned `Query` object is ready for execution.
240    ///
241    /// # Parameters
242    /// * `connection` - A database connection (`&Connection`) or transaction (`&Transaction`)
243    ///   where the query will be executed. This parameter accepts any type that can be converted
244    ///   into a `ConnectionLike` enum, typically a reference to a `Connection` or `Transaction`.
245    ///
246    /// # Returns
247    /// A `Query` instance containing the finalized SQL query, bound parameters, and the connection,
248    /// ready to be executed or fetched.
249    pub fn finalize(self) -> Query {
250        let query = self.query;
251        let values = self.values;
252        let value_tree = self.value_tree;
253
254        Query {
255            query,
256            values,
257            value_tree,
258        }
259    }
260
261    /// Returns the number of values currently bound to the query builder.
262    ///
263    /// This can be useful for generating parameter placeholders (e.g., `$1`, `$2`)
264    /// dynamically while building the query string.
265    pub fn bound_values(&self) -> usize {
266        self.values.len()
267    }
268}
269
270impl Write for QueryBuilder {
271    fn write_str(&mut self, s: &str) -> std::fmt::Result {
272        self.query.write_str(s)
273    }
274}