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    /// Consumes the `RowValue` and returns the underlying raw binary data.
125    ///
126    /// This method transfers ownership of the byte vector. If the value
127    /// is NULL, it returns `None`.
128    ///
129    /// # Returns
130    /// An owned `Vec<u8>` if the value is not NULL, or `None` if the value is NULL.
131    pub fn into_bytes(self) -> Option<Vec<u8>> {
132        self.value
133    }
134
135    /// This method attempts to interpret the binary data as a UTF-8 encoded string.
136    ///
137    /// # Returns
138    /// * `Ok(Some(str))` if the value is not NULL and was successfully converted to a string
139    /// * `Ok(None)` if the value is NULL
140    /// * `Err` with a message if the value is not valid UTF-8
141    pub fn as_str(&self) -> Result<Option<&str>, SdkError> {
142        self.value
143            .as_deref()
144            .map(|value| {
145                std::str::from_utf8(value)
146                    .map_err(|e| SdkError::from(format!("Failed to convert bytes to string: {e}")))
147            })
148            .transpose()
149    }
150
151    /// Converts the binary data to a value that implements `TryFromBytes`.
152    ///
153    /// This method attempts to interpret the binary data as a value of the specified type.
154    /// It's particularly useful for converting database values to Rust values.
155    ///
156    /// # Type Parameters
157    /// * `T` - The type to convert the binary data to, must implement `TryFromBytes`
158    ///
159    /// # Returns
160    /// * `Ok(Some(T))` if the value is not NULL and was successfully converted
161    /// * `Ok(None)` if the value is NULL
162    /// * `Err` with a message if conversion failed
163    pub fn as_value<T>(&self) -> Result<Option<T>, SdkError>
164    where
165        T: TryFromBytes,
166    {
167        self.value
168            .as_deref()
169            .map(|value| {
170                T::try_read_from_bytes(value)
171                    .map_err(|e| SdkError::from(format!("Failed to convert bytes to primitive: {e:?}")))
172            })
173            .transpose()
174    }
175
176    /// Deserializes this value as JSON into the specified type.
177    ///
178    /// This method parses the binary data as JSON and converts it to the
179    /// requested type.
180    ///
181    /// # Type Parameters
182    /// * `T` - The type to deserialize the JSON into
183    ///
184    /// # Returns
185    /// * `Ok(Some(T))` if the value is not NULL and was successfully deserialized
186    /// * `Ok(None)` if the value is NULL
187    /// * `Err` with a message if deserialization failed
188    pub fn as_json<T>(&self) -> Result<Option<T>, SdkError>
189    where
190        T: for<'a> Deserialize<'a>,
191    {
192        match self.value {
193            Some(ref value) => serde_json::from_slice(value).map_err(SdkError::from),
194            None => Ok(None),
195        }
196    }
197}
198
199/// A builder for constructing SQL queries with bound parameters.
200///
201/// This struct facilitates the creation of `Query` objects by allowing
202/// the gradual binding of parameters before finalizing the query for execution.
203#[derive(Debug, Default)]
204pub struct QueryBuilder {
205    /// The SQL query string being built.
206    query: String,
207    /// A list of bound parameter values for the query.
208    values: Vec<wit::PgBoundValue>,
209    /// A tree structure holding nested values, primarily used for arrays.
210    value_tree: wit::PgValueTree,
211}
212
213impl QueryBuilder {
214    /// Binds a value to the query as a parameter.
215    ///
216    /// This method adds a value to be used as a parameter in the SQL query.
217    /// The value will be properly escaped and converted to the appropriate PostgreSQL type.
218    ///
219    /// # Parameters
220    /// * `value` - Any value that implements the `DatabaseType` trait
221    ///
222    /// # Returns
223    /// The query builder for method chaining
224    pub fn bind(&mut self, value: impl DatabaseType) {
225        let value = value.into_bound_value(self.value_tree.len() as u64);
226        self.bind_value(value);
227    }
228
229    /// Binds a pre-constructed `DatabaseValue` to the query as a parameter.
230    ///
231    /// This method is similar to `bind()` but accepts a `DatabaseValue` that has already been
232    /// created, which can be useful when you need more control over how values are bound.
233    ///
234    /// # Parameters
235    /// * `value` - A `DatabaseValue` instance to bind to the query
236    pub fn bind_value(&mut self, value: DatabaseValue) {
237        let DatabaseValue {
238            value: bound_value,
239            array_values,
240        } = value;
241
242        let wit::PgBoundValue {
243            mut value,
244            type_,
245            is_array,
246        } = bound_value;
247
248        // If the value is an array, adjust the indices based on the current size of the value_tree
249        if let wit::PgValue::Array(items) = &mut value {
250            let offset = self.value_tree.len() as u64;
251            items.iter_mut().for_each(|x| *x += offset);
252        }
253
254        // Add the potentially modified value to the list of bound values
255        self.values.push(wit::PgBoundValue { value, type_, is_array });
256
257        // If there are associated array values (nested arrays), extend the value_tree
258        if let Some(array_values) = array_values {
259            self.value_tree.extend(array_values);
260        }
261    }
262
263    /// Finalizes the query construction process.
264    ///
265    /// This method takes the built query string and bound parameters from the `QueryBuilder`
266    /// and combines them with a database connection or transaction to create a `Query` object.
267    /// The returned `Query` object is ready for execution.
268    ///
269    /// # Parameters
270    /// * `connection` - A database connection (`&Connection`) or transaction (`&Transaction`)
271    ///   where the query will be executed. This parameter accepts any type that can be converted
272    ///   into a `ConnectionLike` enum, typically a reference to a `Connection` or `Transaction`.
273    ///
274    /// # Returns
275    /// A `Query` instance containing the finalized SQL query, bound parameters, and the connection,
276    /// ready to be executed or fetched.
277    pub fn finalize(self) -> Query {
278        let query = self.query;
279        let values = self.values;
280        let value_tree = self.value_tree;
281
282        Query {
283            query,
284            values,
285            value_tree,
286        }
287    }
288
289    /// Returns the number of values currently bound to the query builder.
290    ///
291    /// This can be useful for generating parameter placeholders (e.g., `$1`, `$2`)
292    /// dynamically while building the query string.
293    pub fn bound_values(&self) -> usize {
294        self.values.len()
295    }
296}
297
298impl Write for QueryBuilder {
299    fn write_str(&mut self, s: &str) -> std::fmt::Result {
300        self.query.write_str(s)
301    }
302}