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}