1use std::collections::{HashMap, HashSet};
10
11use crate::ast::ColumnType;
12
13use super::{ColumnIdentity, RowSchema, SchemaBuildMetadata, NULL_SLOT};
14
15impl RowSchema {
16 pub fn with_typed_outer_scope(
18 input: &Self,
19 outer_columns: &[(String, Option<ColumnType>)],
20 ) -> Self {
21 let identities = outer_columns
22 .iter()
23 .map(|(column, ty)| (ColumnIdentity::unqualified(column), ty.clone()))
24 .collect::<Vec<_>>();
25 Self::with_typed_outer_identities(input, &identities)
26 }
27
28 pub fn with_typed_outer_identities(
30 input: &Self,
31 outer_columns: &[(ColumnIdentity, Option<ColumnType>)],
32 ) -> Self {
33 let outer_base = input.physical_width();
34 let current_qualifiers = input.relation_qualifiers().collect::<HashSet<_>>();
35 let mut current_unqualified = input
36 .identities()
37 .iter()
38 .map(ColumnIdentity::column)
39 .collect::<HashSet<_>>();
40 current_unqualified.extend(
41 input
42 .index
43 .aliases
44 .keys()
45 .filter(|identity| identity.qualifier().is_none())
46 .map(ColumnIdentity::column),
47 );
48
49 let mut aliases = input.index.aliases.clone();
50 let mut alias_types = input.index.cold.aliases.clone();
51 let mut ambiguous_qualified = input.index.ambiguous_qualified.clone();
52 let mut outer_exact = HashSet::<&str>::new();
53 let mut outer_qualified = HashMap::<&str, Vec<usize>>::new();
54 let mut outer_qualified_counts = HashMap::<ColumnIdentity, usize>::new();
55 for (identity, _) in outer_columns {
56 if identity
57 .qualifier()
58 .is_some_and(|qualifier| !current_qualifiers.contains(qualifier))
59 {
60 *outer_qualified_counts.entry(identity.clone()).or_default() += 1;
61 }
62 }
63 for (position, (identity, ty)) in outer_columns.iter().enumerate() {
64 let slot = outer_base + position;
65 if let Some(qualifier) = identity.qualifier() {
66 if !current_qualifiers.contains(qualifier) {
67 if outer_qualified_counts.get(identity) == Some(&1) {
68 aliases.insert(identity.clone(), slot);
69 alias_types.insert(identity.clone(), ty.clone());
70 } else {
71 aliases.remove(identity);
72 alias_types.remove(identity);
73 ambiguous_qualified.insert(identity.clone());
74 }
75 }
76 outer_qualified
77 .entry(identity.column())
78 .or_default()
79 .push(position);
80 } else {
81 outer_exact.insert(identity.column());
82 if !current_unqualified.contains(identity.column()) {
83 aliases.insert(identity.clone(), slot);
84 alias_types.insert(identity.clone(), ty.clone());
85 }
86 }
87 }
88
89 let mut outer_ambiguous = HashSet::new();
90 for (column, positions) in outer_qualified {
91 if current_unqualified.contains(column) || outer_exact.contains(column) {
92 continue;
93 }
94 match positions.as_slice() {
95 [position] => {
96 let identity = ColumnIdentity::unqualified(column);
97 aliases.insert(identity.clone(), outer_base + position);
98 alias_types.insert(identity, outer_columns[*position].1.clone());
99 }
100 [_, _, ..] => {
101 outer_ambiguous.insert(Box::<str>::from(column));
102 }
103 [] => {}
104 }
105 }
106
107 Self::from_typed_parts_with_aliases_and_exact_precedence(
108 input.columns().to_vec(),
109 input.identities().to_vec(),
110 input.column_types().to_vec(),
111 input.index.slots.to_vec(),
112 input.physical_width() + outer_columns.len(),
113 SchemaBuildMetadata {
114 collations: input.index.cold.collations.clone(),
115 record_fields: input.index.cold.record_fields.clone(),
116 aliases,
117 alias_types,
118 internal: input.index.executor_attributes.clone(),
119 internal_types: input.index.cold.executor_attribute_types.clone(),
120 score_sources: input.index.cold.score_sources.clone(),
121 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
122 binding_only: input.index.cold.binding_only.clone(),
123 row_types: input.index.cold.row_types.clone(),
124 open_qualifiers: input.index.cold.open_qualifiers.clone(),
125 extra_ambiguous_unqualified: outer_ambiguous,
126 extra_ambiguous_qualified: ambiguous_qualified,
127 ..SchemaBuildMetadata::default()
128 },
129 )
130 }
131
132 #[expect(
134 clippy::too_many_lines,
135 reason = "outer-scope merge keeps schema and value precedence aligned"
136 )]
137 pub fn with_outer_schema(input: &Self, outer: &Self) -> Self {
138 let mut collations = input.index.cold.collations.clone();
139 let outer_base = input.physical_width();
140 let current_qualifiers = input.relation_qualifiers().collect::<HashSet<_>>();
141 let mut current_unqualified = input
142 .index
143 .unqualified
144 .keys()
145 .map(std::convert::AsRef::as_ref)
146 .collect::<HashSet<_>>();
147 current_unqualified.extend(
148 input
149 .index
150 .aliases
151 .keys()
152 .filter(|identity| identity.qualifier().is_none())
153 .map(ColumnIdentity::column),
154 );
155
156 let shifted_slot = |slot: usize| {
157 if slot == NULL_SLOT {
158 NULL_SLOT
159 } else {
160 outer_base + slot
161 }
162 };
163 let mut aliases = input.index.aliases.clone();
164 let mut alias_types = input.index.cold.aliases.clone();
165 let mut ambiguous_unqualified = input.index.ambiguous_unqualified.clone();
166 let mut ambiguous_qualified = input.index.ambiguous_qualified.clone();
167
168 let qualified_identities = outer
169 .index
170 .qualified
171 .keys()
172 .chain(
173 outer
174 .index
175 .aliases
176 .keys()
177 .filter(|identity| identity.qualifier().is_some()),
178 )
179 .chain(outer.index.ambiguous_qualified.iter())
180 .cloned()
181 .collect::<HashSet<_>>();
182 for identity in qualified_identities {
183 let qualifier = identity
184 .qualifier()
185 .expect("qualified outer identity has a qualifier");
186 if current_qualifiers.contains(qualifier) {
187 continue;
188 }
189 if outer.qualified_column_is_ambiguous(qualifier, identity.column()) {
190 aliases.remove(&identity);
191 alias_types.remove(&identity);
192 ambiguous_qualified.insert(identity);
193 continue;
194 }
195 let slot = outer
196 .qualified_slot(qualifier, identity.column())
197 .map_or(NULL_SLOT, shifted_slot);
198 let ty = outer.qualified_type(qualifier, identity.column()).cloned();
199 collations.alias(
200 &identity,
201 outer.qualified_collation(qualifier, identity.column()),
202 ty.as_ref(),
203 );
204 aliases.insert(identity.clone(), slot);
205 alias_types.insert(identity, ty);
206 }
207
208 let outer_unqualified = outer
209 .index
210 .unqualified
211 .keys()
212 .map(std::convert::AsRef::as_ref)
213 .chain(
214 outer
215 .index
216 .aliases
217 .keys()
218 .filter(|identity| identity.qualifier().is_none())
219 .map(ColumnIdentity::column),
220 )
221 .chain(
222 outer
223 .index
224 .ambiguous_unqualified
225 .iter()
226 .map(std::convert::AsRef::as_ref),
227 )
228 .collect::<HashSet<_>>();
229 for column in outer_unqualified {
230 if current_unqualified.contains(column) {
231 continue;
232 }
233 let identity = ColumnIdentity::unqualified(column);
234 if outer.column_is_ambiguous(column) {
235 aliases.remove(&identity);
236 alias_types.remove(&identity);
237 ambiguous_unqualified.insert(Box::<str>::from(column));
238 continue;
239 }
240 let slot = outer.column_slot(column).map_or(NULL_SLOT, shifted_slot);
241 aliases.insert(identity.clone(), slot);
242 collations.alias(&identity, outer.collation_of(column), outer.type_of(column));
243 alias_types.insert(identity, outer.type_of(column).cloned());
244 }
245
246 let mut internal = input.index.executor_attributes.clone();
247 let mut internal_types = input.index.cold.executor_attribute_types.clone();
248 for (column, slot) in &outer.index.executor_attributes {
249 internal.insert(*column, shifted_slot(*slot));
250 }
251 for (column, ty) in &outer.index.cold.executor_attribute_types {
252 collations.internal(*column, outer.internal_collation(*column), ty.as_ref());
253 if let Some(existing) = internal_types.get(column) {
254 assert_eq!(
255 existing, ty,
256 "structural identity type changed across correlated scopes"
257 );
258 } else {
259 internal_types.insert(*column, ty.clone());
260 }
261 }
262 let mut score_sources = input.index.cold.score_sources.clone();
263 score_sources.extend(outer.index.cold.score_sources.iter().cloned());
264 let mut open_qualifiers = input.index.cold.open_qualifiers.clone();
265 open_qualifiers.extend(
266 outer
267 .index
268 .cold
269 .open_qualifiers
270 .iter()
271 .filter(|qualifier| {
272 qualifier
273 .as_deref()
274 .is_none_or(|qualifier| !input.has_qualifier(qualifier))
275 })
276 .cloned(),
277 );
278
279 let schema = Self::from_typed_parts_with_aliases_and_exact_precedence(
280 input.columns().to_vec(),
281 input.identities().to_vec(),
282 input.column_types().to_vec(),
283 input.index.slots.to_vec(),
284 input.physical_width() + outer.physical_width(),
285 SchemaBuildMetadata {
286 collations,
287 record_fields: Self::joined_record_fields(input, outer, input.physical_width()),
288 aliases,
289 alias_types,
290 internal,
291 internal_types,
292 score_sources,
293 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
294 binding_only: input.index.cold.binding_only.clone(),
295 row_types: super::relation_types::RelationRowTypes::with_outer(input, outer),
296 open_qualifiers,
297 extra_ambiguous_unqualified: ambiguous_unqualified,
298 extra_ambiguous_qualified: ambiguous_qualified,
299 local_width: Some(
300 input
301 .index
302 .cold
303 .local_width
304 .unwrap_or_else(|| input.physical_width()),
305 ),
306 ..SchemaBuildMetadata::default()
307 },
308 );
309 let virtual_identities = outer
310 .typed_virtual_identities()
311 .filter(|(identity, _)| {
312 identity
313 .qualifier()
314 .is_none_or(|qualifier| !current_qualifiers.contains(qualifier))
315 })
316 .map(|(identity, ty)| (identity.clone(), ty.cloned()))
317 .collect::<Vec<_>>();
318 let mut schema = Self::with_typed_virtual_identities(&schema, &virtual_identities);
319 let index = std::sync::Arc::make_mut(&mut schema.index);
320 for (identity, _) in &virtual_identities {
321 if input.index.cold.binding_only.contains_key(identity) {
322 continue;
323 }
324 if let Some(ty) = index.cold.binding_only.get(identity) {
325 index.cold.collations.virtual_column(
326 identity,
327 outer.virtual_collation(identity),
328 ty.as_ref(),
329 );
330 }
331 }
332 schema
333 }
334}