1use super::{BindingContext, QueryPlan, RowSchema, SQLError, SQLParam, SchemaScope};
10use crate::ast::ColumnType;
11use crate::routines::RoutineResolution;
12
13pub fn hide_recursive_generated_schema(schema: &RowSchema, visible: usize) -> RowSchema {
14 let visible = visible.min(schema.len());
15 let base = RowSchema::with_types(
16 schema.columns()[..visible].to_vec(),
17 schema.column_types()[..visible].to_vec(),
18 )
19 .with_column_collations((0..visible).map(|position| schema.column_collation(position)))
20 .with_record_fields_from(schema);
21 let generated = schema
22 .columns()
23 .iter()
24 .enumerate()
25 .skip(visible)
26 .map(|(position, column)| {
27 (
28 crate::ColumnIdentity::unqualified(column),
29 schema.column_type(position).cloned(),
30 )
31 })
32 .collect::<Vec<_>>();
33 RowSchema::with_typed_virtual_identities(&base, &generated)
34}
35
36pub fn extend_cte_generated_schema(
37 routines: &dyn RoutineResolution,
38 cte: &crate::plan::CtePlan,
39 schema: RowSchema,
40 params: &[SQLParam],
41) -> Result<RowSchema, SQLError> {
42 extend_cte_generated_schema_mode(routines, cte, schema, params, true)
43}
44
45fn extend_cte_generated_schema_mode(
46 routines: &dyn RoutineResolution,
47 cte: &crate::plan::CtePlan,
48 schema: RowSchema,
49 params: &[SQLParam],
50 reject_output_conflicts: bool,
51) -> Result<RowSchema, SQLError> {
52 if cte.search.is_none() && cte.cycle.is_none() {
53 return Ok(schema);
54 }
55 let mut columns = schema.columns().to_vec();
56 let mut types = schema.column_types().to_vec();
57 let base_columns = columns.clone();
58 let require_columns = |requested: &[String], kind: &str| -> Result<(), SQLError> {
59 let mut seen = std::collections::BTreeSet::new();
60 for column in requested {
61 if !seen.insert(column) {
62 return Err(SQLError::Routine {
63 sqlstate: "42701".into(),
64 message: format!("{kind} column \"{column}\" specified more than once"),
65 });
66 }
67 if !base_columns.iter().any(|candidate| candidate == column) {
68 return Err(SQLError::Routine {
69 sqlstate: "42601".into(),
70 message: format!("{kind} column \"{column}\" not in WITH query column list"),
71 });
72 }
73 }
74 Ok(())
75 };
76 let reject_conflict = |name: &str, kind: &str| -> Result<(), SQLError> {
77 if reject_output_conflicts && base_columns.iter().any(|column| column == name) {
78 return Err(SQLError::Routine {
79 sqlstate: "42601".into(),
80 message: format!(
81 "{kind} column name \"{name}\" already used in WITH query column list"
82 ),
83 });
84 }
85 Ok(())
86 };
87 if let Some(search) = &cte.search {
88 require_columns(&search.columns, "search")?;
89 reject_conflict(&search.sequence_column, "search sequence")?;
90 columns.push(search.sequence_column.clone());
91 types.push(Some(if search.breadth_first {
92 ColumnType::Record
93 } else {
94 ColumnType::Array(Box::new(ColumnType::Record))
95 }));
96 }
97 if let Some(cycle) = &cte.cycle {
98 require_columns(&cycle.columns, "cycle")?;
99 if cycle.mark_column == cycle.path_column {
100 return Err(SQLError::Routine {
101 sqlstate: "42601".into(),
102 message: "cycle mark column name and cycle path column name are the same".into(),
103 });
104 }
105 reject_conflict(&cycle.mark_column, "cycle mark")?;
106 reject_conflict(&cycle.path_column, "cycle path")?;
107 let empty = RowSchema::default();
108 let mark_type = match (
110 crate::type_resolution::common_context_expression_type(
111 &cycle.mark_value,
112 &empty,
113 params,
114 Some(routines),
115 )?,
116 crate::type_resolution::common_context_expression_type(
117 &cycle.mark_default,
118 &empty,
119 params,
120 Some(routines),
121 )?,
122 ) {
123 (Some(left), Some(right)) => Some(crate::type_resolution::common_type_in(
124 crate::type_resolution::CommonTypeContext::Cycle,
125 &left,
126 &right,
127 )?),
128 (left @ Some(_), None) | (None, left @ Some(_)) => left,
129 (None, None) => None,
130 };
131 if let Some(mark_type) = mark_type.as_ref() {
132 crate::equality_operand_type(mark_type, mark_type)?;
133 }
134 for column in &cycle.columns {
135 if let Some(column_type) = base_columns
136 .iter()
137 .position(|candidate| candidate == column)
138 .and_then(|position| schema.column_type(position))
139 {
140 crate::equality_operand_type(column_type, column_type)?;
141 }
142 }
143 columns.push(cycle.mark_column.clone());
144 types.push(mark_type);
145 columns.push(cycle.path_column.clone());
146 types.push(Some(ColumnType::Array(Box::new(ColumnType::Record))));
147 }
148 let width = columns.len();
149 Ok(RowSchema::with_types(columns, types)
150 .with_column_collations((0..width).map(|position| schema.column_collation(position)))
151 .with_record_fields_from(&schema))
152}
153
154pub fn extend_recursive_cte_binding_schema(
155 routines: &dyn RoutineResolution,
156 cte: &crate::plan::CtePlan,
157 schema: RowSchema,
158 params: &[SQLParam],
159) -> Result<RowSchema, SQLError> {
160 let extended = extend_cte_generated_schema_mode(routines, cte, schema.clone(), params, false)?;
161 let generated = extended
162 .columns()
163 .iter()
164 .enumerate()
165 .skip(schema.len())
166 .map(|(position, column)| {
167 (
168 crate::ColumnIdentity::unqualified(column),
169 extended.column_type(position).cloned(),
170 )
171 })
172 .collect::<Vec<_>>();
173 Ok(RowSchema::with_typed_conflicting_virtual_identities(
174 &schema, &generated,
175 ))
176}
177
178pub fn analyze_recursive_control_step(
179 routines: &dyn RoutineResolution,
180 cte: &crate::plan::CtePlan,
181 step: &QueryPlan,
182 base_schema: RowSchema,
183 params: &[SQLParam],
184 ctes: &BindingContext,
185) -> Result<(), SQLError> {
186 let provisional = extend_recursive_cte_binding_schema(routines, cte, base_schema, params)?;
187 let mut scope = SchemaScope::for_analysis(ctes)?;
188 scope.ctes.insert(cte.name.clone(), provisional);
189 scope.bind_query(routines, step, params, None).map(|_| ())
190}