1use super::{DOC_ID_COLUMN, TABLE_OID_COLUMN};
10use crate::ast::ReturningAliases;
11use crate::plan::ProjectionPlan;
12use crate::{ColumnIdentity, RowSchema, SQLError, ScalarExpr};
13
14pub fn projection_columns(projections: &[ProjectionPlan]) -> Vec<String> {
15 projections.iter().map(projection_label_at).collect()
16}
17
18pub fn query_output_position(position: i64, width: usize, clause: &str) -> Result<usize, SQLError> {
20 usize::try_from(position)
21 .ok()
22 .and_then(|position| position.checked_sub(1))
23 .filter(|position| *position < width)
24 .ok_or_else(|| output_position_error(clause, position))
25}
26
27pub fn output_position_error(clause: &str, position: i64) -> SQLError {
28 SQLError::Routine {
29 sqlstate: "42P10".into(),
30 message: format!("{clause} position {position} is not in the select list"),
31 }
32}
33
34pub fn projection_label_at(proj: &ProjectionPlan) -> String {
36 if let Some(a) = &proj.alias {
37 return a.clone();
38 }
39 match &proj.expr {
40 ScalarExpr::Column(c) => c.clone(),
41 ScalarExpr::QualifiedColumn { column, .. } => column.clone(),
42 ScalarExpr::Star | ScalarExpr::QualifiedStar(_) => "*".into(),
43 ScalarExpr::Func {
44 name,
45 binding,
46 args,
47 ..
48 } => field_selection_label(binding.as_ref(), args.get(1))
49 .unwrap_or_else(|| function_projection_label(name, binding.as_ref())),
50 ScalarExpr::WindowCall { name, .. } => function_projection_label(name, None),
51 _ => "?column?".into(),
52 }
53}
54
55pub(crate) fn field_selection_label<E: FieldNameLiteral>(
57 binding: Option<&crate::ast::FunctionBinding>,
58 field: Option<&E>,
59) -> Option<String> {
60 (binding.and_then(|binding| binding.dispatch)
61 == Some(crate::ast::FunctionDispatch::FieldSelect))
62 .then(|| field.and_then(FieldNameLiteral::field_name))
63 .flatten()
64 .map(str::to_owned)
65}
66
67pub(crate) trait FieldNameLiteral {
69 fn field_name(&self) -> Option<&str>;
70}
71
72impl FieldNameLiteral for ScalarExpr {
73 fn field_name(&self) -> Option<&str> {
74 match self {
75 ScalarExpr::Literal(uqa_core::Value::Str(field)) => Some(field),
76 _ => None,
77 }
78 }
79}
80
81impl FieldNameLiteral for crate::ast::Expr {
82 fn field_name(&self) -> Option<&str> {
83 match self {
84 crate::ast::Expr::Literal(uqa_core::Value::Str(field)) => Some(field),
85 _ => None,
86 }
87 }
88}
89
90pub fn function_projection_label(
91 name: &str,
92 binding: Option<&crate::ast::FunctionBinding>,
93) -> String {
94 if matches!(
96 binding.and_then(|binding| binding.dispatch),
97 Some(
98 crate::ast::FunctionDispatch::NumericOperator(_)
99 | crate::ast::FunctionDispatch::ArrayConcat
100 | crate::ast::FunctionDispatch::ArrayAppend
101 | crate::ast::FunctionDispatch::ArrayPrepend
102 | crate::ast::FunctionDispatch::IsDistinct
103 | crate::ast::FunctionDispatch::AnyOperator
104 | crate::ast::FunctionDispatch::AllOperator
105 | crate::ast::FunctionDispatch::BetweenSymmetric
106 | crate::ast::FunctionDispatch::JsonExtract { .. }
107 )
108 ) {
109 return "?column?".into();
110 }
111 let name = crate::parse_regobject_name(name)
112 .and_then(|mut names| names.pop())
113 .unwrap_or_else(|| name.to_string());
114 if operator_call(&name) {
115 return "?column?".into();
116 }
117 name
118}
119
120fn operator_call(name: &str) -> bool {
122 matches!(
123 name,
124 "concat_op"
125 | "fts_match"
126 | "jsonpath_exists"
127 | "contains_op"
128 | "contained_by_op"
129 | "array_overlap"
130 | "json_has_key"
131 | "json_has_any_key"
132 | "json_has_all_keys"
133 | "json_delete_path"
134 | "like"
135 | "ilike"
136 | "similar_to"
137 | "regex_match_op"
138 | "regex_imatch_op"
139 )
140}
141
142pub fn expand_from_star_columns(
143 columns: Vec<String>,
144 projections: &[ProjectionPlan],
145 source_schema: &RowSchema,
146) -> Result<Vec<String>, SQLError> {
147 let mut output = Vec::new();
148 for (position, projection) in projections.iter().enumerate() {
149 match &projection.expr {
150 ScalarExpr::Star => {
151 output.extend(
152 source_schema
153 .columns()
154 .iter()
155 .enumerate()
156 .filter(|(position, _)| {
157 visible_projection_source_position(source_schema, *position)
158 })
159 .map(|(source_position, column)| {
160 source_schema
161 .public_name(source_position)
162 .unwrap_or(column)
163 .to_string()
164 }),
165 );
166 }
167 ScalarExpr::QualifiedStar(qualifier) => {
168 let qualified_columns = source_schema
169 .qualified_star_position_layout(qualifier)
170 .into_iter()
171 .filter(|(_, logical, _, _)| {
172 logical.is_none_or(|position| {
173 visible_projection_source_position(source_schema, position)
174 })
175 })
176 .map(|(column, _, _, _)| column)
177 .collect::<Vec<_>>();
178 if qualified_columns.is_empty() {
179 return Err(SQLError::UnknownTable(qualifier.clone()));
180 }
181 output.extend(qualified_columns);
182 }
183 _ => output.push(columns[position].clone()),
184 }
185 }
186 Ok(output)
187}
188
189pub fn bound_projection_expression(schema: &RowSchema, position: usize) -> ScalarExpr {
190 let Some(identity) = schema.identity(position) else {
191 return ScalarExpr::Position(position);
192 };
193 if let Some(qualifier) = identity.qualifier() {
194 if schema.qualified_position(qualifier, identity.column()) == Some(position) {
195 return ScalarExpr::qualified_column(qualifier, identity.column());
196 }
197 } else if schema.unqualified_position(identity.column()) == Some(position) {
198 return ScalarExpr::Column(identity.column().to_string());
199 }
200 ScalarExpr::Position(position)
201}
202
203pub fn expand_bound_projection_stars(
204 projections: &[ProjectionPlan],
205 schema: &RowSchema,
206) -> Result<Vec<ProjectionPlan>, SQLError> {
207 let mut expanded = Vec::new();
208 for projection in projections {
209 match &projection.expr {
210 ScalarExpr::Star => {
211 for (position, column) in schema.columns().iter().enumerate() {
212 if !visible_projection_source_position(schema, position) {
213 continue;
214 }
215 expanded.push(ProjectionPlan {
216 expr: bound_projection_expression(schema, position),
217 alias: Some(schema.public_name(position).unwrap_or(column).to_string()),
218 });
219 }
220 }
221 ScalarExpr::QualifiedStar(qualifier) => {
222 let layout = schema.qualified_star_position_layout(qualifier);
223 if layout.is_empty() {
224 return Err(SQLError::UnknownTable(qualifier.clone()));
225 }
226 for (column, logical, _, _) in layout {
227 if logical.is_some_and(|position| {
228 !visible_projection_source_position(schema, position)
229 }) {
230 continue;
231 }
232 expanded.push(ProjectionPlan {
233 expr: logical.map_or_else(
234 || ScalarExpr::qualified_column(qualifier, &column),
235 |position| bound_projection_expression(schema, position),
236 ),
237 alias: Some(column),
238 });
239 }
240 }
241 _ => expanded.push(projection.clone()),
242 }
243 }
244 Ok(expanded)
245}
246
247pub fn visible_projection_source_position(schema: &RowSchema, position: usize) -> bool {
248 schema.wildcard_position_visible(position)
249}
250
251pub fn returning_context_schema(
252 columns: &[String],
253 types: &[Option<crate::ast::ColumnType>],
254 composite_width: usize,
255 target_qualifier: &str,
256 aliases: &ReturningAliases,
257) -> RowSchema {
258 let target =
259 RowSchema::with_qualified_types(target_qualifier, columns.to_vec(), types.to_vec());
260 let target = RowSchema::with_wildcard_hidden_positions(&target, composite_width..columns.len());
261 let hidden_types = types
262 .iter()
263 .cloned()
264 .chain(types.iter().cloned())
265 .collect::<Vec<_>>();
266 let schema = RowSchema::append_hidden_typed(&target, &hidden_types);
267 let width = columns.len();
268 let identity_aliases = columns
269 .iter()
270 .enumerate()
271 .flat_map(|(position, column)| {
272 [
273 (
274 ColumnIdentity::qualified(&aliases.old, column),
275 width + position,
276 types[position].clone(),
277 ),
278 (
279 ColumnIdentity::qualified(&aliases.new, column),
280 width * 2 + position,
281 types[position].clone(),
282 ),
283 ]
284 })
285 .collect::<Vec<_>>();
286 RowSchema::with_physical_identity_aliases(&schema, &identity_aliases)
287}
288
289pub fn returning_expression_schema(
290 target: &RowSchema,
291 target_qualifier: &str,
292 aliases: &ReturningAliases,
293 supplemental: Option<&RowSchema>,
294) -> RowSchema {
295 let composite_width = target.len();
296 let (columns, types) = target_with_system_columns(target);
297 let target =
298 returning_context_schema(&columns, &types, composite_width, target_qualifier, aliases);
299 supplemental.map_or(target.clone(), |source| {
300 RowSchema::join(&target, source, std::iter::empty())
301 })
302}
303
304#[must_use]
306pub fn mutation_clause_schema(
307 target: &RowSchema,
308 target_qualifier: &str,
309 supplemental: Option<&RowSchema>,
310) -> RowSchema {
311 let composite_width = target.len();
312 let (columns, types) = target_with_system_columns(target);
313 let width = columns.len();
314 let target = RowSchema::with_wildcard_hidden_positions(
315 &RowSchema::with_qualified_types(target_qualifier, columns, types),
316 composite_width..width,
317 );
318 supplemental.map_or(target.clone(), |source| {
319 RowSchema::join(&target, source, std::iter::empty())
320 })
321}
322
323fn target_with_system_columns(
325 target: &RowSchema,
326) -> (Vec<String>, Vec<Option<crate::ast::ColumnType>>) {
327 let mut columns = target.columns().to_vec();
328 let mut types = target.column_types().to_vec();
329 if !columns.iter().any(|column| column == DOC_ID_COLUMN) {
330 columns.push(DOC_ID_COLUMN.into());
331 types.push(Some(crate::ast::ColumnType::BigInteger));
332 }
333 columns.push(TABLE_OID_COLUMN.into());
334 types.push(Some(crate::ast::ColumnType::Oid));
335 columns.push(crate::semantics::XMIN_COLUMN.into());
336 types.push(Some(crate::ast::ColumnType::Xid));
337 (columns, types)
338}
339
340pub fn query_plan_output_columns(plan: &crate::plan::QueryPlan) -> Option<Vec<String>> {
341 match &plan.root {
342 crate::plan::RelationalPlan::QueryBlock(block) => {
343 Some(projection_columns(&block.projections))
344 }
345 crate::plan::RelationalPlan::SetOp { left, .. } => query_plan_output_columns(left),
346 crate::plan::RelationalPlan::Values { rows, .. } => rows.first().map(|row| {
347 (1..=row.len())
348 .map(|index| format!("column{index}"))
349 .collect()
350 }),
351 }
352}
353
354pub fn should_defer_distinct_limit(stmt: &crate::plan::QueryBlockPlan) -> bool {
355 stmt.distinct && (stmt.limit.is_some() || stmt.offset.is_some())
356}
357
358pub fn select_execution_stmt(
359 stmt: &crate::plan::QueryBlockPlan,
360 defer_distinct_limit: bool,
361) -> crate::plan::QueryBlockPlan {
362 if !defer_distinct_limit {
363 return stmt.clone();
364 }
365 let mut exec_stmt = stmt.clone();
366 exec_stmt.limit = None;
367 exec_stmt.offset = None;
368 exec_stmt
369}