1use super::{
10 ColumnIdentity, ColumnType, HashMap, InternalColumnRef, RowSchema, SchemaLayoutError,
11 SchemaLayoutResult,
12};
13use crate::catalog::type_metadata::pg_type_collation_oid;
14use std::collections::BTreeMap;
15
16#[derive(Clone, Debug, Default, PartialEq, Eq)]
17pub(super) struct Collations(Option<Box<Bindings>>);
18
19#[derive(Clone, Debug, Default, PartialEq, Eq)]
20struct Bindings {
21 columns: BTreeMap<usize, u32>,
22 aliases: BTreeMap<ColumnIdentity, u32>,
23 internal: BTreeMap<InternalColumnRef, u32>,
24 virtual_columns: BTreeMap<ColumnIdentity, u32>,
25}
26
27#[derive(Clone, Copy, Debug)]
29pub enum CollationBinding<'a> {
30 Column(usize),
31 Alias(&'a ColumnIdentity),
32 Internal(InternalColumnRef),
33}
34
35fn type_default(ty: Option<&ColumnType>) -> Option<u32> {
36 ty.map(|ty| u32::try_from(pg_type_collation_oid(ty)).expect("type collation identity"))
37}
38
39fn override_value(oid: Option<u32>, ty: Option<&ColumnType>) -> Option<u32> {
40 oid.filter(|oid| {
41 ty.is_some_and(|ty| {
42 crate::schema::composites::type_is_collatable(ty)
43 && type_default(Some(ty)) != Some(*oid)
44 })
45 })
46}
47
48impl Collations {
49 pub(super) fn same_physical_bindings(&self, other: &Self) -> bool {
50 let empty = Bindings::default();
51 let left = self.0.as_deref().unwrap_or(&empty);
52 let right = other.0.as_deref().unwrap_or(&empty);
53 left.columns == right.columns
54 && left.aliases == right.aliases
55 && left.internal == right.internal
56 }
57
58 pub(super) fn joined(left: &RowSchema, right: &RowSchema) -> Self {
59 let mut result = left.index.cold.collations.clone();
60 if let Some(target) = &mut result.0 {
61 if !target.aliases.is_empty() {
62 for identity in right.index.cold.aliases.keys() {
63 target.aliases.remove(identity);
64 }
65 }
66 if !target.virtual_columns.is_empty() {
67 for identity in right.index.cold.binding_only.keys() {
68 target.virtual_columns.remove(identity);
69 }
70 }
71 }
72 if let Some(source) = &right.index.cold.collations.0 {
73 let target = result.0.get_or_insert_with(Default::default);
74 target.columns.extend(
75 source
76 .columns
77 .iter()
78 .map(|(position, oid)| (left.len() + position, *oid)),
79 );
80 target.aliases.extend(
81 source
82 .aliases
83 .iter()
84 .map(|(identity, oid)| (identity.clone(), *oid)),
85 );
86 target
87 .internal
88 .extend(source.internal.iter().map(|(column, oid)| (*column, *oid)));
89 target.virtual_columns.extend(
90 source
91 .virtual_columns
92 .iter()
93 .map(|(identity, oid)| (identity.clone(), *oid)),
94 );
95 }
96 result.compact();
97 result
98 }
99
100 fn compact(&mut self) {
101 if self.0.as_ref().is_some_and(|bindings| {
102 bindings.columns.is_empty()
103 && bindings.aliases.is_empty()
104 && bindings.internal.is_empty()
105 && bindings.virtual_columns.is_empty()
106 }) {
107 self.0 = None;
108 }
109 }
110
111 pub(super) fn column(&mut self, position: usize, oid: Option<u32>, ty: Option<&ColumnType>) {
112 let value = override_value(oid, ty);
113 if let Some(value) = value {
114 self.0
115 .get_or_insert_with(Default::default)
116 .columns
117 .insert(position, value);
118 } else if let Some(bindings) = &mut self.0 {
119 bindings.columns.remove(&position);
120 }
121 }
122
123 pub(super) fn alias(
124 &mut self,
125 identity: &ColumnIdentity,
126 oid: Option<u32>,
127 ty: Option<&ColumnType>,
128 ) {
129 let value = override_value(oid, ty);
130 if let Some(value) = value {
131 self.0
132 .get_or_insert_with(Default::default)
133 .aliases
134 .insert(identity.clone(), value);
135 } else if let Some(bindings) = &mut self.0 {
136 bindings.aliases.remove(identity);
137 }
138 }
139
140 pub(super) fn internal(
141 &mut self,
142 column: InternalColumnRef,
143 oid: Option<u32>,
144 ty: Option<&ColumnType>,
145 ) {
146 let value = override_value(oid, ty);
147 if let Some(value) = value {
148 self.0
149 .get_or_insert_with(Default::default)
150 .internal
151 .insert(column, value);
152 } else if let Some(bindings) = &mut self.0 {
153 bindings.internal.remove(&column);
154 }
155 }
156
157 pub(super) fn virtual_column(
158 &mut self,
159 identity: &ColumnIdentity,
160 oid: Option<u32>,
161 ty: Option<&ColumnType>,
162 ) {
163 let value = override_value(oid, ty);
164 if let Some(value) = value {
165 self.0
166 .get_or_insert_with(Default::default)
167 .virtual_columns
168 .insert(identity.clone(), value);
169 } else if let Some(bindings) = &mut self.0 {
170 bindings.virtual_columns.remove(identity);
171 }
172 }
173
174 pub(super) fn clear_columns(&mut self) {
175 if let Some(bindings) = &mut self.0 {
176 bindings.columns.clear();
177 }
178 }
179
180 pub(super) fn retain(
182 &mut self,
183 columns: &[Option<ColumnType>],
184 aliases: &HashMap<ColumnIdentity, Option<ColumnType>>,
185 internal: &HashMap<InternalColumnRef, Option<ColumnType>>,
186 virtual_columns: &HashMap<ColumnIdentity, Option<ColumnType>>,
187 ) {
188 let Some(bindings) = &mut self.0 else {
189 return;
190 };
191 bindings.columns.retain(|position, oid| {
192 override_value(Some(*oid), columns.get(*position).and_then(Option::as_ref)).is_some()
193 });
194 bindings.aliases.retain(|identity, oid| {
195 override_value(Some(*oid), aliases.get(identity).and_then(Option::as_ref)).is_some()
196 });
197 bindings.internal.retain(|column, oid| {
198 override_value(Some(*oid), internal.get(column).and_then(Option::as_ref)).is_some()
199 });
200 bindings.virtual_columns.retain(|identity, oid| {
201 override_value(
202 Some(*oid),
203 virtual_columns.get(identity).and_then(Option::as_ref),
204 )
205 .is_some()
206 });
207 if bindings.columns.is_empty()
208 && bindings.aliases.is_empty()
209 && bindings.internal.is_empty()
210 && bindings.virtual_columns.is_empty()
211 {
212 self.0 = None;
213 }
214 }
215}
216
217impl RowSchema {
218 pub fn input_collation(&self, expression: &crate::ScalarExpr) -> Option<u32> {
220 match expression {
221 crate::ScalarExpr::TypedLiteral { collation_oid, .. } => *collation_oid,
222 crate::ScalarExpr::Collated { collation, .. } => Some(collation.oid()),
223 crate::ScalarExpr::Func {
224 binding: Some(binding),
225 ..
226 } => match binding.dispatch {
227 Some(crate::ast::FunctionDispatch::Collate { oid }) => oid,
228 _ => None,
229 },
230 crate::ScalarExpr::Column(name) => self.collation_of(name),
231 crate::ScalarExpr::QualifiedColumn { qualifier, column } => {
232 self.qualified_collation(qualifier, column)
233 }
234 crate::ScalarExpr::Position(position) => self.column_collation(*position),
235 crate::ScalarExpr::InternalColumn(column) => self.internal_collation(*column),
236 _ => None,
237 }
238 }
239
240 pub fn projection_input(&self, expression: &crate::ScalarExpr) -> Option<super::ProjectedSlot> {
242 let (logical, alias) = match expression {
243 crate::ScalarExpr::Column(name) => {
244 if self.column_is_ambiguous(name) {
245 return None;
246 }
247 let logical = self.unqualified_position(name);
248 (
249 logical,
250 logical.is_none().then(|| ColumnIdentity::unqualified(name)),
251 )
252 }
253 crate::ScalarExpr::QualifiedColumn { qualifier, column } => {
254 if self.qualified_column_is_ambiguous(qualifier, column) {
255 return None;
256 }
257 let logical = self.qualified_position(qualifier, column);
258 (
259 logical,
260 logical
261 .is_none()
262 .then(|| ColumnIdentity::qualified(qualifier, column)),
263 )
264 }
265 crate::ScalarExpr::Position(position) if *position < self.len() => {
266 (Some(*position), None)
267 }
268 _ => return None,
269 };
270 let (physical, collation) = if let Some(position) = logical {
271 (
272 self.physical_slot(position),
273 self.column_collation(position),
274 )
275 } else {
276 let identity = alias?;
277 let slot = *self.index.aliases.get(&identity)?;
278 (
279 (slot != super::NULL_SLOT).then_some(slot),
280 self.alias_collation(&identity),
281 )
282 };
283 Some(super::ProjectedSlot::BoundInput {
284 physical,
285 collation,
286 })
287 }
288
289 pub fn with_column_collations(
291 mut self,
292 collations: impl IntoIterator<Item = Option<u32>>,
293 ) -> Self {
294 let mut width = 0;
295 let index = std::sync::Arc::make_mut(&mut self.index);
296 for (position, oid) in collations.into_iter().enumerate() {
297 assert!(
298 position < index.columns.len(),
299 "row schema column/collation width mismatch"
300 );
301 let ty = index.cold.columns[position].as_ref();
302 assert!(
303 oid.is_none_or(
304 |oid| oid == 0 || ty.is_some_and(crate::schema::composites::type_is_collatable)
305 ),
306 "collation requires a collatable column type"
307 );
308 index.cold.collations.column(position, oid, ty);
309 width += 1;
310 }
311 assert_eq!(
312 width,
313 index.columns.len(),
314 "row schema column/collation width mismatch"
315 );
316 index.cold.collations.compact();
317 self
318 }
319
320 pub fn with_virtual_collation(
322 mut self,
323 identity: &ColumnIdentity,
324 oid: u32,
325 ) -> SchemaLayoutResult<Self> {
326 let ty = self
327 .index
328 .cold
329 .binding_only
330 .get(identity)
331 .and_then(Option::as_ref);
332 if ty.is_none_or(|ty| !crate::schema::composites::type_is_collatable(ty)) {
333 return Err(SchemaLayoutError(
334 "collation metadata requires an existing collatable virtual binding".into(),
335 ));
336 }
337 let index = std::sync::Arc::make_mut(&mut self.index);
338 index.cold.collations.virtual_column(
339 identity,
340 Some(oid),
341 index
342 .cold
343 .binding_only
344 .get(identity)
345 .and_then(Option::as_ref),
346 );
347 index.cold.collations.compact();
348 Ok(self)
349 }
350
351 pub fn column_collation(&self, position: usize) -> Option<u32> {
352 self.index
353 .cold
354 .collations
355 .0
356 .as_ref()
357 .and_then(|bindings| bindings.columns.get(&position).copied())
358 .or_else(|| type_default(self.column_type(position)))
359 }
360
361 pub fn internal_collation(&self, column: InternalColumnRef) -> Option<u32> {
362 self.index
363 .cold
364 .collations
365 .0
366 .as_ref()
367 .and_then(|bindings| bindings.internal.get(&column).copied())
368 .or_else(|| type_default(self.internal_type(column)))
369 }
370
371 pub fn collation_of(&self, name: &str) -> Option<u32> {
372 if self.column_is_ambiguous(name) {
373 return None;
374 }
375 self.index
376 .unqualified
377 .get(name)
378 .and_then(|position| self.column_collation(*position))
379 .or_else(|| self.hidden_collation(&ColumnIdentity::unqualified(name)))
380 }
381
382 pub fn qualified_collation(&self, qualifier: &str, name: &str) -> Option<u32> {
383 let identity = ColumnIdentity::qualified(qualifier, name);
384 if self.index.ambiguous_qualified.contains(&identity) {
385 return None;
386 }
387 self.index
388 .qualified
389 .get(&identity)
390 .and_then(|position| self.column_collation(*position))
391 .or_else(|| self.hidden_collation(&identity))
392 }
393
394 fn hidden_collation(&self, identity: &ColumnIdentity) -> Option<u32> {
395 self.alias_collation(identity)
396 .or_else(|| self.virtual_collation(identity))
397 }
398
399 pub(super) fn virtual_collation(&self, identity: &ColumnIdentity) -> Option<u32> {
400 self.index
401 .cold
402 .collations
403 .0
404 .as_ref()
405 .and_then(|bindings| bindings.virtual_columns.get(identity).copied())
406 .or_else(|| {
407 type_default(
408 self.index
409 .cold
410 .binding_only
411 .get(identity)
412 .and_then(Option::as_ref),
413 )
414 })
415 }
416
417 pub(super) fn alias_collation(&self, identity: &ColumnIdentity) -> Option<u32> {
418 self.index
419 .cold
420 .collations
421 .0
422 .as_ref()
423 .and_then(|bindings| bindings.aliases.get(identity).copied())
424 .or_else(|| {
425 type_default(
426 self.index
427 .cold
428 .aliases
429 .get(identity)
430 .and_then(Option::as_ref),
431 )
432 })
433 }
434
435 pub(super) fn inherited_column_collation(&self, position: usize) -> Option<u32> {
436 self.column_type(position)
437 .filter(|ty| crate::schema::composites::type_is_collatable(ty))
438 .and_then(|_| self.column_collation(position))
439 }
440
441 pub(super) fn inherited_physical_collation(&self, physical: usize) -> Option<u32> {
442 if physical == super::NULL_SLOT {
443 return None;
444 }
445 self.index
446 .slots
447 .iter()
448 .position(|slot| *slot == physical)
449 .and_then(|position| self.inherited_column_collation(position))
450 .or_else(|| {
451 self.index.aliases.iter().find_map(|(identity, slot)| {
452 (*slot == physical
453 && self
454 .index
455 .cold
456 .aliases
457 .get(identity)
458 .and_then(Option::as_ref)
459 .is_some_and(crate::schema::composites::type_is_collatable))
460 .then(|| self.alias_collation(identity))
461 .flatten()
462 })
463 })
464 .or_else(|| {
465 self.index
466 .executor_attributes
467 .iter()
468 .find_map(|(column, slot)| {
469 (*slot == physical
470 && self
471 .internal_type(*column)
472 .is_some_and(crate::schema::composites::type_is_collatable))
473 .then(|| self.internal_collation(*column))
474 .flatten()
475 })
476 })
477 }
478
479 pub fn collation_overrides(&self) -> impl Iterator<Item = (CollationBinding<'_>, u32)> {
481 self.index.cold.collations.0.iter().flat_map(|bindings| {
482 bindings
483 .columns
484 .iter()
485 .map(|(column, oid)| (CollationBinding::Column(*column), *oid))
486 .chain(
487 bindings
488 .aliases
489 .iter()
490 .map(|(identity, oid)| (CollationBinding::Alias(identity), *oid)),
491 )
492 .chain(
493 bindings
494 .internal
495 .iter()
496 .map(|(column, oid)| (CollationBinding::Internal(*column), *oid)),
497 )
498 })
499 }
500
501 pub fn with_collation_override(
503 mut self,
504 binding: CollationBinding<'_>,
505 oid: u32,
506 ) -> SchemaLayoutResult<Self> {
507 let ty = match binding {
508 CollationBinding::Column(position) => self.column_type(position),
509 CollationBinding::Alias(identity) => self
510 .index
511 .cold
512 .aliases
513 .get(identity)
514 .and_then(Option::as_ref),
515 CollationBinding::Internal(column) => self.internal_type(column),
516 };
517 if ty.is_none_or(|ty| !crate::schema::composites::type_is_collatable(ty)) {
518 return Err(SchemaLayoutError(
519 "collation metadata requires an existing collatable binding".into(),
520 ));
521 }
522 let value = override_value(Some(oid), ty);
523 let index = std::sync::Arc::make_mut(&mut self.index);
524 match binding {
525 CollationBinding::Column(position) => {
526 index.cold.collations.column(
527 position,
528 value,
529 index.cold.columns[position].as_ref(),
530 );
531 }
532 CollationBinding::Alias(identity) => index.cold.collations.alias(
533 identity,
534 value,
535 index.cold.aliases.get(identity).and_then(Option::as_ref),
536 ),
537 CollationBinding::Internal(column) => index.cold.collations.internal(
538 column,
539 value,
540 index
541 .cold
542 .executor_attribute_types
543 .get(&column)
544 .and_then(Option::as_ref),
545 ),
546 }
547 index.cold.collations.compact();
548 Ok(self)
549 }
550}
551
552#[cfg(test)]
553mod tests;