use crate::{
db::query::plan::expr::ast::{Alias, BinaryOp, Expr, FieldId, parse_supported_order_expr},
model::entity::{EntityModel, resolve_field_slot},
value::Value,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum ProjectionSelection {
All,
Fields(Vec<FieldId>),
Exprs(Vec<ProjectionField>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum ProjectionField {
Scalar { expr: Expr, alias: Option<Alias> },
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub(crate) struct ProjectionSpec {
fields: Vec<ProjectionField>,
}
impl ProjectionSpec {
#[must_use]
pub(in crate::db::query::plan) const fn new(fields: Vec<ProjectionField>) -> Self {
Self { fields }
}
#[must_use]
#[cfg(test)]
pub(in crate::db) const fn from_fields_for_test(fields: Vec<ProjectionField>) -> Self {
Self::new(fields)
}
#[must_use]
pub(crate) const fn len(&self) -> usize {
self.fields.len()
}
pub(crate) fn fields(&self) -> std::slice::Iter<'_, ProjectionField> {
self.fields.iter()
}
}
#[must_use]
pub(crate) const fn projection_field_expr(field: &ProjectionField) -> &Expr {
match field {
ProjectionField::Scalar { expr, .. } => expr,
}
}
#[must_use]
#[cfg(not(test))]
pub(in crate::db) const fn projection_field_direct_field_name(
field: &ProjectionField,
) -> Option<&str> {
direct_projection_expr_field_name(projection_field_expr(field))
}
#[must_use]
#[cfg(test)]
pub(in crate::db) fn projection_field_direct_field_name(field: &ProjectionField) -> Option<&str> {
direct_projection_expr_field_name(projection_field_expr(field))
}
#[must_use]
#[cfg(not(test))]
pub(in crate::db) const fn direct_projection_expr_field_name(expr: &Expr) -> Option<&str> {
match expr {
Expr::Field(field) => Some(field.as_str()),
Expr::Literal(_) | Expr::FunctionCall { .. } | Expr::Aggregate(_) | Expr::Binary { .. } => {
None
}
}
}
#[must_use]
#[cfg(test)]
pub(in crate::db) fn direct_projection_expr_field_name(expr: &Expr) -> Option<&str> {
match expr {
Expr::Field(field) => Some(field.as_str()),
Expr::Alias { expr, .. } => direct_projection_expr_field_name(expr.as_ref()),
Expr::Literal(_)
| Expr::FunctionCall { .. }
| Expr::Aggregate(_)
| Expr::Unary { .. }
| Expr::Binary { .. } => None,
}
}
#[must_use]
pub(crate) fn collect_unique_direct_projection_slots<'a>(
model: &EntityModel,
field_names: impl IntoIterator<Item = &'a str>,
) -> Option<Vec<usize>> {
let mut field_slots = Vec::new();
for field_name in field_names {
let slot = resolve_field_slot(model, field_name)?;
if field_slots.contains(&slot) {
return None;
}
field_slots.push(slot);
}
Some(field_slots)
}
#[must_use]
pub(crate) fn expr_references_only_fields(expr: &Expr, allowed: &[&str]) -> bool {
match expr {
Expr::Field(field) => allowed.iter().any(|allowed| *allowed == field.as_str()),
Expr::Aggregate(_) => true,
Expr::Literal(_) => true,
Expr::FunctionCall { args, .. } => args
.iter()
.all(|arg| expr_references_only_fields(arg, allowed)),
#[cfg(test)]
Expr::Alias { expr, .. } => expr_references_only_fields(expr.as_ref(), allowed),
#[cfg(test)]
Expr::Unary { expr, .. } => expr_references_only_fields(expr.as_ref(), allowed),
Expr::Binary { left, right, .. } => {
expr_references_only_fields(left.as_ref(), allowed)
&& expr_references_only_fields(right.as_ref(), allowed)
}
}
}
#[must_use]
pub(crate) fn order_term_preserves_group_field_order(
term: &str,
expected_group_field: &str,
) -> bool {
parse_supported_order_expr(term)
.is_some_and(|expr| order_expr_preserves_group_field_order(&expr, expected_group_field))
}
fn order_expr_preserves_group_field_order(expr: &Expr, expected_group_field: &str) -> bool {
match expr {
Expr::Field(field) => field.as_str() == expected_group_field,
Expr::Binary { op, left, right }
if matches!(op, BinaryOp::Add | BinaryOp::Sub)
&& matches!(
left.as_ref(),
Expr::Field(field) if field.as_str() == expected_group_field
)
&& is_numeric_order_offset_literal(right.as_ref()) =>
{
true
}
Expr::Literal(_) | Expr::FunctionCall { .. } | Expr::Aggregate(_) | Expr::Binary { .. } => {
false
}
#[cfg(test)]
Expr::Alias { .. } | Expr::Unary { .. } => false,
}
}
const fn is_numeric_order_offset_literal(expr: &Expr) -> bool {
matches!(
expr,
Expr::Literal(
Value::Int(_)
| Value::Int128(_)
| Value::IntBig(_)
| Value::Uint(_)
| Value::Uint128(_)
| Value::UintBig(_)
| Value::Decimal(_)
| Value::Float32(_)
| Value::Float64(_)
)
)
}