uqa_sql/schema/
join_output.rs1use super::SchemaLayoutError;
10use crate::{ColumnIdentity, ColumnType, RowSchema};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum JoinOutputSource {
15 Input(usize),
17 Cast { input: usize, ty: ColumnType },
19 Coalesce {
22 left: usize,
23 right: usize,
24 ty: ColumnType,
25 },
26}
27
28pub fn compile_layout(
29 input: &RowSchema,
30 columns: &[(String, ColumnIdentity, JoinOutputSource)],
31 aliases: &[(ColumnIdentity, JoinOutputSource)],
32) -> Result<(RowSchema, Vec<JoinOutputSource>), SchemaLayoutError> {
33 let input_width = input.len();
34 let mut computed = Vec::<JoinOutputSource>::new();
35 for source in columns
36 .iter()
37 .map(|(_, _, source)| source)
38 .chain(aliases.iter().map(|(_, source)| source))
39 {
40 match source {
41 JoinOutputSource::Input(position) if *position >= input_width => {
42 return Err(SchemaLayoutError(format!(
43 "join output input position {position} is outside width {input_width}"
44 )));
45 }
46 JoinOutputSource::Cast { input, .. } if *input >= input_width => {
47 return Err(SchemaLayoutError(format!(
48 "join output cast position {input} is outside width {input_width}"
49 )));
50 }
51 JoinOutputSource::Coalesce { left, right, .. }
52 if *left >= input_width || *right >= input_width =>
53 {
54 return Err(SchemaLayoutError(format!(
55 "join output coalesce positions ({left}, {right}) are outside width {input_width}"
56 )));
57 }
58 source @ (JoinOutputSource::Cast { .. } | JoinOutputSource::Coalesce { .. }) => {
59 if !computed.contains(source) {
60 computed.push(source.clone());
61 }
62 }
63 JoinOutputSource::Input(_) => {}
64 }
65 }
66
67 let computed_types = computed.iter().map(source_type).collect::<Vec<_>>();
68 let intermediate = RowSchema::append_hidden_typed(input, &computed_types);
69 let source_position = |source: &JoinOutputSource| -> usize {
70 match source {
71 JoinOutputSource::Input(position) => input
72 .physical_slot(*position)
73 .expect("validated join output input position has a physical slot"),
74 JoinOutputSource::Cast { .. } | JoinOutputSource::Coalesce { .. } => {
75 let index = computed
76 .iter()
77 .position(|candidate| candidate == source)
78 .expect("computed join output source was registered");
79 input.physical_width() + index
80 }
81 }
82 };
83 let columns = columns
84 .iter()
85 .map(|(name, identity, source)| {
86 let ty = match source {
87 JoinOutputSource::Input(position) => intermediate.column_type(*position).cloned(),
88 JoinOutputSource::Cast { .. } | JoinOutputSource::Coalesce { .. } => {
89 source_type(source)
90 }
91 };
92 (name.clone(), identity.clone(), source_position(source), ty)
93 })
94 .collect::<Vec<_>>();
95 let aliases = aliases
96 .iter()
97 .map(|(name, source)| {
98 let ty = match source {
99 JoinOutputSource::Input(position) => input.column_type(*position).cloned(),
100 JoinOutputSource::Cast { .. } | JoinOutputSource::Coalesce { .. } => {
101 source_type(source)
102 }
103 };
104 (name.clone(), source_position(source), ty)
105 })
106 .collect::<Vec<_>>();
107 let schema = RowSchema::remap_typed_physical_identities(&intermediate, &columns, &aliases);
108 Ok((schema, computed))
109}
110
111pub fn source_type(source: &JoinOutputSource) -> Option<ColumnType> {
112 match source {
113 JoinOutputSource::Input(_) => None,
114 JoinOutputSource::Cast { ty, .. } | JoinOutputSource::Coalesce { ty, .. } => {
115 Some(ty.clone())
116 }
117 }
118}