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