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                aliases: lookup_aliases,
77                alias_types,
78                internal: input.index.executor_attributes.clone(),
79                internal_types: input.index.cold.executor_attribute_types.clone(),
80                score_sources: input.index.cold.score_sources.clone(),
81                wildcard_hidden,
82                binding_only: input.index.cold.binding_only.clone(),
83                open_qualifiers: input.index.cold.open_qualifiers.clone(),
84                ..SchemaBuildMetadata::default()
85            },
86        )
87    }
88
89    /// Select logical positions with explicit public labels, SQL identities, and types while preserving hidden aliases and physical fragments.
90    pub fn remap_typed_identities(
91        input: &Self,
92        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
93        aliases: &[(ColumnIdentity, usize)],
94    ) -> Self {
95        let output_names = columns
96            .iter()
97            .map(|(output, _, _, _)| output.clone())
98            .collect();
99        let identities = columns
100            .iter()
101            .map(|(_, identity, _, _)| identity.clone())
102            .collect();
103        let slots = columns
104            .iter()
105            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
106            .collect();
107        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
108        let wildcard_hidden = columns
109            .iter()
110            .enumerate()
111            .filter_map(|(output, (_, _, logical, _))| {
112                input
113                    .index
114                    .cold
115                    .wildcard_hidden
116                    .contains(logical)
117                    .then_some(output)
118            })
119            .collect();
120        let mut lookup_aliases = input.index.aliases.clone();
121        let mut alias_types = input.index.cold.aliases.clone();
122        for (identity, logical) in aliases {
123            lookup_aliases.insert(identity.clone(), input.slot(*logical).unwrap_or(NULL_SLOT));
124            alias_types.insert(identity.clone(), input.column_type(*logical).cloned());
125        }
126        Self::from_typed_parts_with_aliases_and_exact_precedence(
127            output_names,
128            identities,
129            types,
130            slots,
131            input.physical_width(),
132            SchemaBuildMetadata {
133                aliases: lookup_aliases,
134                alias_types,
135                internal: input.index.executor_attributes.clone(),
136                internal_types: input.index.cold.executor_attribute_types.clone(),
137                score_sources: input.index.cold.score_sources.clone(),
138                wildcard_hidden,
139                binding_only: input.index.cold.binding_only.clone(),
140                open_qualifiers: input.index.cold.open_qualifiers.clone(),
141                ..SchemaBuildMetadata::default()
142            },
143        )
144    }
145
146    /// 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.
147    pub fn remap_typed_identities_without_input_aliases(
148        input: &Self,
149        columns: &[(String, ColumnIdentity, usize, Option<ColumnType>)],
150    ) -> Self {
151        let output_names = columns
152            .iter()
153            .map(|(output, _, _, _)| output.clone())
154            .collect();
155        let identities = columns
156            .iter()
157            .map(|(_, identity, _, _)| identity.clone())
158            .collect();
159        let slots = columns
160            .iter()
161            .map(|(_, _, logical, _)| input.slot(*logical).unwrap_or(NULL_SLOT))
162            .collect();
163        let types = columns.iter().map(|(_, _, _, ty)| ty.clone()).collect();
164        let wildcard_hidden = columns
165            .iter()
166            .enumerate()
167            .filter_map(|(output, (_, _, logical, _))| {
168                input
169                    .index
170                    .cold
171                    .wildcard_hidden
172                    .contains(logical)
173                    .then_some(output)
174            })
175            .collect();
176        Self::from_typed_parts_with_aliases_and_exact_precedence(
177            output_names,
178            identities,
179            types,
180            slots,
181            input.physical_width(),
182            SchemaBuildMetadata {
183                aliases: HashMap::new(),
184                alias_types: HashMap::new(),
185                internal: input.index.executor_attributes.clone(),
186                internal_types: input.index.cold.executor_attribute_types.clone(),
187                score_sources: input.index.cold.score_sources.clone(),
188                wildcard_hidden,
189                binding_only: HashMap::new(),
190                ..SchemaBuildMetadata::default()
191            },
192        )
193    }
194}