use crate::{
db::{
numeric::{
NumericArithmeticOp, NumericEvalError, apply_decimal_arithmetic_checked,
coerce_numeric_decimal, decimal_cbrt_checked, decimal_exp_checked, decimal_ln_checked,
decimal_log_base_checked, decimal_log2_checked, decimal_log10_checked,
decimal_power_checked, decimal_sign, decimal_sqrt_checked,
},
query::plan::expr::ast::{Expr, Function},
},
types::Decimal,
value::Value,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum NumericSubtype {
Integer,
Float,
Decimal,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[remain::sorted]
pub(in crate::db::query::plan::expr) enum FunctionCategory {
BooleanPredicate,
Collection,
NullHandling,
Numeric,
Text,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum FunctionNullBehavior {
NullIgnoring,
NullObserving,
Strict,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum FunctionDeterminism {
Deterministic,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum FunctionTypeInferenceShape {
ByteLengthResult,
BoolResult {
text_positions: &'static [usize],
},
CollectionContains,
DynamicCoalesce,
DynamicNullIf,
NumericResult {
text_positions: &'static [usize],
numeric_positions: &'static [usize],
subtype: NumericSubtype,
},
NumericScaleResult,
TextResult {
text_positions: &'static [usize],
numeric_positions: &'static [usize],
},
UnaryBoolPredicate,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum FunctionSurface {
AggregateInput,
AggregateInputCondition,
HavingCondition,
Projection,
ProjectionCondition,
Where,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum BooleanFunctionShape {
CollectionContains,
FieldPredicate,
NullTest,
TextPredicate,
TruthCoalesce,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum NullTestFunctionKind {
IsNotNull,
IsNull,
}
impl NullTestFunctionKind {
#[must_use]
pub(in crate::db::query::plan::expr) const fn null_matches_true(self) -> bool {
matches!(self, Self::IsNull)
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn eval_value(self, value: &Value) -> Value {
Value::Bool(self.null_matches_true() == matches!(value, Value::Null))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum TextPredicateFunctionKind {
Contains,
EndsWith,
StartsWith,
}
impl TextPredicateFunctionKind {
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_text(self, text: &str, needle: &str) -> Value {
Value::Bool(match self {
Self::Contains => text.contains(needle),
Self::EndsWith => text.ends_with(needle),
Self::StartsWith => text.starts_with(needle),
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum UnaryTextFunctionKind {
Length,
Lower,
Ltrim,
Rtrim,
Trim,
Upper,
}
impl UnaryTextFunctionKind {
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_text(self, text: &str) -> Value {
match self {
Self::Trim => Value::Text(text.trim().to_string()),
Self::Ltrim => Value::Text(text.trim_start().to_string()),
Self::Rtrim => Value::Text(text.trim_end().to_string()),
Self::Lower => Value::Text(text.to_lowercase()),
Self::Upper => Value::Text(text.to_uppercase()),
Self::Length => Value::Uint(u64::try_from(text.chars().count()).unwrap_or(u64::MAX)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum UnaryNumericFunctionKind {
Abs,
Cbrt,
Ceiling,
Exp,
Floor,
Ln,
Log10,
Log2,
Sign,
Sqrt,
}
impl UnaryNumericFunctionKind {
pub(in crate::db::query::plan::expr) fn eval_decimal(
self,
decimal: Decimal,
) -> Result<Value, NumericEvalError> {
let result = match self {
Self::Abs => decimal.checked_abs().ok_or(NumericEvalError::Overflow)?,
Self::Cbrt => decimal_cbrt_checked(decimal)?,
Self::Ceiling => decimal.ceil_dp0(),
Self::Exp => decimal_exp_checked(decimal)?,
Self::Floor => decimal.floor_dp0(),
Self::Ln => decimal_ln_checked(decimal)?,
Self::Log10 => decimal_log10_checked(decimal)?,
Self::Log2 => decimal_log2_checked(decimal)?,
Self::Sign => decimal_sign(decimal),
Self::Sqrt => decimal_sqrt_checked(decimal)?,
};
Ok(Value::Decimal(result))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum BinaryNumericFunctionKind {
Log,
Mod,
Power,
}
impl BinaryNumericFunctionKind {
pub(in crate::db::query::plan::expr) fn eval_decimal(
self,
left: Decimal,
right: Decimal,
) -> Result<Value, NumericEvalError> {
let result = match self {
Self::Log => decimal_log_base_checked(left, right)?,
Self::Mod => apply_decimal_arithmetic_checked(NumericArithmeticOp::Rem, left, right)?,
Self::Power => decimal_power_checked(left, right)?,
};
Ok(Value::Decimal(result))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum NumericScaleFunctionKind {
Round,
Trunc,
}
impl NumericScaleFunctionKind {
#[must_use]
pub(in crate::db::query::plan::expr) const fn eval_decimal(
self,
decimal: Decimal,
scale: u32,
) -> Value {
let result = match self {
Self::Round => decimal.round_dp(scale),
Self::Trunc => decimal.trunc_dp(scale),
};
Value::Decimal(result)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum LeftRightTextFunctionKind {
Left,
Right,
}
impl LeftRightTextFunctionKind {
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_text(self, text: &str, count: i64) -> Value {
Value::Text(match self {
Self::Left => Self::left_chars(text, count),
Self::Right => Self::right_chars(text, count),
})
}
fn left_chars(text: &str, count: i64) -> String {
if count <= 0 {
return String::new();
}
text.chars()
.take(usize::try_from(count).unwrap_or(usize::MAX))
.collect()
}
fn right_chars(text: &str, count: i64) -> String {
if count <= 0 {
return String::new();
}
let count = usize::try_from(count).unwrap_or(usize::MAX);
let total = text.chars().count();
let skip = total.saturating_sub(count);
text.chars().skip(skip).collect()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum FieldPredicateFunctionKind {
Empty,
Missing,
NotEmpty,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum AggregateInputConstantFoldShape {
DynamicCoalesce,
DynamicNullIf,
BinaryNumeric,
Round,
UnaryNumeric,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) enum ScalarEvalFunctionShape {
DynamicCoalesce,
DynamicNullIf,
BinaryNumeric,
LeftRightText,
NonExecutableProjection,
NullTest,
PositionText,
ReplaceText,
NumericScale,
OctetLength,
SubstringText,
TextPredicate,
UnaryNumeric,
UnaryText,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db::query::plan::expr) struct FunctionSpec {
pub(in crate::db::query::plan::expr) category: FunctionCategory,
pub(in crate::db::query::plan::expr) null_behavior: FunctionNullBehavior,
pub(in crate::db::query::plan::expr) determinism: FunctionDeterminism,
pub(in crate::db::query::plan::expr) type_inference_shape: FunctionTypeInferenceShape,
pub(in crate::db::query::plan::expr) allowed_surfaces: &'static [FunctionSurface],
}
const BOOLEAN_FUNCTION_SURFACES: &[FunctionSurface] = &[
FunctionSurface::ProjectionCondition,
FunctionSurface::AggregateInputCondition,
FunctionSurface::HavingCondition,
FunctionSurface::Where,
];
const GENERAL_SCALAR_FUNCTION_SURFACES: &[FunctionSurface] = &[
FunctionSurface::Projection,
FunctionSurface::ProjectionCondition,
FunctionSurface::AggregateInput,
FunctionSurface::AggregateInputCondition,
FunctionSurface::HavingCondition,
FunctionSurface::Where,
];
const ROUND_FUNCTION_SURFACES: &[FunctionSurface] = &[
FunctionSurface::Projection,
FunctionSurface::ProjectionCondition,
FunctionSurface::AggregateInput,
FunctionSurface::HavingCondition,
FunctionSurface::Where,
];
impl FunctionSpec {
#[must_use]
pub(in crate::db::query::plan::expr) const fn new(
category: FunctionCategory,
null_behavior: FunctionNullBehavior,
determinism: FunctionDeterminism,
type_inference_shape: FunctionTypeInferenceShape,
allowed_surfaces: &'static [FunctionSurface],
) -> Self {
Self {
category,
null_behavior,
determinism,
type_inference_shape,
allowed_surfaces,
}
}
#[must_use]
const fn strict_unary_text_result() -> Self {
Self::new(
FunctionCategory::Text,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::TextResult {
text_positions: &[0],
numeric_positions: &[],
},
GENERAL_SCALAR_FUNCTION_SURFACES,
)
}
#[must_use]
const fn strict_text_result(
text_positions: &'static [usize],
numeric_positions: &'static [usize],
) -> Self {
Self::new(
FunctionCategory::Text,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::TextResult {
text_positions,
numeric_positions,
},
GENERAL_SCALAR_FUNCTION_SURFACES,
)
}
#[must_use]
const fn strict_text_bool_result(text_positions: &'static [usize]) -> Self {
Self::new(
FunctionCategory::Text,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::BoolResult { text_positions },
GENERAL_SCALAR_FUNCTION_SURFACES,
)
}
#[must_use]
const fn strict_numeric_result(
text_positions: &'static [usize],
numeric_positions: &'static [usize],
subtype: NumericSubtype,
) -> Self {
Self::new(
FunctionCategory::Numeric,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::NumericResult {
text_positions,
numeric_positions,
subtype,
},
GENERAL_SCALAR_FUNCTION_SURFACES,
)
}
#[must_use]
const fn null_observing_unary_bool_predicate() -> Self {
Self::new(
FunctionCategory::BooleanPredicate,
FunctionNullBehavior::NullObserving,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::UnaryBoolPredicate,
BOOLEAN_FUNCTION_SURFACES,
)
}
#[must_use]
pub(in crate::db::query::plan::expr) fn supports_surface(
self,
surface: FunctionSurface,
) -> bool {
let mut index = 0usize;
while index < self.allowed_surfaces.len() {
if matches!(self.allowed_surfaces[index], current if current == surface) {
return true;
}
index = index.saturating_add(1);
}
false
}
}
impl Function {
#[must_use]
pub(in crate::db::query::plan::expr) const fn spec(self) -> FunctionSpec {
match self {
Self::Abs
| Self::Cbrt
| Self::Ceiling
| Self::Exp
| Self::Floor
| Self::Ln
| Self::Log10
| Self::Log2
| Self::Sign
| Self::Sqrt => FunctionSpec::strict_numeric_result(&[], &[0], NumericSubtype::Decimal),
Self::Coalesce => FunctionSpec::new(
FunctionCategory::NullHandling,
FunctionNullBehavior::NullIgnoring,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::DynamicCoalesce,
GENERAL_SCALAR_FUNCTION_SURFACES,
),
Self::CollectionContains => FunctionSpec::new(
FunctionCategory::Collection,
FunctionNullBehavior::NullObserving,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::CollectionContains,
BOOLEAN_FUNCTION_SURFACES,
),
Self::Contains | Self::EndsWith | Self::StartsWith => {
FunctionSpec::strict_text_bool_result(&[0, 1])
}
Self::IsEmpty | Self::IsMissing | Self::IsNotEmpty | Self::IsNotNull | Self::IsNull => {
FunctionSpec::null_observing_unary_bool_predicate()
}
Self::Left | Self::Right => FunctionSpec::strict_text_result(&[0], &[1]),
Self::Length => FunctionSpec::new(
FunctionCategory::Text,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::NumericResult {
text_positions: &[0],
numeric_positions: &[],
subtype: NumericSubtype::Integer,
},
GENERAL_SCALAR_FUNCTION_SURFACES,
),
Self::OctetLength => FunctionSpec::new(
FunctionCategory::Numeric,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::ByteLengthResult,
GENERAL_SCALAR_FUNCTION_SURFACES,
),
Self::Lower | Self::Ltrim | Self::Rtrim | Self::Trim | Self::Upper => {
FunctionSpec::strict_unary_text_result()
}
Self::Log | Self::Mod | Self::Power => {
FunctionSpec::strict_numeric_result(&[], &[0, 1], NumericSubtype::Decimal)
}
Self::NullIf => FunctionSpec::new(
FunctionCategory::NullHandling,
FunctionNullBehavior::NullIgnoring,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::DynamicNullIf,
GENERAL_SCALAR_FUNCTION_SURFACES,
),
Self::Position => FunctionSpec::new(
FunctionCategory::Text,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::NumericResult {
text_positions: &[0, 1],
numeric_positions: &[],
subtype: NumericSubtype::Integer,
},
GENERAL_SCALAR_FUNCTION_SURFACES,
),
Self::Replace => FunctionSpec::strict_text_result(&[0, 1, 2], &[]),
Self::Round | Self::Trunc => FunctionSpec::new(
FunctionCategory::Numeric,
FunctionNullBehavior::Strict,
FunctionDeterminism::Deterministic,
FunctionTypeInferenceShape::NumericScaleResult,
ROUND_FUNCTION_SURFACES,
),
Self::Substring => FunctionSpec::strict_text_result(&[0], &[1, 2]),
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn type_inference_shape(
self,
) -> FunctionTypeInferenceShape {
self.spec().type_inference_shape
}
#[must_use]
pub(in crate::db) fn supports_surface(self, surface: FunctionSurface) -> bool {
self.spec().supports_surface(surface)
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn is_compare_operand_coarse_family(self) -> bool {
matches!(
self.type_inference_shape(),
FunctionTypeInferenceShape::TextResult { .. }
| FunctionTypeInferenceShape::NumericResult { .. }
| FunctionTypeInferenceShape::ByteLengthResult
| FunctionTypeInferenceShape::NumericScaleResult
| FunctionTypeInferenceShape::DynamicCoalesce
| FunctionTypeInferenceShape::DynamicNullIf
)
}
#[must_use]
pub(in crate::db) fn fixed_decimal_scale(self, args: &[Expr]) -> Option<u32> {
if !matches!(self, Self::Round | Self::Trunc) {
return None;
}
match args.get(1) {
Some(Expr::Literal(Value::Uint(scale))) => u32::try_from(*scale).ok(),
Some(Expr::Literal(Value::Int(scale))) if *scale >= 0 => u32::try_from(*scale).ok(),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn boolean_function_shape(
self,
) -> Option<BooleanFunctionShape> {
match self {
Self::Coalesce => Some(BooleanFunctionShape::TruthCoalesce),
Self::IsNull | Self::IsNotNull => Some(BooleanFunctionShape::NullTest),
Self::StartsWith | Self::EndsWith | Self::Contains => {
Some(BooleanFunctionShape::TextPredicate)
}
Self::IsMissing | Self::IsEmpty | Self::IsNotEmpty => {
Some(BooleanFunctionShape::FieldPredicate)
}
Self::CollectionContains => Some(BooleanFunctionShape::CollectionContains),
Self::Abs
| Self::Cbrt
| Self::Ceiling
| Self::Exp
| Self::Floor
| Self::Left
| Self::Length
| Self::Ln
| Self::Log
| Self::Log10
| Self::Log2
| Self::Lower
| Self::Ltrim
| Self::Mod
| Self::NullIf
| Self::OctetLength
| Self::Position
| Self::Power
| Self::Replace
| Self::Right
| Self::Round
| Self::Rtrim
| Self::Sign
| Self::Substring
| Self::Sqrt
| Self::Trim
| Self::Trunc
| Self::Upper => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn boolean_null_test_kind(
self,
) -> Option<NullTestFunctionKind> {
match self {
Self::IsNull => Some(NullTestFunctionKind::IsNull),
Self::IsNotNull => Some(NullTestFunctionKind::IsNotNull),
_ => None,
}
}
#[must_use]
pub(in crate::db) const fn boolean_text_predicate_kind(
self,
) -> Option<TextPredicateFunctionKind> {
match self {
Self::StartsWith => Some(TextPredicateFunctionKind::StartsWith),
Self::EndsWith => Some(TextPredicateFunctionKind::EndsWith),
Self::Contains => Some(TextPredicateFunctionKind::Contains),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn boolean_field_predicate_kind(
self,
) -> Option<FieldPredicateFunctionKind> {
match self {
Self::IsMissing => Some(FieldPredicateFunctionKind::Missing),
Self::IsEmpty => Some(FieldPredicateFunctionKind::Empty),
Self::IsNotEmpty => Some(FieldPredicateFunctionKind::NotEmpty),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn is_casefold_transform(self) -> bool {
matches!(self, Self::Lower | Self::Upper)
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn unary_text_function_kind(
self,
) -> Option<UnaryTextFunctionKind> {
match self {
Self::Trim => Some(UnaryTextFunctionKind::Trim),
Self::Ltrim => Some(UnaryTextFunctionKind::Ltrim),
Self::Rtrim => Some(UnaryTextFunctionKind::Rtrim),
Self::Lower => Some(UnaryTextFunctionKind::Lower),
Self::Upper => Some(UnaryTextFunctionKind::Upper),
Self::Length => Some(UnaryTextFunctionKind::Length),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn unary_numeric_function_kind(
self,
) -> Option<UnaryNumericFunctionKind> {
match self {
Self::Abs => Some(UnaryNumericFunctionKind::Abs),
Self::Cbrt => Some(UnaryNumericFunctionKind::Cbrt),
Self::Ceiling => Some(UnaryNumericFunctionKind::Ceiling),
Self::Exp => Some(UnaryNumericFunctionKind::Exp),
Self::Floor => Some(UnaryNumericFunctionKind::Floor),
Self::Ln => Some(UnaryNumericFunctionKind::Ln),
Self::Log10 => Some(UnaryNumericFunctionKind::Log10),
Self::Log2 => Some(UnaryNumericFunctionKind::Log2),
Self::Sign => Some(UnaryNumericFunctionKind::Sign),
Self::Sqrt => Some(UnaryNumericFunctionKind::Sqrt),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn binary_numeric_function_kind(
self,
) -> Option<BinaryNumericFunctionKind> {
match self {
Self::Log => Some(BinaryNumericFunctionKind::Log),
Self::Mod => Some(BinaryNumericFunctionKind::Mod),
Self::Power => Some(BinaryNumericFunctionKind::Power),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn numeric_scale_function_kind(
self,
) -> Option<NumericScaleFunctionKind> {
match self {
Self::Round => Some(NumericScaleFunctionKind::Round),
Self::Trunc => Some(NumericScaleFunctionKind::Trunc),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn left_right_text_function_kind(
self,
) -> Option<LeftRightTextFunctionKind> {
match self {
Self::Left => Some(LeftRightTextFunctionKind::Left),
Self::Right => Some(LeftRightTextFunctionKind::Right),
_ => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn scalar_eval_shape(
self,
) -> ScalarEvalFunctionShape {
match self {
Self::IsNull | Self::IsNotNull => ScalarEvalFunctionShape::NullTest,
Self::IsMissing | Self::IsEmpty | Self::IsNotEmpty | Self::CollectionContains => {
ScalarEvalFunctionShape::NonExecutableProjection
}
Self::Trim | Self::Ltrim | Self::Rtrim | Self::Lower | Self::Upper | Self::Length => {
ScalarEvalFunctionShape::UnaryText
}
Self::Coalesce => ScalarEvalFunctionShape::DynamicCoalesce,
Self::NullIf => ScalarEvalFunctionShape::DynamicNullIf,
Self::OctetLength => ScalarEvalFunctionShape::OctetLength,
Self::Abs
| Self::Cbrt
| Self::Ceiling
| Self::Exp
| Self::Floor
| Self::Ln
| Self::Log10
| Self::Log2
| Self::Sign
| Self::Sqrt => ScalarEvalFunctionShape::UnaryNumeric,
Self::Log | Self::Mod | Self::Power => ScalarEvalFunctionShape::BinaryNumeric,
Self::Left | Self::Right => ScalarEvalFunctionShape::LeftRightText,
Self::StartsWith | Self::EndsWith | Self::Contains => {
ScalarEvalFunctionShape::TextPredicate
}
Self::Position => ScalarEvalFunctionShape::PositionText,
Self::Replace => ScalarEvalFunctionShape::ReplaceText,
Self::Substring => ScalarEvalFunctionShape::SubstringText,
Self::Round | Self::Trunc => ScalarEvalFunctionShape::NumericScale,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn projection_eval_name(self) -> &'static str {
match self {
Self::IsNull => "is_null",
Self::IsNotNull => "is_not_null",
Self::IsMissing => "is_missing",
Self::IsEmpty => "is_empty",
Self::IsNotEmpty => "is_not_empty",
Self::Trim => "trim",
Self::Ltrim => "ltrim",
Self::Rtrim => "rtrim",
Self::Coalesce => "coalesce",
Self::NullIf => "nullif",
Self::OctetLength => "octet_length",
Self::Abs => "abs",
Self::Cbrt => "cbrt",
Self::Ceiling => "ceiling",
Self::Exp => "exp",
Self::Floor => "floor",
Self::Ln => "ln",
Self::Log => "log",
Self::Log10 => "log10",
Self::Log2 => "log2",
Self::Sign => "sign",
Self::Sqrt => "sqrt",
Self::Mod => "mod",
Self::Power => "power",
Self::Lower => "lower",
Self::Upper => "upper",
Self::Length => "length",
Self::Left => "left",
Self::Right => "right",
Self::StartsWith => "starts_with",
Self::EndsWith => "ends_with",
Self::Contains => "contains",
Self::CollectionContains => "collection_contains",
Self::Position => "position",
Self::Replace => "replace",
Self::Substring => "substring",
Self::Round => "round",
Self::Trunc => "trunc",
}
}
#[must_use]
pub(in crate::db::query::plan::expr) const fn aggregate_input_constant_fold_shape(
self,
) -> Option<AggregateInputConstantFoldShape> {
match self {
Self::Round | Self::Trunc => Some(AggregateInputConstantFoldShape::Round),
Self::Coalesce => Some(AggregateInputConstantFoldShape::DynamicCoalesce),
Self::NullIf => Some(AggregateInputConstantFoldShape::DynamicNullIf),
Self::Log | Self::Mod | Self::Power => {
Some(AggregateInputConstantFoldShape::BinaryNumeric)
}
Self::Abs
| Self::Cbrt
| Self::Ceiling
| Self::Exp
| Self::Floor
| Self::Ln
| Self::Log10
| Self::Log2
| Self::Sign
| Self::Sqrt => Some(AggregateInputConstantFoldShape::UnaryNumeric),
Self::IsNull
| Self::IsNotNull
| Self::IsMissing
| Self::IsEmpty
| Self::IsNotEmpty
| Self::Trim
| Self::Ltrim
| Self::Rtrim
| Self::Left
| Self::Right
| Self::StartsWith
| Self::EndsWith
| Self::Contains
| Self::CollectionContains
| Self::Position
| Self::Replace
| Self::Substring
| Self::Lower
| Self::Upper
| Self::Length
| Self::OctetLength => None,
}
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_coalesce_values(self, args: &[Value]) -> Value {
debug_assert!(matches!(self, Self::Coalesce));
args.iter()
.find(|value| !matches!(value, Value::Null))
.cloned()
.unwrap_or(Value::Null)
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_nullif_values(
self,
left: &Value,
right: &Value,
equals: bool,
) -> Value {
debug_assert!(matches!(self, Self::NullIf));
if matches!(left, Value::Null) || matches!(right, Value::Null) {
return left.clone();
}
if equals { Value::Null } else { left.clone() }
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_position_text(
self,
text: &str,
needle: &str,
) -> Value {
debug_assert!(matches!(self, Self::Position));
Value::Uint(Self::text_position_1_based(text, needle))
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_replace_text(
self,
text: &str,
from: &str,
to: &str,
) -> Value {
debug_assert!(matches!(self, Self::Replace));
Value::Text(text.replace(from, to))
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_substring_text(
self,
text: &str,
start: i64,
length: Option<i64>,
) -> Value {
debug_assert!(matches!(self, Self::Substring));
Value::Text(Self::substring_1_based(text, start, length))
}
#[must_use]
pub(in crate::db::query::plan::expr) fn eval_numeric_scale(
self,
value: &Value,
scale: u32,
) -> Option<Value> {
debug_assert!(matches!(self, Self::Round | Self::Trunc));
let decimal = coerce_numeric_decimal(value)?;
Some(
self.numeric_scale_function_kind()?
.eval_decimal(decimal, scale),
)
}
fn text_position_1_based(haystack: &str, needle: &str) -> u64 {
let Some(byte_index) = haystack.find(needle) else {
return 0;
};
let char_offset = haystack[..byte_index].chars().count();
u64::try_from(char_offset)
.unwrap_or(u64::MAX)
.saturating_add(1)
}
fn substring_1_based(text: &str, start: i64, length: Option<i64>) -> String {
if start <= 0 {
return String::new();
}
if matches!(length, Some(inner) if inner <= 0) {
return String::new();
}
let start_index = usize::try_from(start.saturating_sub(1)).unwrap_or(usize::MAX);
let chars = text.chars().skip(start_index);
match length {
Some(length) => chars
.take(usize::try_from(length).unwrap_or(usize::MAX))
.collect(),
None => chars.collect(),
}
}
}