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}