Skip to main content

uqa_execution/batch/
owned_row.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Owned physical rows used by row-at-a-time consumers.
8
9use uqa_core::Value;
10use uqa_sql::expr::RowLookup;
11use uqa_sql::ResultRow;
12
13use crate::physical::{ExecError, ExecResult};
14
15use super::{PhysicalRow, PhysicalRowView, RowSchema};
16
17/// Owned schema/row pair for row-at-a-time consumers that must outlive a decoded batch. Cloning this carrier shares the immutable schema index and row fragments; it does not build a named row or clone contained values.
18#[derive(Debug, Clone, PartialEq)]
19pub struct OwnedPhysicalRow {
20    pub schema: RowSchema,
21    pub row: PhysicalRow,
22}
23
24impl OwnedPhysicalRow {
25    pub fn new(schema: RowSchema, row: PhysicalRow) -> Self {
26        Self { schema, row }
27    }
28
29    pub fn view(&self) -> PhysicalRowView<'_> {
30        self.schema.view(&self.row)
31    }
32
33    pub fn get(&self, name: &str) -> Option<&Value> {
34        self.schema
35            .exact_slot(name)
36            .and_then(|slot| self.row.value(slot))
37    }
38
39    /// Apply a new logical schema by position while sharing the existing value fragments. Relation aliases and derived-column names therefore do not require an intermediate named row.
40    pub fn relabel(self, schema: RowSchema) -> ExecResult<Self> {
41        if self.schema.len() != schema.len() {
42            return Err(ExecError::Other(format!(
43                "cannot relabel {} columns as {} columns",
44                self.schema.len(),
45                schema.len()
46            )));
47        }
48        let slots = self.schema.index.slots.to_vec();
49        Ok(Self::new(schema, self.row.project_slots(&slots)))
50    }
51
52    pub fn into_result_row(self) -> ResultRow {
53        self.schema.materialize_result_row(self.row)
54    }
55}
56
57impl RowLookup for OwnedPhysicalRow {
58    fn column(&self, name: &str) -> Option<&Value> {
59        self.schema
60            .column_slot(name)
61            .and_then(|slot| self.row.value(slot))
62    }
63
64    fn column_is_ambiguous(&self, name: &str) -> bool {
65        self.schema.column_is_ambiguous(name)
66    }
67
68    fn qualified_column(&self, qualifier: &str, column: &str) -> Option<&Value> {
69        self.schema
70            .qualified_slot(qualifier, column)
71            .and_then(|slot| self.row.value(slot))
72    }
73
74    fn qualified_column_is_ambiguous(&self, qualifier: &str, column: &str) -> bool {
75        self.schema.qualified_column_is_ambiguous(qualifier, column)
76    }
77
78    fn positional_column(&self, index: usize) -> Option<&Value> {
79        self.schema
80            .slot(index)
81            .and_then(|slot| self.row.value(slot))
82    }
83
84    fn visit_columns(&self, visitor: &mut dyn FnMut(&str, &Value)) {
85        self.view().visit_columns(visitor);
86    }
87}