Skip to main content

sql_middleware/results/
result_set.rs

1use super::row::CustomDbRow;
2use crate::error::SqlMiddlewareDbError;
3use crate::types::RowValues;
4
5type ColumnCacheMap = std::sync::LazyLock<
6    std::sync::Mutex<
7        std::collections::HashMap<usize, std::sync::Arc<std::collections::HashMap<String, usize>>>,
8    >,
9>;
10
11/// A result set from a database query
12///
13/// This struct represents the result of a database query,
14/// containing the rows returned by the query and metadata.
15#[derive(Debug, Clone, Default)]
16pub struct ResultSet {
17    /// The rows returned by the query
18    pub results: Vec<CustomDbRow>,
19    /// The number of rows affected (for DML statements)
20    pub rows_affected: usize,
21    /// Column names shared by all rows (to avoid duplicating in each row)
22    column_names: Option<std::sync::Arc<Vec<String>>>,
23}
24
25impl ResultSet {
26    /// Create a new result set with a known capacity
27    ///
28    /// # Arguments
29    ///
30    /// * `capacity` - The initial capacity for the result rows
31    ///
32    /// # Returns
33    ///
34    /// A new `ResultSet` instance with preallocated capacity
35    #[must_use]
36    pub fn with_capacity(capacity: usize) -> ResultSet {
37        ResultSet {
38            results: Vec::with_capacity(capacity),
39            rows_affected: 0,
40            column_names: None,
41        }
42    }
43
44    /// Set the column names for this result set (to be shared by all rows)
45    pub fn set_column_names(&mut self, column_names: std::sync::Arc<Vec<String>>) {
46        self.column_names = Some(column_names);
47    }
48
49    /// Get the column names for this result set
50    #[must_use]
51    pub fn get_column_names(&self) -> Option<&std::sync::Arc<Vec<String>>> {
52        self.column_names.as_ref()
53    }
54
55    /// Add a row to the result set
56    ///
57    /// # Arguments
58    ///
59    /// * `row_values` - The values for this row
60    pub fn add_row_values(&mut self, row_values: Vec<RowValues>) {
61        if let Some(column_names) = &self.column_names {
62            // Build a cache of column name to index for faster lookups
63            // We only need to build this cache once and reuse it
64            static CACHE_MAP: ColumnCacheMap =
65                std::sync::LazyLock::new(
66                    || std::sync::Mutex::new(std::collections::HashMap::new()),
67                );
68
69            // Use the pointer to column_names as a key for the cache
70            let ptr = column_names.as_ref().as_ptr() as usize;
71            let cache = {
72                let mut cache_map = match CACHE_MAP.lock() {
73                    Ok(guard) => guard,
74                    Err(poisoned) => {
75                        // Clear the poison and continue with the recovered data
76                        poisoned.into_inner()
77                    }
78                };
79                let cache_entry = cache_map.entry(ptr).or_insert_with(|| {
80                    std::sync::Arc::new(
81                        column_names
82                            .iter()
83                            .enumerate()
84                            .map(|(i, name)| (name.clone(), i))
85                            .collect::<std::collections::HashMap<_, _>>(),
86                    )
87                });
88                cache_entry.clone()
89            };
90
91            let row = CustomDbRow {
92                column_names: column_names.clone(),
93                rows: row_values,
94                column_index_cache: cache,
95            };
96
97            self.results.push(row);
98            self.rows_affected += 1;
99        }
100    }
101
102    /// Add a row to the result set (legacy method, less efficient)
103    ///
104    /// # Arguments
105    ///
106    /// * `row` - The row to add
107    pub fn add_row(&mut self, row: CustomDbRow) {
108        // If column names haven't been set yet, use the ones from this row
109        if self.column_names.is_none() {
110            self.column_names = Some(row.column_names.clone());
111        }
112
113        self.results.push(row);
114        self.rows_affected += 1;
115    }
116
117    /// Return the first row, if one was returned.
118    #[must_use]
119    pub fn into_optional(mut self) -> Option<CustomDbRow> {
120        if self.results.is_empty() {
121            None
122        } else {
123            Some(self.results.remove(0))
124        }
125    }
126
127    /// Return the first row or an error when the result set is empty.
128    ///
129    /// # Errors
130    /// Returns [`SqlMiddlewareDbError::ExecutionError`] if no row was returned.
131    pub fn into_one(self) -> Result<CustomDbRow, SqlMiddlewareDbError> {
132        self.into_optional().ok_or_else(|| {
133            SqlMiddlewareDbError::ExecutionError("query returned no rows".to_string())
134        })
135    }
136
137    /// Map the first row, if one was returned.
138    ///
139    /// # Errors
140    /// Returns any error produced by the mapper.
141    pub fn map_optional<T, F>(self, mapper: F) -> Result<Option<T>, SqlMiddlewareDbError>
142    where
143        F: FnOnce(&CustomDbRow) -> Result<T, SqlMiddlewareDbError>,
144    {
145        self.into_optional().map(|row| mapper(&row)).transpose()
146    }
147
148    /// Map the first row or return an error when the result set is empty.
149    ///
150    /// # Errors
151    /// Returns [`SqlMiddlewareDbError::ExecutionError`] if no row was returned, or any error
152    /// produced by the mapper.
153    pub fn map_one<T, F>(self, mapper: F) -> Result<T, SqlMiddlewareDbError>
154    where
155        F: FnOnce(&CustomDbRow) -> Result<T, SqlMiddlewareDbError>,
156    {
157        let row = self.into_one()?;
158        mapper(&row)
159    }
160}