use std::collections::BTreeMap;
use uqa_core::Value;
use crate::ast::ColumnType;
use crate::RowSchema;
mod labels;
mod text;
pub use labels::render_result_enum_labels;
pub use text::{format_postgres_text, format_postgres_text_with_catalog};
pub type ResultRow = BTreeMap<String, Value>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SQLResultKind {
#[default]
Command,
Rows,
Unknown,
}
#[derive(Debug, Clone, Default)]
pub struct SQLResult {
pub kind: SQLResultKind,
pub command_tag: Option<String>,
pub columns: Vec<String>,
pub column_types: Vec<Option<ColumnType>>,
pub row_schema: Option<RowSchema>,
pub rows: Vec<ResultRow>,
#[doc(hidden)]
pub positional_rows: Option<Vec<Vec<Value>>>,
pub affected_rows: u64,
}
impl SQLResult {
pub fn with_command_tag(mut self, tag: impl Into<String>) -> Self {
self.command_tag = Some(tag.into());
self
}
pub fn empty() -> Self {
Self::default()
}
pub fn from_rows(columns: Vec<String>, rows: Vec<ResultRow>) -> Self {
let column_types = vec![None; columns.len()];
Self {
kind: SQLResultKind::Rows,
command_tag: None,
columns,
column_types,
row_schema: None,
rows,
positional_rows: None,
affected_rows: 0,
}
}
pub fn from_rows_with_positions(
columns: Vec<String>,
rows: Vec<ResultRow>,
positional_rows: Option<Vec<Vec<Value>>>,
) -> Self {
let column_types = vec![None; columns.len()];
Self::from_typed_rows_with_positions(columns, column_types, rows, positional_rows)
}
pub fn from_typed_rows_with_positions(
columns: Vec<String>,
column_types: Vec<Option<ColumnType>>,
mut rows: Vec<ResultRow>,
positional_rows: Option<Vec<Vec<Value>>>,
) -> Self {
debug_assert_eq!(columns.len(), column_types.len());
debug_assert!(positional_rows.as_ref().is_none_or(|values| {
values.len() == rows.len() && values.iter().all(|row| row.len() == columns.len())
}));
if let Some(positional_rows) = positional_rows.as_ref() {
let compatibility_labels = unique_compatibility_labels(&columns);
for (row, positional) in rows.iter_mut().zip(positional_rows) {
for (label, value) in compatibility_labels.iter().zip(positional) {
row.insert(label.clone(), value.clone());
}
}
}
Self {
kind: SQLResultKind::Rows,
command_tag: None,
columns,
column_types,
row_schema: None,
rows,
positional_rows,
affected_rows: 0,
}
}
pub fn from_schema_rows_with_positions(
schema: RowSchema,
rows: Vec<ResultRow>,
positional_rows: Option<Vec<Vec<Value>>>,
) -> Self {
let mut result = Self::from_typed_rows_with_positions(
schema.columns().to_vec(),
schema.column_types().to_vec(),
rows,
positional_rows,
);
result.row_schema = Some(schema);
result
}
pub fn row_schema(&self) -> std::borrow::Cow<'_, RowSchema> {
self.row_schema.as_ref().map_or_else(
|| {
std::borrow::Cow::Owned(RowSchema::with_types(
self.columns.clone(),
self.column_types.clone(),
))
},
std::borrow::Cow::Borrowed,
)
}
pub fn value_at(&self, row: usize, column: usize) -> Option<&Value> {
self.positional_rows
.as_ref()
.and_then(|rows| rows.get(row))
.and_then(|row| row.get(column))
.or_else(|| {
self.columns
.get(column)
.and_then(|name| self.rows.get(row)?.get(name))
})
}
pub fn from_affected(affected: u64) -> Self {
Self {
affected_rows: affected,
..Self::default()
}
}
}
fn unique_compatibility_labels(columns: &[String]) -> Vec<String> {
let mut labels = Vec::with_capacity(columns.len());
for base in columns {
let mut label = base.clone();
let mut suffix = 1usize;
while labels.contains(&label) {
label = format!("{base}_{suffix}");
suffix += 1;
}
labels.push(label);
}
labels
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn result_metadata_survives_empty_rows_and_shared_result_clones() {
let schema = RowSchema::with_column_metadata(
None,
[
("value".into(), Some(ColumnType::Text), Some(950)),
("value".into(), Some(ColumnType::Text), Some(951)),
],
);
for rows in [Vec::new(), vec![vec![Value::Null, Value::Null]]] {
let result = SQLResult::from_schema_rows_with_positions(
schema.clone(),
vec![ResultRow::new(); rows.len()],
Some(rows),
);
let retained = result.row_schema();
assert!(std::ptr::eq(retained.columns(), schema.columns()));
assert!(matches!(retained, std::borrow::Cow::Borrowed(_)));
assert!(std::ptr::eq(
retained.as_ref(),
result.row_schema.as_ref().unwrap()
));
assert_eq!(result.kind, SQLResultKind::Rows);
assert_eq!(retained.column_collation(0), Some(950));
assert_eq!(retained.column_collation(1), Some(951));
assert_eq!(*result.clone().row_schema(), schema);
}
}
#[test]
fn repeated_postgresql_labels_keep_unique_named_compatibility_keys() {
let result = SQLResult::from_rows_with_positions(
vec!["value".into(), "value".into()],
vec![ResultRow::from([("value".into(), Value::Int(6))])],
Some(vec![vec![Value::Int(5), Value::Int(6)]]),
);
assert_eq!(result.columns, ["value", "value"]);
assert_eq!(result.rows[0].get("value"), Some(&Value::Int(5)));
assert_eq!(result.rows[0].get("value_1"), Some(&Value::Int(6)));
assert_eq!(result.value_at(0, 0), Some(&Value::Int(5)));
assert_eq!(result.value_at(0, 1), Some(&Value::Int(6)));
}
}
pub mod completion;
pub mod vector;
pub struct ExplainAnalysis {
pub vector_searches: vector::ExplainVectorSearches,
pub elapsed: std::time::Duration,
pub rows: u64,
pub affected_rows: u64,
}
#[derive(Debug, Clone, Default)]
pub struct ExplainPhysicalPlan {
pub nodes: Vec<serde_json::Value>,
}