use crate::{
db::{
query::plan::{AggregateKind, expr::Function},
sql::identifier::split_qualified_identifier,
},
value::Value,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlStatement {
Select(SqlSelectStatement),
Delete(SqlDeleteStatement),
Insert(SqlInsertStatement),
Update(SqlUpdateStatement),
Explain(SqlExplainStatement),
Describe(SqlDescribeStatement),
ShowIndexes(SqlShowIndexesStatement),
ShowColumns(SqlShowColumnsStatement),
ShowEntities(SqlShowEntitiesStatement),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlProjection {
All,
Items(Vec<SqlSelectItem>),
}
impl SqlProjection {
#[must_use]
pub(in crate::db) fn is_already_local_scalar(&self) -> bool {
match self {
Self::All => true,
Self::Items(items) => items.iter().all(SqlSelectItem::is_already_local_projection),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlSelectItem {
Field(String),
Aggregate(SqlAggregateCall),
Expr(SqlExpr),
}
impl SqlSelectItem {
#[must_use]
pub(crate) fn contains_aggregate(&self) -> bool {
match self {
Self::Field(_) => false,
Self::Aggregate(_) => true,
Self::Expr(expr) => expr.contains_aggregate(),
}
}
#[must_use]
pub(in crate::db) fn is_already_local_projection(&self) -> bool {
match self {
Self::Field(field) => SqlExpr::identifier_is_already_local(field.as_str()),
Self::Aggregate(aggregate) => aggregate.is_already_local_scalar(),
Self::Expr(_) => false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlExprUnaryOp {
Not,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlExprBinaryOp {
Or,
And,
Eq,
Ne,
Lt,
Lte,
Gt,
Gte,
Add,
Sub,
Mul,
Div,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlCaseArm {
pub(crate) condition: SqlExpr,
pub(crate) result: SqlExpr,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlExpr {
Field(String),
Aggregate(SqlAggregateCall),
Literal(Value),
Param {
index: usize,
},
Membership {
expr: Box<Self>,
values: Vec<Value>,
negated: bool,
},
NullTest {
expr: Box<Self>,
negated: bool,
},
FunctionCall {
function: SqlScalarFunction,
args: Vec<Self>,
},
Unary {
op: SqlExprUnaryOp,
expr: Box<Self>,
},
Binary {
op: SqlExprBinaryOp,
left: Box<Self>,
right: Box<Self>,
},
Case {
arms: Vec<SqlCaseArm>,
else_expr: Option<Box<Self>>,
},
}
impl SqlExpr {
#[must_use]
pub(crate) fn from_select_item(item: &SqlSelectItem) -> Self {
match item {
SqlSelectItem::Field(field) => Self::Field(field.clone()),
SqlSelectItem::Aggregate(aggregate) => Self::Aggregate(aggregate.clone()),
SqlSelectItem::Expr(expr) => expr.clone(),
}
}
#[must_use]
pub(crate) fn contains_aggregate(&self) -> bool {
match self {
Self::Aggregate(_) => true,
Self::Field(_) | Self::Literal(_) | Self::Param { .. } => false,
Self::Membership { expr, .. }
| Self::NullTest { expr, .. }
| Self::Unary { expr, .. } => expr.contains_aggregate(),
Self::FunctionCall { args, .. } => args.iter().any(Self::contains_aggregate),
Self::Binary { left, right, .. } => {
left.contains_aggregate() || right.contains_aggregate()
}
Self::Case { arms, else_expr } => {
arms.iter().any(|arm| {
arm.condition.contains_aggregate() || arm.result.contains_aggregate()
}) || else_expr
.as_ref()
.is_some_and(|else_expr| else_expr.contains_aggregate())
}
}
}
#[must_use]
pub(in crate::db) fn is_already_local_scalar(&self) -> bool {
match self {
Self::Field(field) => Self::identifier_is_already_local(field.as_str()),
Self::Literal(_) | Self::Param { .. } => true,
Self::Membership { expr, values, .. } => {
expr.is_already_local_scalar()
&& values
.iter()
.all(|value| !matches!(value, Value::List(_) | Value::Map(_)))
}
Self::NullTest { expr, .. } | Self::Unary { expr, .. } => {
expr.is_already_local_scalar()
}
Self::Binary { left, right, .. } => {
left.is_already_local_scalar() && right.is_already_local_scalar()
}
Self::Aggregate(_) | Self::FunctionCall { .. } | Self::Case { .. } => false,
}
}
#[must_use]
pub(in crate::db) fn fields_are_already_local(&self) -> bool {
match self {
Self::Field(field) => Self::identifier_is_already_local(field.as_str()),
Self::Aggregate(aggregate) => aggregate.is_already_local_scalar(),
Self::Literal(_) | Self::Param { .. } => true,
Self::Membership { expr, .. }
| Self::NullTest { expr, .. }
| Self::Unary { expr, .. } => expr.fields_are_already_local(),
Self::FunctionCall { args, .. } => args.iter().all(Self::fields_are_already_local),
Self::Binary { left, right, .. } => {
left.fields_are_already_local() && right.fields_are_already_local()
}
Self::Case { arms, else_expr } => {
arms.iter().all(|arm| {
arm.condition.fields_are_already_local()
&& arm.result.fields_are_already_local()
}) && else_expr
.as_ref()
.is_none_or(|else_expr| else_expr.fields_are_already_local())
}
}
}
#[must_use]
pub(in crate::db) fn is_casefold_field_wrapper(&self) -> bool {
matches!(
self,
Self::FunctionCall {
function,
args,
} if function.is_casefold_transform() && matches!(args.as_slice(), [Self::Field(_)])
)
}
#[must_use]
pub(in crate::db) fn contains_omitted_else_case(&self) -> bool {
match self {
Self::Field(_) | Self::Aggregate(_) | Self::Literal(_) | Self::Param { .. } => false,
Self::Membership { expr, .. }
| Self::NullTest { expr, .. }
| Self::Unary { expr, .. } => expr.contains_omitted_else_case(),
Self::FunctionCall { args, .. } => args.iter().any(Self::contains_omitted_else_case),
Self::Binary { left, right, .. } => {
left.contains_omitted_else_case() || right.contains_omitted_else_case()
}
Self::Case { arms, else_expr } => {
else_expr.is_none()
|| arms.iter().any(|arm| {
arm.condition.contains_omitted_else_case()
|| arm.result.contains_omitted_else_case()
})
|| else_expr
.as_ref()
.is_some_and(|else_expr| else_expr.contains_omitted_else_case())
}
}
}
fn identifier_is_already_local(identifier: &str) -> bool {
split_qualified_identifier(identifier).is_none()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlAggregateKind {
Count,
Sum,
Avg,
Min,
Max,
}
impl SqlAggregateKind {
#[must_use]
pub(crate) const fn supports_star_input(self) -> bool {
matches!(self, Self::Count)
}
#[must_use]
pub(in crate::db) const fn lowers_shared_field_target_shape(self) -> bool {
!matches!(self, Self::Count)
}
#[must_use]
pub(in crate::db) const fn aggregate_kind(self) -> AggregateKind {
match self {
Self::Count => AggregateKind::Count,
Self::Sum => AggregateKind::Sum,
Self::Avg => AggregateKind::Avg,
Self::Min => AggregateKind::Min,
Self::Max => AggregateKind::Max,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlAggregateCall {
pub(crate) kind: SqlAggregateKind,
pub(crate) input: Option<Box<SqlExpr>>,
pub(crate) filter_expr: Option<Box<SqlExpr>>,
pub(crate) distinct: bool,
}
impl SqlAggregateCall {
#[must_use]
pub(in crate::db) fn is_already_local_scalar(&self) -> bool {
let input_is_local = self
.input
.as_deref()
.is_none_or(SqlExpr::is_already_local_scalar);
input_is_local
&& self
.filter_expr
.as_deref()
.is_none_or(SqlExpr::is_already_local_scalar)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[remain::sorted]
pub(crate) enum SqlScalarFunction {
Abs,
Ceiling,
Coalesce,
Contains,
EndsWith,
Floor,
Left,
Length,
Lower,
Ltrim,
NullIf,
Position,
Replace,
Right,
Round,
Rtrim,
StartsWith,
Substring,
Trim,
Upper,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlScalarFunctionCallShape {
BinaryExprArgs,
FieldPlusLiteral,
Position,
Replace,
RoundSpecial,
SharedScalarCall,
Substring,
UnaryExpr,
VariadicExprArgs,
WherePredicateExprPair,
}
impl SqlScalarFunction {
#[must_use]
pub(crate) const fn is_casefold_transform(self) -> bool {
matches!(self, Self::Lower | Self::Upper)
}
#[must_use]
pub(crate) const fn uses_round_special_case(self) -> bool {
matches!(self, Self::Round)
}
#[must_use]
pub(in crate::db) const fn planner_function(self) -> Function {
match self {
Self::Abs => Function::Abs,
Self::Ceiling => Function::Ceiling,
Self::Coalesce => Function::Coalesce,
Self::Contains => Function::Contains,
Self::EndsWith => Function::EndsWith,
Self::Floor => Function::Floor,
Self::Left => Function::Left,
Self::Length => Function::Length,
Self::Lower => Function::Lower,
Self::Ltrim => Function::Ltrim,
Self::NullIf => Function::NullIf,
Self::Position => Function::Position,
Self::Replace => Function::Replace,
Self::Right => Function::Right,
Self::Round => Function::Round,
Self::Rtrim => Function::Rtrim,
Self::StartsWith => Function::StartsWith,
Self::Substring => Function::Substring,
Self::Trim => Function::Trim,
Self::Upper => Function::Upper,
}
}
#[must_use]
pub(in crate::db) const fn non_where_call_shape(self) -> SqlScalarFunctionCallShape {
match self {
Self::Round => SqlScalarFunctionCallShape::RoundSpecial,
Self::Coalesce => SqlScalarFunctionCallShape::VariadicExprArgs,
Self::NullIf => SqlScalarFunctionCallShape::BinaryExprArgs,
Self::Trim
| Self::Ltrim
| Self::Rtrim
| Self::Abs
| Self::Ceiling
| Self::Floor
| Self::Lower
| Self::Upper
| Self::Length => SqlScalarFunctionCallShape::UnaryExpr,
Self::Left | Self::Right | Self::StartsWith | Self::EndsWith | Self::Contains => {
SqlScalarFunctionCallShape::FieldPlusLiteral
}
Self::Position => SqlScalarFunctionCallShape::Position,
Self::Replace => SqlScalarFunctionCallShape::Replace,
Self::Substring => SqlScalarFunctionCallShape::Substring,
}
}
#[must_use]
pub(in crate::db) const fn where_call_shape(self) -> SqlScalarFunctionCallShape {
match self {
Self::Round => SqlScalarFunctionCallShape::RoundSpecial,
Self::Coalesce => SqlScalarFunctionCallShape::VariadicExprArgs,
Self::NullIf => SqlScalarFunctionCallShape::BinaryExprArgs,
Self::StartsWith | Self::EndsWith | Self::Contains => {
SqlScalarFunctionCallShape::WherePredicateExprPair
}
Self::Trim
| Self::Ltrim
| Self::Rtrim
| Self::Abs
| Self::Ceiling
| Self::Floor
| Self::Lower
| Self::Upper
| Self::Length
| Self::Left
| Self::Right
| Self::Position
| Self::Replace
| Self::Substring => SqlScalarFunctionCallShape::SharedScalarCall,
}
}
#[must_use]
pub(crate) fn from_identifier(identifier: &str) -> Option<Self> {
const SUPPORTED_SCALAR_FUNCTIONS: [(&str, SqlScalarFunction); 21] = [
("trim", SqlScalarFunction::Trim),
("ltrim", SqlScalarFunction::Ltrim),
("rtrim", SqlScalarFunction::Rtrim),
("round", SqlScalarFunction::Round),
("coalesce", SqlScalarFunction::Coalesce),
("nullif", SqlScalarFunction::NullIf),
("abs", SqlScalarFunction::Abs),
("ceil", SqlScalarFunction::Ceiling),
("ceiling", SqlScalarFunction::Ceiling),
("floor", SqlScalarFunction::Floor),
("lower", SqlScalarFunction::Lower),
("upper", SqlScalarFunction::Upper),
("length", SqlScalarFunction::Length),
("left", SqlScalarFunction::Left),
("right", SqlScalarFunction::Right),
("starts_with", SqlScalarFunction::StartsWith),
("ends_with", SqlScalarFunction::EndsWith),
("contains", SqlScalarFunction::Contains),
("position", SqlScalarFunction::Position),
("replace", SqlScalarFunction::Replace),
("substring", SqlScalarFunction::Substring),
];
for (name, function) in SUPPORTED_SCALAR_FUNCTIONS {
if identifier.eq_ignore_ascii_case(name) {
return Some(function);
}
}
None
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlOrderDirection {
Asc,
Desc,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlOrderTerm {
pub(crate) field: SqlExpr,
pub(crate) direction: SqlOrderDirection,
}
impl SqlOrderTerm {
#[must_use]
pub(in crate::db) fn is_already_local_supported(&self) -> bool {
self.field.fields_are_already_local()
}
#[must_use]
pub(in crate::db) const fn direct_field_name(&self) -> Option<&str> {
match &self.field {
SqlExpr::Field(field) => Some(field.as_str()),
SqlExpr::Aggregate(_)
| SqlExpr::Literal(_)
| SqlExpr::Param { .. }
| SqlExpr::Membership { .. }
| SqlExpr::NullTest { .. }
| SqlExpr::FunctionCall { .. }
| SqlExpr::Unary { .. }
| SqlExpr::Binary { .. }
| SqlExpr::Case { .. } => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlSelectStatement {
pub(crate) entity: String,
pub(crate) projection: SqlProjection,
pub(crate) projection_aliases: Vec<Option<String>>,
pub(crate) predicate: Option<SqlExpr>,
pub(crate) distinct: bool,
pub(crate) group_by: Vec<String>,
pub(crate) having: Vec<SqlExpr>,
pub(crate) order_by: Vec<SqlOrderTerm>,
pub(crate) limit: Option<u32>,
pub(crate) offset: Option<u32>,
}
impl SqlSelectStatement {
#[must_use]
pub(in crate::db) fn is_already_local_canonical(&self) -> bool {
if !self.projection_aliases.iter().all(Option::is_none) {
return false;
}
if !self.having.is_empty() {
return false;
}
if !self
.group_by
.iter()
.all(|field| SqlExpr::identifier_is_already_local(field.as_str()))
{
return false;
}
if !self.projection.is_already_local_scalar() {
return false;
}
if self
.predicate
.as_ref()
.is_some_and(|predicate| !predicate.is_already_local_scalar())
{
return false;
}
self.order_by
.iter()
.all(SqlOrderTerm::is_already_local_supported)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlReturningProjection {
All,
Fields(Vec<String>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlDeleteStatement {
pub(crate) entity: String,
pub(crate) predicate: Option<SqlExpr>,
pub(crate) order_by: Vec<SqlOrderTerm>,
pub(crate) limit: Option<u32>,
pub(crate) offset: Option<u32>,
pub(crate) returning: Option<SqlReturningProjection>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlInsertSource {
Values(Vec<Vec<Value>>),
Select(Box<SqlSelectStatement>),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlInsertStatement {
pub(crate) entity: String,
pub(crate) columns: Vec<String>,
pub(crate) source: SqlInsertSource,
pub(crate) returning: Option<SqlReturningProjection>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlAssignment {
pub(crate) field: String,
pub(crate) value: Value,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlUpdateStatement {
pub(crate) entity: String,
pub(crate) assignments: Vec<SqlAssignment>,
pub(crate) predicate: Option<SqlExpr>,
pub(crate) order_by: Vec<SqlOrderTerm>,
pub(crate) limit: Option<u32>,
pub(crate) offset: Option<u32>,
pub(crate) returning: Option<SqlReturningProjection>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum SqlExplainMode {
Plan,
Execution,
Json,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum SqlExplainTarget {
Select(SqlSelectStatement),
Delete(SqlDeleteStatement),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlExplainStatement {
pub(crate) mode: SqlExplainMode,
pub(crate) verbose: bool,
pub(crate) statement: SqlExplainTarget,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlDescribeStatement {
pub(crate) entity: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlShowIndexesStatement {
pub(crate) entity: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlShowColumnsStatement {
pub(crate) entity: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct SqlShowEntitiesStatement;