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 output_names = columns
25            .iter()
26            .map(|(output, _, _, _)| output.clone())
27            .collect();
28        let identities = columns
29            .iter()
30            .map(|(_, identity, _, _)| identity.clone())
31            .collect();
32        let slots = columns
33            .iter()
34            .map(|(_, _, physical, _)| {
35                if *physical < input.physical_width() {
36                    *physical
37                } else {
38                    NULL_SLOT
39                }
40            })
41            .collect();
42        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
43        let wildcard_hidden = columns
44            .iter()
45            .enumerate()
46            .filter_map(|(output, (_, _, physical, _))| {
47                input
48                    .index
49                    .cold
50                    .wildcard_hidden
51                    .iter()
52                    .any(|logical| input.slot(*logical) == Some(*physical))
53                    .then_some(output)
54            })
55            .collect();
56        let mut lookup_aliases = input.index.aliases.clone();
57        let mut alias_types = input.index.cold.aliases.clone();
58        for (identity, physical, ty) in aliases {
59            lookup_aliases.insert(
60                identity.clone(),
61                if *physical < input.physical_width() {
62                    *physical
63                } else {
64                    NULL_SLOT
65                },
66            );
67            alias_types.insert(identity.clone(), ty.clone());
68        }
69        Self::from_typed_parts_with_aliases_and_exact_precedence(
70            output_names,
71            identities,
72            types,
73            slots,
74            input.physical_width(),
75            SchemaBuildMetadata {
76                record_fields: input.index.cold.record_fields.clone(),
77                aliases: lookup_aliases,
78                alias_types,
79                internal: input.index.executor_attributes.clone(),
80                internal_types: input.index.cold.executor_attribute_types.clone(),
81                score_sources: input.index.cold.score_sources.clone(),
82                wildcard_hidden,
83                binding_only: input.index.cold.binding_only.clone(),
84                open_qualifiers: input.index.cold.open_qualifiers.clone(),
85                ..SchemaBuildMetadata::default()
86            },
87        )
88    }
89
90    /// Select logical positions with explicit public labels, SQL identities, and types while preserving hidden aliases and physical fragments.
91    pub fn remap_typed_identities(
92        input: &Self,
93        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
94        aliases: &[(ColumnIdentity, usize)],
95    ) -> Self {
96        let output_names = columns
97            .iter()
98            .map(|(output, _, _, _)| output.clone())
99            .collect();
100        let identities = columns
101            .iter()
102            .map(|(_, identity, _, _)| identity.clone())
103            .collect();
104        let slots = columns
105            .iter()
106            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
107            .collect();
108        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
109        let wildcard_hidden = columns
110            .iter()
111            .enumerate()
112            .filter_map(|(output, (_, _, logical, _))| {
113                input
114                    .index
115                    .cold
116                    .wildcard_hidden
117                    .contains(logical)
118                    .then_some(output)
119            })
120            .collect();
121        let mut lookup_aliases = input.index.aliases.clone();
122        let mut alias_types = input.index.cold.aliases.clone();
123        for (identity, logical) in aliases {
124            lookup_aliases.insert(identity.clone(), input.slot(*logical).unwrap_or(NULL_SLOT));
125            alias_types.insert(identity.clone(), input.column_type(*logical).cloned());
126        }
127        Self::from_typed_parts_with_aliases_and_exact_precedence(
128            output_names,
129            identities,
130            types,
131            slots,
132            input.physical_width(),
133            SchemaBuildMetadata {
134                record_fields: input.index.cold.record_fields.clone(),
135                aliases: lookup_aliases,
136                alias_types,
137                internal: input.index.executor_attributes.clone(),
138                internal_types: input.index.cold.executor_attribute_types.clone(),
139                score_sources: input.index.cold.score_sources.clone(),
140                wildcard_hidden,
141                binding_only: input.index.cold.binding_only.clone(),
142                open_qualifiers: input.index.cold.open_qualifiers.clone(),
143                ..SchemaBuildMetadata::default()
144            },
145        )
146    }
147
148    /// 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.
149    pub fn remap_typed_identities_without_input_aliases(
150        input: &Self,
151        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
152    ) -> Self {
153        let output_names = columns
154            .iter()
155            .map(|(output, _, _, _)| output.clone())
156            .collect();
157        let identities = columns
158            .iter()
159            .map(|(_, identity, _, _)| identity.clone())
160            .collect();
161        let slots = columns
162            .iter()
163            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
164            .collect();
165        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
166        let wildcard_hidden = columns
167            .iter()
168            .enumerate()
169            .filter_map(|(output, (_, _, logical, _))| {
170                input
171                    .index
172                    .cold
173                    .wildcard_hidden
174                    .contains(logical)
175                    .then_some(output)
176            })
177            .collect();
178        Self::from_typed_parts_with_aliases_and_exact_precedence(
179            output_names,
180            identities,
181            types,
182            slots,
183            input.physical_width(),
184            SchemaBuildMetadata {
185                record_fields: input.index.cold.record_fields.clone(),
186                aliases: HashMap::new(),
187                alias_types: HashMap::new(),
188                internal: input.index.executor_attributes.clone(),
189                internal_types: input.index.cold.executor_attribute_types.clone(),
190                score_sources: input.index.cold.score_sources.clone(),
191                wildcard_hidden,
192                binding_only: HashMap::new(),
193                ..SchemaBuildMetadata::default()
194            },
195        )
196    }
197}