Skip to main content

uqa_execution/
batch.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Schema-bound, allocation-light physical rows and batches.
8//!
9//! Column names belong to [`RowSchema`], not to every row. A physical row is
10//! made from shared value fragments. Joins concatenate fragment handles while
11//! schemas remap `(qualifier, column)` identities to physical slots; neither
12//! operation rebuilds a string-keyed map or clones the contained values.
13
14use std::collections::{HashMap, HashSet};
15use std::sync::Arc;
16
17use smallvec::SmallVec;
18use uqa_core::Value;
19use uqa_sql::ast::{ColumnType, InternalColumnRef};
20use uqa_sql::expr::RowLookup;
21use uqa_sql::ResultRow;
22
23use crate::physical::{ExecError, ExecResult};
24
25mod batches;
26mod materialization;
27mod name_binding;
28mod outer_scope;
29mod owned_row;
30mod physical_row;
31mod physical_row_view;
32mod row_lock_origins;
33mod schema_composition;
34mod schema_construction;
35mod schema_layout;
36mod schema_projection;
37mod schema_remap;
38
39pub use batches::Batch;
40pub use owned_row::OwnedPhysicalRow;
41use physical_row::RowFragment;
42pub use physical_row::{PhysicalRow, RowProjectionValue};
43pub use physical_row_view::PhysicalRowView;
44use row_lock_origins::concat_lock_origins;
45pub use row_lock_origins::RowLockOrigin;
46
47#[cfg(test)]
48mod tests;
49
50/// Default rows-per-batch hint.
51pub const DEFAULT_BATCH_SIZE: usize = 1024;
52
53const NULL_SLOT: usize = usize::MAX;
54/// Keep the optional row-lock lineage pointer inside the pre-lineage 64-bit row footprint while retaining seven allocation-free join/projection fragments.
55const INLINE_ROW_FRAGMENTS: usize = 7;
56static NULL_VALUE: Value = Value::Null;
57
58/// Structured SQL column identity. A qualifier is metadata, never a prefix encoded into the column name, so quoted names containing `.` remain intact.
59#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
60pub struct ColumnIdentity {
61    qualifier: Option<Box<str>>,
62    column: Box<str>,
63}
64
65/// One score-bearing relation carried through the executor under an opaque internal attribute. The optional qualifier is SQL namespace metadata; the score value itself is never addressed by a magic SQL column name.
66#[derive(Debug, Clone, PartialEq, Eq)]
67struct ScoreSource {
68    qualifier: Option<Box<str>>,
69    column: InternalColumnRef,
70}
71
72impl ColumnIdentity {
73    #[must_use]
74    pub fn unqualified(column: impl Into<String>) -> Self {
75        Self {
76            qualifier: None,
77            column: Box::<str>::from(column.into()),
78        }
79    }
80
81    #[must_use]
82    pub fn qualified(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
83        Self {
84            qualifier: Some(Box::<str>::from(qualifier.into())),
85            column: Box::<str>::from(column.into()),
86        }
87    }
88
89    #[must_use]
90    pub fn qualifier(&self) -> Option<&str> {
91        self.qualifier.as_deref()
92    }
93
94    #[must_use]
95    pub fn column(&self) -> &str {
96        &self.column
97    }
98}
99
100#[derive(Debug, PartialEq, Eq)]
101struct SchemaIndex {
102    /// Public/materialized output labels in logical order.
103    columns: Box<[String]>,
104    /// SQL lookup identities aligned with `columns`.
105    identities: Box<[ColumnIdentity]>,
106    /// Logical column position -> flattened physical value position.
107    slots: Box<[usize]>,
108    physical_width: usize,
109    /// Structural lookup by physical/public label. SQL name binding uses `unqualified` or `qualified`, never this map.
110    exact: HashMap<Box<str>, usize>,
111    unqualified: HashMap<Box<str>, usize>,
112    qualified: HashMap<ColumnIdentity, usize>,
113    /// Additional lookup identities that point directly at an existing physical slot without becoming output columns. Correlated table aliases use this to expose `(alias, column)` without duplicating the value.
114    aliases: HashMap<ColumnIdentity, usize>,
115    /// Executor-only relation/attribute identities mapped directly to physical
116    /// slots. These never participate in SQL name lookup or wildcard output.
117    executor_attributes: HashMap<InternalColumnRef, usize>,
118    /// Visible unqualified names with more than one logical owner.
119    ambiguous_unqualified: HashSet<Box<str>>,
120    /// Visible qualified identities with more than one logical owner.
121    ambiguous_qualified: HashSet<ColumnIdentity>,
122    /// Static type metadata stays behind a cold pointer so declared SQL identities do not enlarge or displace the cache-hot row lookup fields above.
123    cold: Box<SchemaColdMetadata>,
124}
125
126#[derive(Debug, PartialEq, Eq)]
127struct SchemaColdMetadata {
128    /// `None` is an as-yet unresolved type, not a runtime NULL value.
129    columns: Box<[Option<ColumnType>]>,
130    aliases: HashMap<ColumnIdentity, Option<ColumnType>>,
131    executor_attribute_types: HashMap<InternalColumnRef, Option<ColumnType>>,
132    score_sources: Vec<ScoreSource>,
133    /// Logical attributes omitted from unqualified and qualified wildcard
134    /// expansion. Explicit references and projections remain ordinary SQL
135    /// columns; only the source-owned metadata positions are hidden.
136    wildcard_hidden: HashSet<usize>,
137    /// Static name-binding identities with no runtime slot. Unlike aliases,
138    /// these are never part of qualified wildcard expansion or spill layout.
139    binding_only: HashMap<ColumnIdentity, Option<ColumnType>>,
140    identity_layout: bool,
141}
142
143#[derive(Default)]
144struct SchemaBuildMetadata {
145    aliases: HashMap<ColumnIdentity, usize>,
146    alias_types: HashMap<ColumnIdentity, Option<ColumnType>>,
147    internal: HashMap<InternalColumnRef, usize>,
148    internal_types: HashMap<InternalColumnRef, Option<ColumnType>>,
149    score_sources: Vec<ScoreSource>,
150    wildcard_hidden: HashSet<usize>,
151    binding_only: HashMap<ColumnIdentity, Option<ColumnType>>,
152    exact_unqualified_precedence: bool,
153    extra_ambiguous_unqualified: HashSet<Box<str>>,
154    extra_ambiguous_qualified: HashSet<ColumnIdentity>,
155}
156
157pub(crate) struct PhysicalLayout {
158    pub(crate) columns: Vec<String>,
159    pub(crate) identities: Vec<ColumnIdentity>,
160    pub(crate) types: Vec<Option<ColumnType>>,
161    pub(crate) slots: Vec<Option<usize>>,
162    pub(crate) physical_width: usize,
163    pub(crate) aliases: Vec<(ColumnIdentity, Option<usize>, Option<ColumnType>)>,
164    pub(crate) internal: Vec<(InternalColumnRef, Option<usize>, Option<ColumnType>)>,
165    pub(crate) score_sources: Vec<(Option<String>, InternalColumnRef)>,
166    pub(crate) wildcard_hidden: HashSet<usize>,
167}
168
169/// Immutable column layout shared by an operator and all of its batches.
170///
171/// `columns` are the logical output labels. `slots` may point into a wider
172/// composite physical row after a projection/rename, allowing those operators
173/// to change row shape without moving any values.
174#[derive(Debug, Clone, PartialEq, Eq)]
175pub struct RowSchema {
176    index: Arc<SchemaIndex>,
177}
178
179/// Physical source of one scalar-projection output. Direct input slots stay in the child row; only computed values extend its physical layout.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181pub(crate) enum ProjectedSlot {
182    Input(Option<usize>),
183    Computed(usize),
184}