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