1use std::collections::BTreeMap;
10
11use uqa_core::Value;
12
13use crate::ast::ColumnType;
14use crate::RowSchema;
15
16mod labels;
17mod text;
18pub use labels::render_result_enum_labels;
19pub use text::{format_postgres_text, format_postgres_text_with_catalog};
20
21pub type ResultRow = BTreeMap<String, Value>;
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub enum SQLResultKind {
26 #[default]
27 Command,
28 Rows,
29 Unknown,
31}
32
33#[derive(Debug, Clone, Default)]
34pub struct SQLResult {
35 pub kind: SQLResultKind,
37 pub command_tag: Option<String>,
39 pub columns: Vec<String>,
41 pub column_types: Vec<Option<ColumnType>>,
45 pub row_schema: Option<RowSchema>,
47 pub rows: Vec<ResultRow>,
51 #[doc(hidden)]
55 pub positional_rows: Option<Vec<Vec<Value>>>,
56 pub affected_rows: u64,
58}
59
60impl SQLResult {
61 pub fn with_command_tag(mut self, tag: impl Into<String>) -> Self {
63 self.command_tag = Some(tag.into());
64 self
65 }
66
67 pub fn empty() -> Self {
68 Self::default()
69 }
70
71 pub fn from_rows(columns: Vec<String>, rows: Vec<ResultRow>) -> Self {
72 let column_types = vec![None; columns.len()];
73 Self {
74 kind: SQLResultKind::Rows,
75 command_tag: None,
76 columns,
77 column_types,
78 row_schema: None,
79 rows,
80 positional_rows: None,
81 affected_rows: 0,
82 }
83 }
84
85 pub fn from_rows_with_positions(
86 columns: Vec<String>,
87 rows: Vec<ResultRow>,
88 positional_rows: Option<Vec<Vec<Value>>>,
89 ) -> Self {
90 let column_types = vec![None; columns.len()];
91 Self::from_typed_rows_with_positions(columns, column_types, rows, positional_rows)
92 }
93
94 pub fn from_typed_rows_with_positions(
95 columns: Vec<String>,
96 column_types: Vec<Option<ColumnType>>,
97 mut rows: Vec<ResultRow>,
98 positional_rows: Option<Vec<Vec<Value>>>,
99 ) -> Self {
100 debug_assert_eq!(columns.len(), column_types.len());
101 debug_assert!(positional_rows.as_ref().is_none_or(|values| {
102 values.len() == rows.len() && values.iter().all(|row| row.len() == columns.len())
103 }));
104 if let Some(positional_rows) = positional_rows.as_ref() {
105 let compatibility_labels = unique_compatibility_labels(&columns);
106 for (row, positional) in rows.iter_mut().zip(positional_rows) {
107 for (label, value) in compatibility_labels.iter().zip(positional) {
108 row.insert(label.clone(), value.clone());
109 }
110 }
111 }
112 Self {
113 kind: SQLResultKind::Rows,
114 command_tag: None,
115 columns,
116 column_types,
117 row_schema: None,
118 rows,
119 positional_rows,
120 affected_rows: 0,
121 }
122 }
123
124 pub fn from_schema_rows_with_positions(
126 schema: RowSchema,
127 rows: Vec<ResultRow>,
128 positional_rows: Option<Vec<Vec<Value>>>,
129 ) -> Self {
130 let mut result = Self::from_typed_rows_with_positions(
131 schema.columns().to_vec(),
132 schema.column_types().to_vec(),
133 rows,
134 positional_rows,
135 );
136 result.row_schema = Some(schema);
137 result
138 }
139
140 pub fn row_schema(&self) -> std::borrow::Cow<'_, RowSchema> {
142 self.row_schema.as_ref().map_or_else(
143 || {
144 std::borrow::Cow::Owned(RowSchema::with_types(
145 self.columns.clone(),
146 self.column_types.clone(),
147 ))
148 },
149 std::borrow::Cow::Borrowed,
150 )
151 }
152
153 pub fn value_at(&self, row: usize, column: usize) -> Option<&Value> {
159 self.positional_rows
160 .as_ref()
161 .and_then(|rows| rows.get(row))
162 .and_then(|row| row.get(column))
163 .or_else(|| {
164 self.columns
165 .get(column)
166 .and_then(|name| self.rows.get(row)?.get(name))
167 })
168 }
169
170 pub fn from_affected(affected: u64) -> Self {
171 Self {
172 affected_rows: affected,
173 ..Self::default()
174 }
175 }
176}
177
178fn unique_compatibility_labels(columns: &[String]) -> Vec<String> {
179 let mut labels = Vec::with_capacity(columns.len());
180 for base in columns {
181 let mut label = base.clone();
182 let mut suffix = 1usize;
183 while labels.contains(&label) {
184 label = format!("{base}_{suffix}");
185 suffix += 1;
186 }
187 labels.push(label);
188 }
189 labels
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn result_metadata_survives_empty_rows_and_shared_result_clones() {
198 let schema = RowSchema::with_column_metadata(
199 None,
200 [
201 ("value".into(), Some(ColumnType::Text), Some(950)),
202 ("value".into(), Some(ColumnType::Text), Some(951)),
203 ],
204 );
205 for rows in [Vec::new(), vec![vec![Value::Null, Value::Null]]] {
206 let result = SQLResult::from_schema_rows_with_positions(
207 schema.clone(),
208 vec![ResultRow::new(); rows.len()],
209 Some(rows),
210 );
211 let retained = result.row_schema();
212 assert!(std::ptr::eq(retained.columns(), schema.columns()));
213 assert!(matches!(retained, std::borrow::Cow::Borrowed(_)));
214 assert!(std::ptr::eq(
215 retained.as_ref(),
216 result.row_schema.as_ref().unwrap()
217 ));
218 assert_eq!(result.kind, SQLResultKind::Rows);
219 assert_eq!(retained.column_collation(0), Some(950));
220 assert_eq!(retained.column_collation(1), Some(951));
221 assert_eq!(*result.clone().row_schema(), schema);
222 }
223 }
224
225 #[test]
226 fn repeated_postgresql_labels_keep_unique_named_compatibility_keys() {
227 let result = SQLResult::from_rows_with_positions(
228 vec!["value".into(), "value".into()],
229 vec![ResultRow::from([("value".into(), Value::Int(6))])],
230 Some(vec![vec![Value::Int(5), Value::Int(6)]]),
231 );
232
233 assert_eq!(result.columns, ["value", "value"]);
234 assert_eq!(result.rows[0].get("value"), Some(&Value::Int(5)));
235 assert_eq!(result.rows[0].get("value_1"), Some(&Value::Int(6)));
236 assert_eq!(result.value_at(0, 0), Some(&Value::Int(5)));
237 assert_eq!(result.value_at(0, 1), Some(&Value::Int(6)));
238 }
239}
240
241pub mod completion;
242pub mod vector;
243
244pub struct ExplainAnalysis {
246 pub vector_searches: vector::ExplainVectorSearches,
247 pub elapsed: std::time::Duration,
248 pub rows: u64,
249 pub affected_rows: u64,
250}
251
252#[derive(Debug, Clone, Default)]
254pub struct ExplainPhysicalPlan {
255 pub nodes: Vec<serde_json::Value>,
256}