use std::collections::HashSet;
use std::sync::Arc;
use surrealdb_types::ToSql;
use super::super::Planner;
use super::super::util::{derive_field_name, idiom_to_field_name, idiom_to_field_path};
use crate::err::Error;
use crate::exec::expression_registry::{ComputePoint, ExpressionRegistry};
use crate::exec::operators::{
Compute, FieldSelection, Project, ProjectValue, Projection, SelectProject,
};
use crate::exec::{ExecOperator, OperatorMetrics};
use crate::expr::field::{Field, Fields};
use crate::expr::{Expr, Idiom};
pub(super) enum ProjectedField {
All,
Include(String),
Rename {
from: String,
to: String,
},
Compute {
expr_key: String,
physical: Arc<dyn crate::exec::PhysicalExpr>,
output_name: String,
},
Fallback(&'static str),
}
impl<'ctx> Planner<'ctx> {
pub(crate) async fn plan_projections(
&self,
fields: Fields,
omit: Vec<Expr>,
input: Arc<dyn ExecOperator>,
) -> Result<Arc<dyn ExecOperator>, Error> {
match fields {
Fields::Value(selector) => {
let omit_fields = if !omit.is_empty() {
self.plan_omit(omit).await?
} else {
vec![]
};
let expr = self.physical_expr(selector.expr).await?;
Ok(Arc::new(ProjectValue::new(input, expr, omit_fields)) as Arc<dyn ExecOperator>)
}
Fields::Select(field_list) => {
let is_select_all =
field_list.len() == 1 && matches!(field_list.first(), Some(Field::All));
if is_select_all {
let omit_fields = if !omit.is_empty() {
self.plan_omit(omit).await?
} else {
vec![]
};
return Ok(Arc::new(Project::new(input, vec![], omit_fields, true))
as Arc<dyn ExecOperator>);
}
let has_wildcard = field_list.iter().any(|f| matches!(f, Field::All));
let mut field_selections = Vec::with_capacity(field_list.len());
for field in field_list {
if let Field::Single(selector) = field {
let field_selection = if let Some(alias) = &selector.alias {
let output_path = idiom_to_field_path(alias);
let expr = self.physical_expr(selector.expr).await?;
FieldSelection::with_alias_path(output_path, expr)
} else {
let output_name_or_path = match &selector.expr {
Expr::Idiom(idiom) => Ok(idiom_to_field_path(idiom)),
_ => Err(derive_field_name(&selector.expr)),
};
let expr = self.physical_expr(selector.expr).await?;
match output_name_or_path {
Ok(output_path) => {
FieldSelection::from_field_path(output_path, expr)
}
Err(output_name) => FieldSelection::new(output_name.as_str(), expr),
}
};
field_selections.push(field_selection);
}
}
let omit_fields = if !omit.is_empty() {
self.plan_omit(omit).await?
} else {
vec![]
};
Ok(Arc::new(Project::new(input, field_selections, omit_fields, has_wildcard))
as Arc<dyn ExecOperator>)
}
}
}
pub(crate) async fn plan_projections_fast(
&self,
fields: Fields,
omit: Vec<Expr>,
input: Arc<dyn ExecOperator>,
registry: &mut ExpressionRegistry,
) -> Result<Arc<dyn ExecOperator>, Error> {
match fields {
Fields::Value(selector) => {
let omit_fields = if !omit.is_empty() {
self.plan_omit(omit).await?
} else {
vec![]
};
if let Some(ref alias) = selector.alias
&& alias.len() == 1
&& let Some(crate::expr::part::Part::Field(name)) = alias.first()
&& registry.contains_name(name)
&& !omit_fields.iter().any(|f| {
f.len() == 1
&& matches!(f.first(), Some(crate::expr::part::Part::Field(n)) if n == name)
}) {
let idiom = Idiom(vec![crate::expr::part::Part::Field(name.clone())]);
let expr = self.physical_expr(Expr::Idiom(idiom)).await?;
return Ok(Arc::new(ProjectValue::new(input, expr, omit_fields))
as Arc<dyn ExecOperator>);
}
let expr = self.physical_expr(selector.expr).await?;
Ok(Arc::new(ProjectValue::new(input, expr, omit_fields)) as Arc<dyn ExecOperator>)
}
Fields::Select(ref field_list) => {
let is_select_all =
field_list.len() == 1 && matches!(field_list.first(), Some(Field::All));
if is_select_all {
if Self::has_complex_omit(&omit) {
return self.plan_projections(fields, omit, input).await;
}
let mut projections = vec![Projection::All];
for expr in &omit {
if let Expr::Idiom(idiom) = expr {
projections.push(Projection::Omit(idiom_to_field_name(idiom).into()));
}
}
return Ok(Arc::new(SelectProject::new(
input,
projections,
Arc::new(OperatorMetrics::new()),
)) as Arc<dyn ExecOperator>);
}
let has_wildcard = field_list.iter().any(|f| matches!(f, Field::All));
if Self::has_complex_omit(&omit) {
return self.plan_projections(fields, omit, input).await;
}
let mut classifications = Vec::with_capacity(field_list.len());
for field in field_list {
classifications.push(self.classify_select_field(field).await?);
}
if let Some(reason) = classifications.iter().find_map(|c| match c {
ProjectedField::Fallback(r) => Some(*r),
_ => None,
}) {
tracing::debug!(
reason = %reason,
"SELECT projection fast path declined; using full Project operator",
);
return self.plan_projections(fields, omit, input).await;
}
let simple_source_fields: HashSet<String> = classifications
.iter()
.filter_map(|c| match c {
ProjectedField::Include(s) => Some(s.clone()),
ProjectedField::Rename {
from,
..
} => Some(from.clone()),
_ => None,
})
.collect();
let project_shadow = if simple_source_fields.is_empty() {
false
} else {
let mut simulated_registry = registry.clone();
classifications.iter().any(|c| match c {
ProjectedField::Compute {
expr_key,
physical,
output_name,
} => {
let internal_name = simulated_registry.register_physical(
expr_key.clone(),
Arc::clone(physical),
ComputePoint::Project,
Some(output_name.clone()),
);
simple_source_fields.contains(&internal_name)
}
_ => false,
})
};
let sort_shadow = !simple_source_fields.is_empty()
&& registry
.get_expressions_for_point(ComputePoint::Sort)
.iter()
.any(|(name, _)| simple_source_fields.contains(name));
if project_shadow || sort_shadow {
return self.plan_projections(fields, omit, input).await;
}
let mut projections = Vec::with_capacity(
classifications.len() + omit.len() + usize::from(has_wildcard),
);
if has_wildcard {
projections.push(Projection::All);
}
for classification in classifications {
match classification {
ProjectedField::All => {} ProjectedField::Include(name) => {
projections.push(Projection::Include(name.into()));
}
ProjectedField::Rename {
from,
to,
} => {
if from == to {
projections.push(Projection::Include(to.into()));
} else {
projections.push(Projection::Rename {
from: from.into(),
to: to.into(),
});
}
}
ProjectedField::Compute {
expr_key,
physical,
output_name,
} => {
Self::register_and_push_projection(
&mut projections,
registry,
expr_key,
physical,
output_name,
);
}
ProjectedField::Fallback(reason) => {
debug_assert!(
false,
"Fallback should have been filtered before commit phase: {reason}",
);
return self.plan_projections(fields, omit, input).await;
}
}
}
for expr in &omit {
if let Expr::Idiom(idiom) = expr {
projections.push(Projection::Omit(idiom_to_field_name(idiom).into()));
}
}
let computed = if registry.has_expressions_for_point(ComputePoint::Project) {
let compute_fields = registry
.get_expressions_for_point(ComputePoint::Project)
.into_iter()
.map(|(name, expr)| (crate::val::Strand::new(name), expr))
.collect();
Arc::new(Compute::new(input, compute_fields)) as Arc<dyn ExecOperator>
} else {
input
};
Ok(Arc::new(SelectProject::new(
computed,
projections,
Arc::new(OperatorMetrics::new()),
)) as Arc<dyn ExecOperator>)
}
}
}
async fn classify_select_field(&self, field: &Field) -> Result<ProjectedField, Error> {
let selector = match field {
Field::All => return Ok(ProjectedField::All),
Field::Single(s) => s,
};
let physical = self.physical_expr(selector.expr.clone()).await?;
if physical.is_projection_function() {
return Ok(ProjectedField::Fallback("projection function"));
}
if let Some(alias) = &selector.alias {
if alias.0.len() > 1 {
return Ok(ProjectedField::Fallback("multi-part alias"));
}
let output_name = idiom_to_field_name(alias);
if let Some(field_name) = physical.try_simple_field() {
return Ok(ProjectedField::Rename {
from: field_name.to_string(),
to: output_name,
});
}
Ok(ProjectedField::Compute {
expr_key: selector.expr.to_sql(),
physical,
output_name,
})
} else {
if let Some(field_name) = physical.try_simple_field() {
return Ok(ProjectedField::Include(field_name.to_string()));
}
if let Expr::Idiom(idiom) = &selector.expr {
let path = idiom_to_field_path(idiom);
if path.len() > 1 {
return Ok(ProjectedField::Fallback("multi-part output idiom"));
}
return Ok(ProjectedField::Compute {
expr_key: selector.expr.to_sql(),
physical,
output_name: idiom_to_field_name(idiom),
});
}
Ok(ProjectedField::Compute {
expr_key: selector.expr.to_sql(),
physical,
output_name: derive_field_name(&selector.expr),
})
}
}
fn register_and_push_projection(
projections: &mut Vec<Projection>,
registry: &mut ExpressionRegistry,
expr_key: String,
physical: Arc<dyn crate::exec::PhysicalExpr>,
output_name: String,
) {
let internal_name = registry.register_physical(
expr_key,
physical,
ComputePoint::Project,
Some(output_name.clone()),
);
if internal_name == output_name {
projections.push(Projection::Include(output_name.into()));
} else {
projections.push(Projection::Rename {
from: internal_name.into(),
to: output_name.into(),
});
}
}
fn has_complex_omit(omit: &[Expr]) -> bool {
omit.iter().any(|e| {
if let Expr::Idiom(idiom) = e {
idiom.len() > 1
} else {
true
}
})
}
pub(crate) async fn plan_omit(
&self,
omit: Vec<Expr>,
) -> Result<Vec<crate::expr::idiom::Idiom>, Error> {
let mut fields = Vec::with_capacity(omit.len());
for expr in omit {
let mut idioms = self.resolve_field_idioms(expr).await?;
fields.append(&mut idioms);
}
Ok(fields)
}
}