Skip to main content

uqa_sql/schema/
schema_remap.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Logical identity remapping over shared physical row slots.
8
9use std::collections::HashMap;
10
11use crate::ast::ColumnType;
12
13use super::{ColumnIdentity, RowSchema, SchemaBuildMetadata, NULL_SLOT};
14
15impl RowSchema {
16    /// Select explicit physical slots with fresh public identities. This is
17    /// the structural equivalent of executor target entries whose values may
18    /// come from anonymous computed slots rather than SQL-named columns.
19    pub fn remap_typed_physical_identities(
20        input: &Self,
21        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
22        aliases: &[(ColumnIdentity, usize, Option<ColumnType>)],
23    ) -> Self {
24        let mut collations = input.index.cold.collations.clone();
25        collations.clear_columns();
26        for (position, (_, _, source, ty)) in columns.iter().enumerate() {
27            collations.column(
28                position,
29                input.inherited_physical_collation(*source),
30                ty.as_ref(),
31            );
32        }
33        let output_names = columns
34            .iter()
35            .map(|(output, _, _, _)| output.clone())
36            .collect();
37        let identities = columns
38            .iter()
39            .map(|(_, identity, _, _)| identity.clone())
40            .collect();
41        let slots = columns
42            .iter()
43            .map(|(_, _, physical, _)| {
44                if *physical < input.physical_width() {
45                    *physical
46                } else {
47                    NULL_SLOT
48                }
49            })
50            .collect();
51        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
52        let wildcard_hidden = columns
53            .iter()
54            .enumerate()
55            .filter_map(|(output, (_, _, physical, _))| {
56                input
57                    .index
58                    .cold
59                    .wildcard_hidden
60                    .iter()
61                    .any(|logical| input.slot(*logical) == Some(*physical))
62                    .then_some(output)
63            })
64            .collect();
65        let mut lookup_aliases = input.index.aliases.clone();
66        let mut alias_types = input.index.cold.aliases.clone();
67        for (identity, physical, ty) in aliases {
68            collations.alias(
69                identity,
70                input.inherited_physical_collation(*physical),
71                ty.as_ref(),
72            );
73            lookup_aliases.insert(
74                identity.clone(),
75                if *physical < input.physical_width() {
76                    *physical
77                } else {
78                    NULL_SLOT
79                },
80            );
81            alias_types.insert(identity.clone(), ty.clone());
82        }
83        Self::from_typed_parts_with_aliases_and_exact_precedence(
84            output_names,
85            identities,
86            types,
87            slots,
88            input.physical_width(),
89            SchemaBuildMetadata {
90                collations,
91                record_fields: input.index.cold.record_fields.clone(),
92                aliases: lookup_aliases,
93                alias_types,
94                internal: input.index.executor_attributes.clone(),
95                internal_types: input.index.cold.executor_attribute_types.clone(),
96                score_sources: input.index.cold.score_sources.clone(),
97                wildcard_hidden,
98                binding_only: input.index.cold.binding_only.clone(),
99                row_types: input.index.cold.row_types.clone(),
100                open_qualifiers: input.index.cold.open_qualifiers.clone(),
101                ..SchemaBuildMetadata::default()
102            },
103        )
104    }
105
106    /// Select logical positions with explicit public labels, SQL identities, and types while preserving hidden aliases and physical fragments.
107    pub fn remap_typed_identities(
108        input: &Self,
109        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
110        aliases: &[(ColumnIdentity, usize)],
111    ) -> Self {
112        let mut collations = input.index.cold.collations.clone();
113        collations.clear_columns();
114        for (position, (_, _, source, ty)) in columns.iter().enumerate() {
115            collations.column(
116                position,
117                input.inherited_column_collation(*source),
118                ty.as_ref(),
119            );
120        }
121        let output_names = columns
122            .iter()
123            .map(|(output, _, _, _)| output.clone())
124            .collect();
125        let identities = columns
126            .iter()
127            .map(|(_, identity, _, _)| identity.clone())
128            .collect();
129        let slots = columns
130            .iter()
131            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
132            .collect();
133        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
134        let wildcard_hidden = columns
135            .iter()
136            .enumerate()
137            .filter_map(|(output, (_, _, logical, _))| {
138                input
139                    .index
140                    .cold
141                    .wildcard_hidden
142                    .contains(logical)
143                    .then_some(output)
144            })
145            .collect();
146        let mut lookup_aliases = input.index.aliases.clone();
147        let mut alias_types = input.index.cold.aliases.clone();
148        for (identity, logical) in aliases {
149            collations.alias(
150                identity,
151                input.inherited_column_collation(*logical),
152                input.column_type(*logical),
153            );
154            lookup_aliases.insert(identity.clone(), input.slot(*logical).unwrap_or(NULL_SLOT));
155            alias_types.insert(identity.clone(), input.column_type(*logical).cloned());
156        }
157        Self::from_typed_parts_with_aliases_and_exact_precedence(
158            output_names,
159            identities,
160            types,
161            slots,
162            input.physical_width(),
163            SchemaBuildMetadata {
164                collations,
165                record_fields: input.index.cold.record_fields.clone(),
166                aliases: lookup_aliases,
167                alias_types,
168                internal: input.index.executor_attributes.clone(),
169                internal_types: input.index.cold.executor_attribute_types.clone(),
170                score_sources: input.index.cold.score_sources.clone(),
171                wildcard_hidden,
172                binding_only: input.index.cold.binding_only.clone(),
173                row_types: input.index.cold.row_types.clone(),
174                open_qualifiers: input.index.cold.open_qualifiers.clone(),
175                ..SchemaBuildMetadata::default()
176            },
177        )
178    }
179
180    /// Select logical positions with fresh SQL identities while deliberately dropping every input lookup alias. A relation alias on a complete parenthesized JOIN uses this boundary because `PostgreSQL` hides all names exposed by the joined inputs.
181    pub fn remap_typed_identities_without_input_aliases(
182        input: &Self,
183        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
184    ) -> Self {
185        let mut collations = input.index.cold.collations.clone();
186        collations.clear_columns();
187        for (position, (_, _, source, ty)) in columns.iter().enumerate() {
188            collations.column(
189                position,
190                input.inherited_column_collation(*source),
191                ty.as_ref(),
192            );
193        }
194        let output_names = columns
195            .iter()
196            .map(|(output, _, _, _)| output.clone())
197            .collect();
198        let identities = columns
199            .iter()
200            .map(|(_, identity, _, _)| identity.clone())
201            .collect();
202        let slots = columns
203            .iter()
204            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
205            .collect();
206        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
207        let wildcard_hidden = columns
208            .iter()
209            .enumerate()
210            .filter_map(|(output, (_, _, logical, _))| {
211                input
212                    .index
213                    .cold
214                    .wildcard_hidden
215                    .contains(logical)
216                    .then_some(output)
217            })
218            .collect();
219        Self::from_typed_parts_with_aliases_and_exact_precedence(
220            output_names,
221            identities,
222            types,
223            slots,
224            input.physical_width(),
225            SchemaBuildMetadata {
226                collations,
227                record_fields: input.index.cold.record_fields.clone(),
228                aliases: HashMap::new(),
229                alias_types: HashMap::new(),
230                internal: input.index.executor_attributes.clone(),
231                internal_types: input.index.cold.executor_attribute_types.clone(),
232                score_sources: input.index.cold.score_sources.clone(),
233                wildcard_hidden,
234                binding_only: HashMap::new(),
235                ..SchemaBuildMetadata::default()
236            },
237        )
238    }
239}