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