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