1use crate::ast::{ColumnType, InternalColumnRef};
10
11use super::{ColumnIdentity, RowSchema, SchemaBuildMetadata, ScoreSource, NULL_SLOT};
12
13impl RowSchema {
14 pub fn with_relation_qualifier(input: &Self, qualifier: &str) -> Self {
16 let identities = input
17 .columns()
18 .iter()
19 .cloned()
20 .map(|column| ColumnIdentity::qualified(qualifier, column))
21 .collect();
22 let score_sources = input
23 .index
24 .cold
25 .score_sources
26 .iter()
27 .map(|source| ScoreSource {
28 qualifier: Some(Box::<str>::from(qualifier)),
29 column: source.column,
30 })
31 .collect();
32 Self::from_typed_parts_with_aliases_and_exact_precedence(
33 input.columns().to_vec(),
34 identities,
35 input.column_types().to_vec(),
36 input.index.slots.to_vec(),
37 input.physical_width(),
38 SchemaBuildMetadata {
39 record_fields: input.index.cold.record_fields.clone(),
40 internal: input.index.executor_attributes.clone(),
41 internal_types: input.index.cold.executor_attribute_types.clone(),
42 score_sources,
43 open_qualifiers: input
44 .columns_are_open(None)
45 .then(|| Some(Box::<str>::from(qualifier)))
46 .into_iter()
47 .collect(),
48 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
49 ..SchemaBuildMetadata::default()
50 },
51 )
52 }
53
54 #[must_use]
56 pub fn has_qualifier(&self, qualifier: &str) -> bool {
57 self.index
58 .identities
59 .iter()
60 .chain(self.index.aliases.keys())
61 .chain(self.index.cold.binding_only.keys())
62 .any(|identity| identity.qualifier() == Some(qualifier))
63 || self.columns_are_open(Some(qualifier))
64 }
65
66 #[must_use]
68 pub fn has_unqualified_column(&self, column: &str) -> bool {
69 let identity = ColumnIdentity::unqualified(column);
70 self.index.unqualified.contains_key(column)
71 || self.index.aliases.contains_key(&identity)
72 || self.index.cold.binding_only.contains_key(&identity)
73 }
74
75 #[must_use]
77 pub fn has_qualified_column(&self, qualifier: &str, column: &str) -> bool {
78 let identity = ColumnIdentity::qualified(qualifier, column);
79 self.index.qualified.contains_key(&identity)
80 || self.index.aliases.contains_key(&identity)
81 || self.index.cold.binding_only.contains_key(&identity)
82 }
83
84 pub fn typed_virtual_identities(
86 &self,
87 ) -> impl Iterator<Item = (&ColumnIdentity, Option<&ColumnType>)> {
88 self.index
89 .cold
90 .binding_only
91 .iter()
92 .map(|(identity, ty)| (identity, ty.as_ref()))
93 }
94
95 pub fn typed_physical_alias_identities(
97 &self,
98 ) -> impl Iterator<Item = (&ColumnIdentity, Option<&ColumnType>)> {
99 self.index.aliases.keys().map(|identity| {
100 (
101 identity,
102 self.index
103 .cold
104 .aliases
105 .get(identity)
106 .and_then(Option::as_ref),
107 )
108 })
109 }
110
111 pub fn type_of(&self, name: &str) -> Option<&ColumnType> {
113 if self.index.ambiguous_unqualified.contains(name) {
114 return None;
115 }
116 self.index
117 .unqualified
118 .get(name)
119 .and_then(|logical| self.column_type(*logical))
120 .or_else(|| {
121 self.index
122 .cold
123 .aliases
124 .get(&ColumnIdentity::unqualified(name))
125 .and_then(Option::as_ref)
126 })
127 .or_else(|| {
128 self.index
129 .cold
130 .binding_only
131 .get(&ColumnIdentity::unqualified(name))
132 .and_then(Option::as_ref)
133 })
134 }
135
136 pub fn qualified_type(&self, qualifier: &str, column: &str) -> Option<&ColumnType> {
138 let identity = ColumnIdentity::qualified(qualifier, column);
139 if self.index.ambiguous_qualified.contains(&identity) {
140 return None;
141 }
142 self.index
143 .qualified
144 .get(&identity)
145 .and_then(|logical| self.column_type(*logical))
146 .or_else(|| {
147 self.index
148 .cold
149 .aliases
150 .get(&identity)
151 .and_then(Option::as_ref)
152 })
153 .or_else(|| {
154 self.index
155 .cold
156 .binding_only
157 .get(&identity)
158 .and_then(Option::as_ref)
159 })
160 }
161
162 pub fn column_is_ambiguous(&self, name: &str) -> bool {
163 self.index.ambiguous_unqualified.contains(name)
164 }
165
166 pub fn qualified_column_is_ambiguous(&self, qualifier: &str, column: &str) -> bool {
167 self.index
168 .ambiguous_qualified
169 .contains(&ColumnIdentity::qualified(qualifier, column))
170 }
171
172 pub fn with_identity_aliases(input: &Self, aliases: &[(ColumnIdentity, usize)]) -> Self {
174 let mut lookup_aliases = input.index.aliases.clone();
175 let mut alias_types = input.index.cold.aliases.clone();
176 for (identity, logical) in aliases {
177 lookup_aliases.insert(identity.clone(), input.slot(*logical).unwrap_or(NULL_SLOT));
178 alias_types.insert(identity.clone(), input.column_type(*logical).cloned());
179 }
180 Self::from_typed_parts_with_aliases_and_exact_precedence(
181 input.columns().to_vec(),
182 input.identities().to_vec(),
183 input.column_types().to_vec(),
184 input.index.slots.to_vec(),
185 input.physical_width(),
186 SchemaBuildMetadata {
187 record_fields: input.index.cold.record_fields.clone(),
188 aliases: lookup_aliases,
189 alias_types,
190 internal: input.index.executor_attributes.clone(),
191 internal_types: input.index.cold.executor_attribute_types.clone(),
192 score_sources: input.index.cold.score_sources.clone(),
193 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
194 binding_only: input.index.cold.binding_only.clone(),
195 open_qualifiers: input.index.cold.open_qualifiers.clone(),
196 extra_ambiguous_unqualified: input.index.ambiguous_unqualified.clone(),
197 extra_ambiguous_qualified: input.index.ambiguous_qualified.clone(),
198 ..SchemaBuildMetadata::default()
199 },
200 )
201 }
202
203 pub fn with_physical_identity_aliases(
207 input: &Self,
208 aliases: &[(ColumnIdentity, usize, Option<ColumnType>)],
209 ) -> Self {
210 let mut lookup_aliases = input.index.aliases.clone();
211 let mut alias_types = input.index.cold.aliases.clone();
212 for (identity, slot, ty) in aliases {
213 assert!(
214 *slot < input.physical_width(),
215 "physical identity alias is outside row width"
216 );
217 lookup_aliases.insert(identity.clone(), *slot);
218 alias_types.insert(identity.clone(), ty.clone());
219 }
220 Self::from_typed_parts_with_aliases_and_exact_precedence(
221 input.columns().to_vec(),
222 input.identities().to_vec(),
223 input.column_types().to_vec(),
224 input.index.slots.to_vec(),
225 input.physical_width(),
226 SchemaBuildMetadata {
227 record_fields: input.index.cold.record_fields.clone(),
228 aliases: lookup_aliases,
229 alias_types,
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: input.index.cold.wildcard_hidden.clone(),
234 binding_only: input.index.cold.binding_only.clone(),
235 open_qualifiers: input.index.cold.open_qualifiers.clone(),
236 ..SchemaBuildMetadata::default()
237 },
238 )
239 }
240
241 pub fn with_physical_internal_aliases(
243 input: &Self,
244 aliases: &[(InternalColumnRef, usize, Option<ColumnType>)],
245 ) -> Self {
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, ty) in aliases {
249 assert!(
250 *slot < input.physical_width(),
251 "physical internal alias is outside row width"
252 );
253 internal.insert(*column, *slot);
254 internal_types.insert(*column, ty.clone());
255 }
256 Self::from_typed_parts_with_aliases_and_exact_precedence(
257 input.columns().to_vec(),
258 input.identities().to_vec(),
259 input.column_types().to_vec(),
260 input.index.slots.to_vec(),
261 input.physical_width(),
262 SchemaBuildMetadata {
263 record_fields: input.index.cold.record_fields.clone(),
264 aliases: input.index.aliases.clone(),
265 alias_types: input.index.cold.aliases.clone(),
266 internal,
267 internal_types,
268 score_sources: input.index.cold.score_sources.clone(),
269 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
270 binding_only: input.index.cold.binding_only.clone(),
271 open_qualifiers: input.index.cold.open_qualifiers.clone(),
272 extra_ambiguous_unqualified: input.index.ambiguous_unqualified.clone(),
273 extra_ambiguous_qualified: input.index.ambiguous_qualified.clone(),
274 ..SchemaBuildMetadata::default()
275 },
276 )
277 }
278
279 pub fn with_trailing_internal_aliases(input: &Self, trailing: &Self) -> Self {
281 let offset = input
282 .physical_width()
283 .checked_sub(trailing.physical_width())
284 .expect("trailing internal-alias scope exceeds input row width");
285 let mut internal = input.index.executor_attributes.clone();
286 let mut internal_types = input.index.cold.executor_attribute_types.clone();
287 for (column, slot) in &trailing.index.executor_attributes {
288 let slot = if *slot == NULL_SLOT {
289 NULL_SLOT
290 } else {
291 offset + *slot
292 };
293 if let Some(previous) = internal.insert(*column, slot) {
294 assert_eq!(
295 previous, slot,
296 "structural identity moved between row scopes"
297 );
298 }
299 }
300 for (column, ty) in &trailing.index.cold.executor_attribute_types {
301 if let Some(previous) = internal_types.insert(*column, ty.clone()) {
302 assert_eq!(
303 previous, *ty,
304 "structural identity type changed between row scopes"
305 );
306 }
307 }
308 Self::from_typed_parts_with_aliases_and_exact_precedence(
309 input.columns().to_vec(),
310 input.identities().to_vec(),
311 input.column_types().to_vec(),
312 input.index.slots.to_vec(),
313 input.physical_width(),
314 SchemaBuildMetadata {
315 record_fields: input.index.cold.record_fields.clone(),
316 aliases: input.index.aliases.clone(),
317 alias_types: input.index.cold.aliases.clone(),
318 internal,
319 internal_types,
320 score_sources: input.index.cold.score_sources.clone(),
321 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
322 binding_only: input.index.cold.binding_only.clone(),
323 open_qualifiers: input.index.cold.open_qualifiers.clone(),
324 extra_ambiguous_unqualified: input.index.ambiguous_unqualified.clone(),
325 extra_ambiguous_qualified: input.index.ambiguous_qualified.clone(),
326 ..SchemaBuildMetadata::default()
327 },
328 )
329 }
330
331 pub fn with_wildcard_hidden_positions(
335 input: &Self,
336 positions: impl IntoIterator<Item = usize>,
337 ) -> Self {
338 let wildcard_hidden = positions.into_iter().collect();
339 Self::from_typed_parts_with_aliases_and_exact_precedence(
340 input.columns().to_vec(),
341 input.identities().to_vec(),
342 input.column_types().to_vec(),
343 input.index.slots.to_vec(),
344 input.physical_width(),
345 SchemaBuildMetadata {
346 record_fields: input.index.cold.record_fields.clone(),
347 aliases: input.index.aliases.clone(),
348 alias_types: input.index.cold.aliases.clone(),
349 internal: input.index.executor_attributes.clone(),
350 internal_types: input.index.cold.executor_attribute_types.clone(),
351 score_sources: input.index.cold.score_sources.clone(),
352 wildcard_hidden,
353 binding_only: input.index.cold.binding_only.clone(),
354 open_qualifiers: input.index.cold.open_qualifiers.clone(),
355 ..SchemaBuildMetadata::default()
356 },
357 )
358 }
359
360 #[must_use]
361 pub fn internal_slot(&self, column: InternalColumnRef) -> Option<usize> {
362 self.index
363 .executor_attributes
364 .get(&column)
365 .copied()
366 .filter(|slot| *slot != NULL_SLOT)
367 }
368
369 #[must_use]
371 pub fn unique_internal_column_for_slot(&self, slot: usize) -> Option<InternalColumnRef> {
372 let mut columns = self
373 .index
374 .executor_attributes
375 .iter()
376 .filter_map(|(column, candidate)| (*candidate == slot).then_some(*column));
377 let column = columns.next()?;
378 columns.next().is_none().then_some(column)
379 }
380
381 #[must_use]
382 pub fn internal_type(&self, column: InternalColumnRef) -> Option<&ColumnType> {
383 self.index
384 .cold
385 .executor_attribute_types
386 .get(&column)
387 .and_then(Option::as_ref)
388 }
389
390 pub fn with_score_source(
392 input: &Self,
393 qualifier: Option<&str>,
394 column: InternalColumnRef,
395 ) -> Self {
396 assert!(
397 input.index.executor_attributes.contains_key(&column),
398 "score source must reference an existing internal attribute"
399 );
400 let mut score_sources = input.index.cold.score_sources.clone();
401 score_sources.retain(|source| source.column != column);
402 score_sources.push(ScoreSource {
403 qualifier: qualifier.map(Box::<str>::from),
404 column,
405 });
406 Self::from_typed_parts_with_aliases_and_exact_precedence(
407 input.columns().to_vec(),
408 input.identities().to_vec(),
409 input.column_types().to_vec(),
410 input.index.slots.to_vec(),
411 input.physical_width(),
412 SchemaBuildMetadata {
413 record_fields: input.index.cold.record_fields.clone(),
414 aliases: input.index.aliases.clone(),
415 alias_types: input.index.cold.aliases.clone(),
416 internal: input.index.executor_attributes.clone(),
417 internal_types: input.index.cold.executor_attribute_types.clone(),
418 score_sources,
419 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
420 binding_only: input.index.cold.binding_only.clone(),
421 open_qualifiers: input.index.cold.open_qualifiers.clone(),
422 ..SchemaBuildMetadata::default()
423 },
424 )
425 }
426
427 pub fn with_rebound_score_sources(input: &Self, qualifier: Option<&str>) -> Self {
429 let score_sources = input
430 .index
431 .cold
432 .score_sources
433 .iter()
434 .map(|source| ScoreSource {
435 qualifier: qualifier.map(Box::<str>::from),
436 column: source.column,
437 })
438 .collect();
439 Self::from_typed_parts_with_aliases_and_exact_precedence(
440 input.columns().to_vec(),
441 input.identities().to_vec(),
442 input.column_types().to_vec(),
443 input.index.slots.to_vec(),
444 input.physical_width(),
445 SchemaBuildMetadata {
446 record_fields: input.index.cold.record_fields.clone(),
447 aliases: input.index.aliases.clone(),
448 alias_types: input.index.cold.aliases.clone(),
449 internal: input.index.executor_attributes.clone(),
450 internal_types: input.index.cold.executor_attribute_types.clone(),
451 score_sources,
452 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
453 binding_only: input.index.cold.binding_only.clone(),
454 open_qualifiers: input.index.cold.open_qualifiers.clone(),
455 ..SchemaBuildMetadata::default()
456 },
457 )
458 }
459
460 fn matching_score_sources<'a>(
461 &'a self,
462 qualifier: Option<&'a str>,
463 ) -> impl Iterator<Item = InternalColumnRef> + 'a {
464 self.index
465 .cold
466 .score_sources
467 .iter()
468 .filter(move |source| {
469 qualifier.is_none_or(|qualifier| source.qualifier.as_deref() == Some(qualifier))
470 })
471 .map(move |source| source.column)
472 }
473
474 #[must_use]
475 pub fn score_source_column(&self, qualifier: Option<&str>) -> Option<InternalColumnRef> {
476 let mut columns = self.matching_score_sources(qualifier);
477 let column = columns.next()?;
478 columns.next().is_none().then_some(column)
479 }
480
481 #[must_use]
482 pub fn score_source_is_ambiguous(&self, qualifier: Option<&str>) -> bool {
483 let mut columns = self.matching_score_sources(qualifier);
484 columns.next().is_some() && columns.next().is_some()
485 }
486
487 #[must_use]
488 pub fn score_source_slot(&self, qualifier: Option<&str>) -> Option<usize> {
489 self.score_source_column(qualifier)
490 .and_then(|column| self.internal_slot(column))
491 }
492
493 pub fn with_typed_virtual_identities(
495 input: &Self,
496 identities: &[(ColumnIdentity, Option<ColumnType>)],
497 ) -> Self {
498 let mut binding_only = input.index.cold.binding_only.clone();
499 for (identity, ty) in identities {
500 let already_visible = match identity.qualifier() {
501 Some(qualifier) => input.has_qualified_column(qualifier, identity.column()),
502 None => {
503 input.column_is_ambiguous(identity.column())
504 || input.has_unqualified_column(identity.column())
505 }
506 };
507 if already_visible {
508 continue;
509 }
510 binding_only
511 .entry(identity.clone())
512 .or_insert_with(|| ty.clone());
513 }
514 Self::from_typed_parts_with_aliases_and_exact_precedence(
515 input.columns().to_vec(),
516 input.identities().to_vec(),
517 input.column_types().to_vec(),
518 input.index.slots.to_vec(),
519 input.physical_width(),
520 SchemaBuildMetadata {
521 record_fields: input.index.cold.record_fields.clone(),
522 aliases: input.index.aliases.clone(),
523 alias_types: input.index.cold.aliases.clone(),
524 internal: input.index.executor_attributes.clone(),
525 internal_types: input.index.cold.executor_attribute_types.clone(),
526 score_sources: input.index.cold.score_sources.clone(),
527 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
528 binding_only,
529 open_qualifiers: input.index.cold.open_qualifiers.clone(),
530 extra_ambiguous_unqualified: input.index.ambiguous_unqualified.clone(),
531 extra_ambiguous_qualified: input.index.ambiguous_qualified.clone(),
532 local_width: input.index.cold.local_width,
533 ..SchemaBuildMetadata::default()
534 },
535 )
536 }
537
538 pub fn resolves_local_column(&self, qualifier: Option<&str>, column: &str) -> bool {
540 let slot = match qualifier {
541 Some(qualifier) => self.qualified_slot(qualifier, column),
542 None => self.column_slot(column),
543 };
544 slot.is_some_and(|slot| self.slot_is_local(slot))
545 }
546
547 pub(crate) fn slot_is_local(&self, slot: usize) -> bool {
548 self.index.cold.local_width.is_none_or(|width| slot < width)
549 }
550
551 pub(crate) fn internal_identities(
552 &self,
553 ) -> impl Iterator<Item = (InternalColumnRef, usize)> + '_ {
554 self.index
555 .executor_attributes
556 .iter()
557 .filter_map(|(column, slot)| (*slot != super::NULL_SLOT).then_some((*column, *slot)))
558 }
559
560 pub fn with_typed_conflicting_virtual_identities(
562 input: &Self,
563 identities: &[(ColumnIdentity, Option<ColumnType>)],
564 ) -> Self {
565 let mut binding_only = input.index.cold.binding_only.clone();
566 let mut ambiguous_unqualified = input.index.ambiguous_unqualified.clone();
567 let mut ambiguous_qualified = input.index.ambiguous_qualified.clone();
568 for (identity, ty) in identities {
569 if input.column_is_ambiguous(identity.column())
570 || input.has_unqualified_column(identity.column())
571 {
572 ambiguous_unqualified.insert(Box::<str>::from(identity.column()));
573 }
574 if let Some(qualifier) = identity.qualifier() {
575 if input.has_qualified_column(qualifier, identity.column()) {
576 ambiguous_qualified.insert(identity.clone());
577 }
578 }
579 binding_only
580 .entry(identity.clone())
581 .or_insert_with(|| ty.clone());
582 }
583 Self::from_typed_parts_with_aliases_and_exact_precedence(
584 input.columns().to_vec(),
585 input.identities().to_vec(),
586 input.column_types().to_vec(),
587 input.index.slots.to_vec(),
588 input.physical_width(),
589 SchemaBuildMetadata {
590 record_fields: input.index.cold.record_fields.clone(),
591 aliases: input.index.aliases.clone(),
592 alias_types: input.index.cold.aliases.clone(),
593 internal: input.index.executor_attributes.clone(),
594 internal_types: input.index.cold.executor_attribute_types.clone(),
595 score_sources: input.index.cold.score_sources.clone(),
596 wildcard_hidden: input.index.cold.wildcard_hidden.clone(),
597 binding_only,
598 open_qualifiers: input.index.cold.open_qualifiers.clone(),
599 extra_ambiguous_unqualified: ambiguous_unqualified,
600 extra_ambiguous_qualified: ambiguous_qualified,
601 ..SchemaBuildMetadata::default()
602 },
603 )
604 }
605}