Skip to main content

chdb_rust/
query_result.rs

1//! Query result handling for chDB.
2//!
3//! This module provides the [`QueryResult`] type for accessing query execution results.
4
5use core::slice;
6use std::borrow::Cow;
7use std::ffi::CStr;
8use std::time::Duration;
9
10use crate::bindings;
11use crate::error::Error;
12use crate::error::Result;
13
14/// The result of a query execution.
15///
16/// `QueryResult` contains the output data from a query execution, along with
17/// metadata such as execution time and number of rows read.
18///
19/// # Thread Safety
20///
21/// `QueryResult` implements `Send`, meaning it can be safely transferred between threads.
22///
23/// # Examples
24///
25/// ```no_run
26/// use chdb_rust::execute;
27/// use chdb_rust::format::OutputFormat;
28/// use chdb_rust::arg::Arg;
29///
30/// let result = execute(
31///     "SELECT number FROM numbers(10)",
32///     Some(&[Arg::OutputFormat(OutputFormat::JSONEachRow)])
33/// )?;
34///
35/// // Access the data as a string
36/// println!("Data: {}", result.data_utf8_lossy());
37///
38/// // Access metadata
39/// println!("Rows read: {}", result.rows_read());
40/// println!("Bytes read: {}", result.bytes_read());
41/// println!("Elapsed time: {:?}", result.elapsed());
42/// # Ok::<(), chdb_rust::error::Error>(())
43/// ```
44#[derive(Debug)]
45pub struct QueryResult {
46    inner: *mut bindings::chdb_result,
47}
48
49// Safety: QueryResult is safe to send between threads
50// The underlying chDB result structure is thread-safe for read access
51unsafe impl Send for QueryResult {}
52
53impl QueryResult {
54    pub(crate) fn new(inner: *mut bindings::chdb_result) -> Self {
55        Self { inner }
56    }
57
58    /// Get the result data as a UTF-8 string.
59    ///
60    /// This method validates that the data is valid UTF-8. If the data contains
61    /// invalid UTF-8 sequences, it returns an error.
62    ///
63    /// # Returns
64    ///
65    /// Returns a `String` containing the query result, or an error if the data
66    /// contains invalid UTF-8 sequences.
67    ///
68    /// # Examples
69    ///
70    /// ```no_run
71    /// use chdb_rust::execute;
72    ///
73    /// let result = execute("SELECT 'Hello, World!' AS greeting", None)?;
74    /// let data = result.data_utf8()?;
75    /// println!("{}", data);
76    /// # Ok::<(), chdb_rust::error::Error>(())
77    /// ```
78    ///
79    /// # Errors
80    ///
81    /// Returns [`Error::NonUtf8Sequence`] if the
82    /// result data contains invalid UTF-8 sequences. Use [`data_utf8_lossy`](Self::data_utf8_lossy)
83    /// if you want to handle invalid UTF-8 gracefully.
84    pub fn data_utf8(&self) -> Result<String> {
85        let buf = self.data_ref();
86        String::from_utf8(buf.to_vec()).map_err(Error::NonUtf8Sequence)
87    }
88
89    /// Get the result data as a UTF-8 string, replacing invalid sequences.
90    ///
91    /// This method converts the result data to a string, replacing any invalid UTF-8
92    /// sequences with the Unicode replacement character (U+FFFD).
93    ///
94    /// # Returns
95    ///
96    /// Returns a `Cow<str>` containing the query result. Invalid UTF-8 sequences
97    /// are replaced with the replacement character.
98    ///
99    /// # Examples
100    ///
101    /// ```no_run
102    /// use chdb_rust::execute;
103    ///
104    /// let result = execute("SELECT 'Hello, World!' AS greeting", None)?;
105    /// let data = result.data_utf8_lossy();
106    /// println!("{}", data);
107    /// # Ok::<(), chdb_rust::error::Error>(())
108    /// ```
109    pub fn data_utf8_lossy(&self) -> Cow<'_, str> {
110        String::from_utf8_lossy(self.data_ref())
111    }
112
113    /// Get the result data as a UTF-8 string without validation.
114    ///
115    /// # Safety
116    ///
117    /// This function is marked as safe, but it will produce invalid UTF-8 strings
118    /// if the underlying data contains non-UTF-8 bytes. Only use this if you're
119    /// certain the data is valid UTF-8, or if you're prepared to handle potentially
120    /// invalid strings.
121    ///
122    /// # Examples
123    ///
124    /// ```no_run
125    /// use chdb_rust::execute;
126    ///
127    /// let result = execute("SELECT 'Hello' AS greeting", None)?;
128    /// let data = result.data_utf8_unchecked();
129    /// println!("{}", data);
130    /// # Ok::<(), chdb_rust::error::Error>(())
131    /// ```
132    pub fn data_utf8_unchecked(&self) -> String {
133        unsafe { String::from_utf8_unchecked(self.data_ref().to_vec()) }
134    }
135
136    /// Get a reference to the raw result data as bytes.
137    ///
138    /// This method returns a byte slice containing the raw query result data.
139    /// The data is in the format specified when executing the query (e.g., JSON, CSV, etc.).
140    ///
141    /// # Returns
142    ///
143    /// Returns a byte slice containing the query result data. Returns an empty slice
144    /// if there's no data or if the buffer pointer is null.
145    ///
146    /// # Examples
147    ///
148    /// ```no_run
149    /// use chdb_rust::execute;
150    ///
151    /// let result = execute("SELECT 1 AS value", None)?;
152    /// let bytes = result.data_ref();
153    /// println!("Data length: {} bytes", bytes.len());
154    /// # Ok::<(), chdb_rust::error::Error>(())
155    /// ```
156    pub fn data_ref(&self) -> &[u8] {
157        let buf = unsafe { bindings::chdb_result_buffer(self.inner) };
158        let len = unsafe { bindings::chdb_result_length(self.inner) };
159        if buf.is_null() || len == 0 {
160            return &[];
161        }
162        unsafe { slice::from_raw_parts(buf as *const u8, len) }
163    }
164
165    /// Get the number of rows read by the query.
166    ///
167    /// This returns the total number of rows that were read from storage during
168    /// query execution.
169    ///
170    /// # Returns
171    ///
172    /// Returns the number of rows read as a `u64`.
173    ///
174    /// # Examples
175    ///
176    /// ```no_run
177    /// use chdb_rust::execute;
178    ///
179    /// let result = execute("SELECT number FROM numbers(100)", None)?;
180    /// println!("Rows read: {}", result.rows_read());
181    /// # Ok::<(), chdb_rust::error::Error>(())
182    /// ```
183    pub fn rows_read(&self) -> u64 {
184        unsafe { bindings::chdb_result_rows_read(self.inner) }
185    }
186
187    /// Get the number of bytes read by the query.
188    ///
189    /// This returns the total number of bytes that were read from storage during
190    /// query execution.
191    ///
192    /// # Returns
193    ///
194    /// Returns the number of bytes read as a `u64`.
195    ///
196    /// # Examples
197    ///
198    /// ```no_run
199    /// use chdb_rust::execute;
200    ///
201    /// let result = execute("SELECT number FROM numbers(100)", None)?;
202    /// println!("Bytes read: {}", result.bytes_read());
203    /// # Ok::<(), chdb_rust::error::Error>(())
204    /// ```
205    pub fn bytes_read(&self) -> u64 {
206        unsafe { bindings::chdb_result_bytes_read(self.inner) }
207    }
208
209    /// Get the elapsed time for query execution.
210    ///
211    /// This returns the time it took to execute the query, measured from when
212    /// the query was submitted until the result was ready.
213    ///
214    /// # Returns
215    ///
216    /// Returns a [`Duration`] representing the elapsed time.
217    ///
218    /// # Examples
219    ///
220    /// ```no_run
221    /// use chdb_rust::execute;
222    ///
223    /// let result = execute("SELECT number FROM numbers(1000)", None)?;
224    /// println!("Query took: {:?}", result.elapsed());
225    /// # Ok::<(), chdb_rust::error::Error>(())
226    /// ```
227    pub fn elapsed(&self) -> Duration {
228        let elapsed = unsafe { bindings::chdb_result_elapsed(self.inner) };
229        Duration::from_secs_f64(elapsed)
230    }
231
232    pub(crate) fn check_error(self) -> Result<Self> {
233        self.check_error_ref()?;
234        Ok(self)
235    }
236
237    pub(crate) fn check_error_ref(&self) -> Result<()> {
238        let err_ptr = unsafe { bindings::chdb_result_error(self.inner) };
239
240        if err_ptr.is_null() {
241            return Ok(());
242        }
243
244        let err_msg = unsafe { CStr::from_ptr(err_ptr).to_string_lossy().to_string() };
245        if err_msg.is_empty() {
246            return Ok(());
247        }
248
249        Err(Error::QueryError(err_msg))
250    }
251}
252
253impl Drop for QueryResult {
254    fn drop(&mut self) {
255        unsafe { bindings::chdb_destroy_query_result(self.inner) };
256    }
257}