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 projection_label_at(proj: &ProjectionPlan) -> String {
20 if let Some(a) = &proj.alias {
21 return a.clone();
22 }
23 match &proj.expr {
24 ScalarExpr::Column(c) => c.clone(),
25 ScalarExpr::QualifiedColumn { column, .. } => column.clone(),
26 ScalarExpr::Star | ScalarExpr::QualifiedStar(_) => "*".into(),
27 ScalarExpr::Func { name, binding, .. } => function_projection_label(name, binding.as_ref()),
28 _ => "?column?".into(),
29 }
30}
31
32pub(crate) fn function_projection_label(
33 name: &str,
34 binding: Option<&crate::ast::FunctionBinding>,
35) -> String {
36 if matches!(
37 binding.and_then(|binding| binding.dispatch),
38 Some(crate::ast::FunctionDispatch::NumericOperator(_))
39 ) {
40 return "?column?".into();
41 }
42 crate::parse_regobject_name(name)
43 .and_then(|mut names| names.pop())
44 .unwrap_or_else(|| name.to_string())
45}
46
47pub fn expand_from_star_columns(
48 columns: Vec<String>,
49 projections: &[ProjectionPlan],
50 source_schema: &RowSchema,
51) -> Result<Vec<String>, SQLError> {
52 let mut output = Vec::new();
53 for (position, projection) in projections.iter().enumerate() {
54 match &projection.expr {
55 ScalarExpr::Star => {
56 output.extend(
57 source_schema
58 .columns()
59 .iter()
60 .enumerate()
61 .filter(|(position, _)| {
62 visible_projection_source_position(source_schema, *position)
63 })
64 .map(|(source_position, column)| {
65 source_schema
66 .public_name(source_position)
67 .unwrap_or(column)
68 .to_string()
69 }),
70 );
71 }
72 ScalarExpr::QualifiedStar(qualifier) => {
73 let qualified_columns = source_schema
74 .qualified_star_position_layout(qualifier)
75 .into_iter()
76 .filter(|(_, logical, _, _)| {
77 logical.is_none_or(|position| {
78 visible_projection_source_position(source_schema, position)
79 })
80 })
81 .map(|(column, _, _, _)| column)
82 .collect::<Vec<_>>();
83 if qualified_columns.is_empty() {
84 return Err(SQLError::UnknownTable(qualifier.clone()));
85 }
86 output.extend(qualified_columns);
87 }
88 _ => output.push(columns[position].clone()),
89 }
90 }
91 Ok(output)
92}
93
94pub fn bound_projection_expression(schema: &RowSchema, position: usize) -> ScalarExpr {
95 let Some(identity) = schema.identity(position) else {
96 return ScalarExpr::Position(position);
97 };
98 if let Some(qualifier) = identity.qualifier() {
99 if schema.qualified_position(qualifier, identity.column()) == Some(position) {
100 return ScalarExpr::qualified_column(qualifier, identity.column());
101 }
102 } else if schema.unqualified_position(identity.column()) == Some(position) {
103 return ScalarExpr::Column(identity.column().to_string());
104 }
105 ScalarExpr::Position(position)
106}
107
108pub fn expand_bound_projection_stars(
109 projections: &[ProjectionPlan],
110 schema: &RowSchema,
111) -> Result<Vec<ProjectionPlan>, SQLError> {
112 let mut expanded = Vec::new();
113 for projection in projections {
114 match &projection.expr {
115 ScalarExpr::Star => {
116 for (position, column) in schema.columns().iter().enumerate() {
117 if !visible_projection_source_position(schema, position) {
118 continue;
119 }
120 expanded.push(ProjectionPlan {
121 expr: bound_projection_expression(schema, position),
122 alias: Some(schema.public_name(position).unwrap_or(column).to_string()),
123 });
124 }
125 }
126 ScalarExpr::QualifiedStar(qualifier) => {
127 let layout = schema.qualified_star_position_layout(qualifier);
128 if layout.is_empty() {
129 return Err(SQLError::UnknownTable(qualifier.clone()));
130 }
131 for (column, logical, _, _) in layout {
132 if logical.is_some_and(|position| {
133 !visible_projection_source_position(schema, position)
134 }) {
135 continue;
136 }
137 expanded.push(ProjectionPlan {
138 expr: logical.map_or_else(
139 || ScalarExpr::qualified_column(qualifier, &column),
140 |position| bound_projection_expression(schema, position),
141 ),
142 alias: Some(column),
143 });
144 }
145 }
146 _ => expanded.push(projection.clone()),
147 }
148 }
149 Ok(expanded)
150}
151
152pub fn visible_projection_source_position(schema: &RowSchema, position: usize) -> bool {
153 schema.wildcard_position_visible(position)
154}
155
156pub fn returning_context_schema(
157 columns: &[String],
158 types: &[Option<crate::ast::ColumnType>],
159 composite_width: usize,
160 target_qualifier: &str,
161 aliases: &ReturningAliases,
162) -> RowSchema {
163 let target =
164 RowSchema::with_qualified_types(target_qualifier, columns.to_vec(), types.to_vec());
165 let target = RowSchema::with_wildcard_hidden_positions(&target, composite_width..columns.len());
166 let hidden_types = types
167 .iter()
168 .cloned()
169 .chain(types.iter().cloned())
170 .collect::<Vec<_>>();
171 let schema = RowSchema::append_hidden_typed(&target, &hidden_types);
172 let width = columns.len();
173 let identity_aliases = columns
174 .iter()
175 .enumerate()
176 .flat_map(|(position, column)| {
177 [
178 (
179 ColumnIdentity::qualified(&aliases.old, column),
180 width + position,
181 types[position].clone(),
182 ),
183 (
184 ColumnIdentity::qualified(&aliases.new, column),
185 width * 2 + position,
186 types[position].clone(),
187 ),
188 ]
189 })
190 .collect::<Vec<_>>();
191 RowSchema::with_physical_identity_aliases(&schema, &identity_aliases)
192}
193
194pub fn returning_expression_schema(
195 target: &RowSchema,
196 target_qualifier: &str,
197 aliases: &ReturningAliases,
198 supplemental: Option<&RowSchema>,
199) -> RowSchema {
200 let composite_width = target.len();
201 let mut columns = target.columns().to_vec();
202 let mut types = target.column_types().to_vec();
203 if !columns.iter().any(|column| column == DOC_ID_COLUMN) {
204 columns.push(DOC_ID_COLUMN.into());
205 types.push(Some(crate::ast::ColumnType::BigInteger));
206 }
207 columns.push(TABLE_OID_COLUMN.into());
208 types.push(Some(crate::ast::ColumnType::Oid));
209 columns.push(crate::semantics::XMIN_COLUMN.into());
210 types.push(Some(crate::ast::ColumnType::Xid));
211 let target =
212 returning_context_schema(&columns, &types, composite_width, target_qualifier, aliases);
213 supplemental.map_or(target.clone(), |source| {
214 RowSchema::join(&target, source, std::iter::empty())
215 })
216}
217
218pub fn query_plan_output_columns(plan: &crate::plan::QueryPlan) -> Option<Vec<String>> {
219 match &plan.root {
220 crate::plan::RelationalPlan::QueryBlock(block) => {
221 Some(projection_columns(&block.projections))
222 }
223 crate::plan::RelationalPlan::SetOp { left, .. } => query_plan_output_columns(left),
224 crate::plan::RelationalPlan::Values { rows, .. } => rows.first().map(|row| {
225 (1..=row.len())
226 .map(|index| format!("column{index}"))
227 .collect()
228 }),
229 }
230}
231
232pub fn should_defer_distinct_limit(stmt: &crate::plan::QueryBlockPlan) -> bool {
233 stmt.distinct && (stmt.limit.is_some() || stmt.offset.is_some())
234}
235
236pub fn select_execution_stmt(
237 stmt: &crate::plan::QueryBlockPlan,
238 defer_distinct_limit: bool,
239) -> crate::plan::QueryBlockPlan {
240 if !defer_distinct_limit {
241 return stmt.clone();
242 }
243 let mut exec_stmt = stmt.clone();
244 exec_stmt.limit = None;
245 exec_stmt.offset = None;
246 exec_stmt
247}