1use std::collections::BTreeMap;
10
11use uqa_core::Value;
12
13use crate::ast::ColumnType;
14
15mod labels;
16mod text;
17pub use labels::render_result_enum_labels;
18pub use text::format_postgres_text;
19
20pub type ResultRow = BTreeMap<String, Value>;
21
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
24pub enum SQLResultKind {
25 #[default]
26 Command,
27 Rows,
28 Unknown,
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct SQLResult {
34 pub kind: SQLResultKind,
36 pub command_tag: Option<String>,
38 pub columns: Vec<String>,
40 pub column_types: Vec<Option<ColumnType>>,
44 pub rows: Vec<ResultRow>,
48 #[doc(hidden)]
52 pub positional_rows: Option<Vec<Vec<Value>>>,
53 pub affected_rows: u64,
55}
56
57impl SQLResult {
58 pub fn with_command_tag(mut self, tag: impl Into<String>) -> Self {
60 self.command_tag = Some(tag.into());
61 self
62 }
63
64 pub fn empty() -> Self {
65 Self::default()
66 }
67
68 pub fn from_rows(columns: Vec<String>, rows: Vec<ResultRow>) -> Self {
69 let column_types = vec![None; columns.len()];
70 Self {
71 kind: SQLResultKind::Rows,
72 command_tag: None,
73 columns,
74 column_types,
75 rows,
76 positional_rows: None,
77 affected_rows: 0,
78 }
79 }
80
81 pub fn from_rows_with_positions(
82 columns: Vec<String>,
83 rows: Vec<ResultRow>,
84 positional_rows: Option<Vec<Vec<Value>>>,
85 ) -> Self {
86 let column_types = vec![None; columns.len()];
87 Self::from_typed_rows_with_positions(columns, column_types, rows, positional_rows)
88 }
89
90 pub fn from_typed_rows_with_positions(
91 columns: Vec<String>,
92 column_types: Vec<Option<ColumnType>>,
93 mut rows: Vec<ResultRow>,
94 positional_rows: Option<Vec<Vec<Value>>>,
95 ) -> Self {
96 debug_assert_eq!(columns.len(), column_types.len());
97 debug_assert!(positional_rows.as_ref().is_none_or(|values| {
98 values.len() == rows.len() && values.iter().all(|row| row.len() == columns.len())
99 }));
100 if let Some(positional_rows) = positional_rows.as_ref() {
101 let compatibility_labels = unique_compatibility_labels(&columns);
102 for (row, positional) in rows.iter_mut().zip(positional_rows) {
103 for (label, value) in compatibility_labels.iter().zip(positional) {
104 row.insert(label.clone(), value.clone());
105 }
106 }
107 }
108 Self {
109 kind: SQLResultKind::Rows,
110 command_tag: None,
111 columns,
112 column_types,
113 rows,
114 positional_rows,
115 affected_rows: 0,
116 }
117 }
118
119 pub fn value_at(&self, row: usize, column: usize) -> Option<&Value> {
125 self.positional_rows
126 .as_ref()
127 .and_then(|rows| rows.get(row))
128 .and_then(|row| row.get(column))
129 .or_else(|| {
130 self.columns
131 .get(column)
132 .and_then(|name| self.rows.get(row)?.get(name))
133 })
134 }
135
136 pub fn from_affected(affected: u64) -> Self {
137 Self {
138 affected_rows: affected,
139 ..Self::default()
140 }
141 }
142}
143
144fn unique_compatibility_labels(columns: &[String]) -> Vec<String> {
145 let mut labels = Vec::with_capacity(columns.len());
146 for base in columns {
147 let mut label = base.clone();
148 let mut suffix = 1usize;
149 while labels.contains(&label) {
150 label = format!("{base}_{suffix}");
151 suffix += 1;
152 }
153 labels.push(label);
154 }
155 labels
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
163 fn repeated_postgresql_labels_keep_unique_named_compatibility_keys() {
164 let result = SQLResult::from_rows_with_positions(
165 vec!["value".into(), "value".into()],
166 vec![ResultRow::from([("value".into(), Value::Int(6))])],
167 Some(vec![vec![Value::Int(5), Value::Int(6)]]),
168 );
169
170 assert_eq!(result.columns, ["value", "value"]);
171 assert_eq!(result.rows[0].get("value"), Some(&Value::Int(5)));
172 assert_eq!(result.rows[0].get("value_1"), Some(&Value::Int(6)));
173 assert_eq!(result.value_at(0, 0), Some(&Value::Int(5)));
174 assert_eq!(result.value_at(0, 1), Some(&Value::Int(6)));
175 }
176}
177
178pub mod completion;
179pub mod vector;
180
181pub struct ExplainAnalysis {
183 pub vector_searches: vector::ExplainVectorSearches,
184 pub elapsed: std::time::Duration,
185 pub rows: u64,
186 pub affected_rows: u64,
187}
188
189#[derive(Debug, Clone, Default)]
191pub struct ExplainPhysicalPlan {
192 pub nodes: Vec<serde_json::Value>,
193}