use crate::db::{
access::AccessPlanError,
cursor::CursorPlanError,
predicate::CompareOp,
query::plan::{
AggregateKind,
expr::{BinaryOp, ExprType, Function, UnaryOp},
},
schema::ValidateError,
};
use icydb_diagnostic_code::{
DiagnosticAggregateKind, DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticOperatorKind,
DiagnosticTypeFamily,
};
type DiagnosticFacts = Vec<(DiagnosticFactTag, u64)>;
const fn diagnostic_index(index: usize) -> u64 {
index as u64
}
const fn diagnostic_aggregate_kind(kind: AggregateKind) -> DiagnosticAggregateKind {
match kind {
AggregateKind::Count => DiagnosticAggregateKind::Count,
AggregateKind::Sum => DiagnosticAggregateKind::Sum,
AggregateKind::Avg => DiagnosticAggregateKind::Avg,
AggregateKind::Exists => DiagnosticAggregateKind::Exists,
AggregateKind::Min => DiagnosticAggregateKind::Min,
AggregateKind::Max => DiagnosticAggregateKind::Max,
AggregateKind::First => DiagnosticAggregateKind::First,
AggregateKind::Last => DiagnosticAggregateKind::Last,
}
}
const fn diagnostic_compare_op(op: CompareOp) -> DiagnosticOperatorKind {
match op {
CompareOp::Eq => DiagnosticOperatorKind::Eq,
CompareOp::Ne => DiagnosticOperatorKind::Ne,
CompareOp::Lt => DiagnosticOperatorKind::Lt,
CompareOp::Lte => DiagnosticOperatorKind::Lte,
CompareOp::Gt => DiagnosticOperatorKind::Gt,
CompareOp::Gte => DiagnosticOperatorKind::Gte,
CompareOp::In => DiagnosticOperatorKind::In,
CompareOp::NotIn => DiagnosticOperatorKind::NotIn,
CompareOp::Contains => DiagnosticOperatorKind::Contains,
CompareOp::StartsWith => DiagnosticOperatorKind::StartsWith,
CompareOp::EndsWith => DiagnosticOperatorKind::EndsWith,
}
}
#[derive(Debug)]
pub enum PlanError {
User(Box<PlanUserError>),
Policy(Box<PlanPolicyError>),
Cursor(Box<CursorPlanError>),
}
impl PlanError {
pub(crate) fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::User(error) => error.diagnostic_facts(),
Self::Policy(error) => error.diagnostic_facts(),
Self::Cursor(error) => error.diagnostic_facts(),
}
}
#[must_use]
pub(crate) fn is_invalid_continuation_cursor(&self) -> bool {
matches!(
self,
Self::Cursor(error) if error.is_invalid_continuation_cursor()
)
}
#[must_use]
pub fn is_unordered_pagination(&self) -> bool {
matches!(
self,
Self::Policy(inner)
if matches!(
inner.as_ref(),
PlanPolicyError::Policy(policy)
if matches!(policy.as_ref(), PolicyPlanError::UnorderedPagination)
)
)
}
}
#[derive(Debug)]
pub enum PlanUserError {
PredicateInvalid(Box<ValidateError>),
Order(Box<OrderPlanError>),
Access(Box<AccessPlanError>),
Group(Box<GroupPlanError>),
Expr(Box<ExprPlanError>),
}
impl PlanUserError {
fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::Order(error) => error.diagnostic_facts(),
Self::Group(error) => error.diagnostic_facts(),
Self::Expr(error) => error.diagnostic_facts(),
Self::PredicateInvalid(_) | Self::Access(_) => Vec::new(),
}
}
}
#[derive(Debug)]
pub enum PlanPolicyError {
Policy(Box<PolicyPlanError>),
Group(Box<GroupPlanError>),
}
impl PlanPolicyError {
fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::Group(error) => error.diagnostic_facts(),
Self::Policy(_) => Vec::new(),
}
}
}
#[derive(Debug)]
pub enum OrderPlanError {
UnknownField { term_index: usize },
UnorderableField { term_index: usize },
DuplicateOrderField {
first_term_index: usize,
duplicate_term_index: usize,
},
MissingPrimaryKeyTieBreak { primary_key_index: usize },
}
impl OrderPlanError {
fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::UnknownField { term_index } | Self::UnorderableField { term_index } => {
vec![(DiagnosticFactTag::TermIndex, diagnostic_index(*term_index))]
}
Self::DuplicateOrderField {
first_term_index,
duplicate_term_index,
} => vec![
(
DiagnosticFactTag::FirstTermIndex,
diagnostic_index(*first_term_index),
),
(
DiagnosticFactTag::DuplicateTermIndex,
diagnostic_index(*duplicate_term_index),
),
],
Self::MissingPrimaryKeyTieBreak { primary_key_index } => vec![(
DiagnosticFactTag::ComponentIndex,
diagnostic_index(*primary_key_index),
)],
}
}
pub(in crate::db::query) const fn unknown_field(term_index: usize) -> Self {
Self::UnknownField { term_index }
}
pub(in crate::db::query) const fn unorderable_field(term_index: usize) -> Self {
Self::UnorderableField { term_index }
}
pub(in crate::db::query) const fn duplicate_order_field(
first_term_index: usize,
duplicate_term_index: usize,
) -> Self {
Self::DuplicateOrderField {
first_term_index,
duplicate_term_index,
}
}
pub(in crate::db::query) const fn missing_primary_key_tie_break(
primary_key_index: usize,
) -> Self {
Self::MissingPrimaryKeyTieBreak { primary_key_index }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PolicyPlanError {
EmptyOrderSpec,
DeletePlanWithGrouping,
DeletePlanWithPagination,
LoadPlanWithDeleteLimit,
DeleteWindowRequiresOrder,
UnorderedPagination,
}
impl PolicyPlanError {
pub(in crate::db::query) const fn empty_order_spec() -> Self {
Self::EmptyOrderSpec
}
pub(in crate::db::query) const fn delete_plan_with_grouping() -> Self {
Self::DeletePlanWithGrouping
}
pub(in crate::db::query) const fn delete_plan_with_pagination() -> Self {
Self::DeletePlanWithPagination
}
pub(in crate::db::query) const fn load_plan_with_delete_limit() -> Self {
Self::LoadPlanWithDeleteLimit
}
pub(in crate::db::query) const fn delete_window_requires_order() -> Self {
Self::DeleteWindowRequiresOrder
}
pub(in crate::db::query) const fn unordered_pagination() -> Self {
Self::UnorderedPagination
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum GroupPlanError {
HavingRequiresGroupBy,
GroupedLogicalPlanRequired,
EmptyGroupFields,
GlobalDistinctAggregateShapeUnsupported,
EmptyAggregates,
UnknownGroupField {
group_index: Option<usize>,
field: String,
},
DuplicateGroupField { group_index: usize, field: String },
DistinctAdjacencyEligibilityRequired,
OrderPrefixNotAlignedWithGroupKeys,
OrderExpressionNotAdmissible { term: String },
OrderRequiresLimit,
DistinctHavingUnsupported,
HavingUnsupportedCompareOp { index: usize, op: CompareOp },
HavingNonGroupFieldReference { index: usize, field: String },
HavingAggregateIndexOutOfBounds {
index: usize,
aggregate_index: usize,
aggregate_count: usize,
},
DistinctAggregateKindUnsupported {
index: usize,
kind: Option<AggregateKind>,
},
DistinctAggregateFieldTargetUnsupported {
index: usize,
kind: AggregateKind,
field: String,
},
UnknownAggregateTargetField { index: usize, field: String },
GlobalDistinctSumTargetNotNumeric { index: usize, field: String },
FieldTargetAggregatesUnsupported {
index: usize,
kind: AggregateKind,
field: String,
},
}
impl GroupPlanError {
fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::HavingUnsupportedCompareOp { index, op } => vec![
(DiagnosticFactTag::ClauseIndex, diagnostic_index(*index)),
(
DiagnosticFactTag::OperatorKind,
diagnostic_compare_op(*op).raw(),
),
],
Self::HavingNonGroupFieldReference { index, .. } => {
vec![(DiagnosticFactTag::ClauseIndex, diagnostic_index(*index))]
}
Self::HavingAggregateIndexOutOfBounds {
index,
aggregate_index,
aggregate_count,
} => vec![
(DiagnosticFactTag::ClauseIndex, diagnostic_index(*index)),
(
DiagnosticFactTag::AggregateIndex,
diagnostic_index(*aggregate_index),
),
(
DiagnosticFactTag::ActualCount,
diagnostic_index(*aggregate_count),
),
],
Self::DistinctAggregateKindUnsupported { index, kind } => {
let mut facts = vec![(DiagnosticFactTag::AggregateIndex, diagnostic_index(*index))];
if let Some(kind) = kind {
facts.push((
DiagnosticFactTag::AggregateKind,
diagnostic_aggregate_kind(*kind).raw(),
));
}
facts
}
Self::DistinctAggregateFieldTargetUnsupported { index, kind, .. }
| Self::FieldTargetAggregatesUnsupported { index, kind, .. } => vec![
(DiagnosticFactTag::AggregateIndex, diagnostic_index(*index)),
(
DiagnosticFactTag::AggregateKind,
diagnostic_aggregate_kind(*kind).raw(),
),
],
Self::UnknownAggregateTargetField { index, .. } => {
vec![(DiagnosticFactTag::AggregateIndex, diagnostic_index(*index))]
}
Self::UnknownGroupField {
group_index: Some(index),
..
}
| Self::DuplicateGroupField {
group_index: index, ..
} => vec![(DiagnosticFactTag::GroupIndex, diagnostic_index(*index))],
Self::GlobalDistinctSumTargetNotNumeric { index, .. } => vec![
(DiagnosticFactTag::AggregateIndex, diagnostic_index(*index)),
(
DiagnosticFactTag::AggregateKind,
DiagnosticAggregateKind::Sum.raw(),
),
],
Self::HavingRequiresGroupBy
| Self::GroupedLogicalPlanRequired
| Self::EmptyGroupFields
| Self::GlobalDistinctAggregateShapeUnsupported
| Self::EmptyAggregates
| Self::UnknownGroupField {
group_index: None, ..
}
| Self::DistinctAdjacencyEligibilityRequired
| Self::OrderPrefixNotAlignedWithGroupKeys
| Self::OrderExpressionNotAdmissible { .. }
| Self::OrderRequiresLimit
| Self::DistinctHavingUnsupported => Vec::new(),
}
}
pub(in crate::db::query) const fn grouped_logical_plan_required() -> Self {
Self::GroupedLogicalPlanRequired
}
pub(in crate::db::query) const fn global_distinct_aggregate_shape_unsupported() -> Self {
Self::GlobalDistinctAggregateShapeUnsupported
}
pub(in crate::db::query) const fn distinct_adjacency_eligibility_required() -> Self {
Self::DistinctAdjacencyEligibilityRequired
}
pub(in crate::db::query) const fn distinct_having_unsupported() -> Self {
Self::DistinctHavingUnsupported
}
pub(in crate::db::query) fn unknown_group_field(field: impl Into<String>) -> Self {
Self::UnknownGroupField {
group_index: None,
field: field.into(),
}
}
pub(in crate::db::query) fn unknown_group_field_at(
group_index: usize,
field: impl Into<String>,
) -> Self {
Self::UnknownGroupField {
group_index: Some(group_index),
field: field.into(),
}
}
pub(in crate::db::query) fn duplicate_group_field(
group_index: usize,
field: impl Into<String>,
) -> Self {
Self::DuplicateGroupField {
group_index,
field: field.into(),
}
}
pub(in crate::db::query) const fn order_requires_limit() -> Self {
Self::OrderRequiresLimit
}
pub(in crate::db::query) const fn order_prefix_not_aligned_with_group_keys() -> Self {
Self::OrderPrefixNotAlignedWithGroupKeys
}
pub(in crate::db::query) fn order_expression_not_admissible(term: impl Into<String>) -> Self {
Self::OrderExpressionNotAdmissible { term: term.into() }
}
pub(in crate::db::query) const fn empty_aggregates() -> Self {
Self::EmptyAggregates
}
pub(in crate::db::query) fn having_non_group_field_reference(
index: usize,
field: impl Into<String>,
) -> Self {
Self::HavingNonGroupFieldReference {
index,
field: field.into(),
}
}
pub(in crate::db::query) const fn having_aggregate_index_out_of_bounds(
index: usize,
aggregate_index: usize,
aggregate_count: usize,
) -> Self {
Self::HavingAggregateIndexOutOfBounds {
index,
aggregate_index,
aggregate_count,
}
}
pub(in crate::db::query) const fn having_unsupported_compare_op(
index: usize,
op: CompareOp,
) -> Self {
Self::HavingUnsupportedCompareOp { index, op }
}
pub(in crate::db::query) const fn distinct_aggregate_kind_unsupported(
index: usize,
kind: Option<AggregateKind>,
) -> Self {
Self::DistinctAggregateKindUnsupported { index, kind }
}
pub(in crate::db::query) fn distinct_aggregate_field_target_unsupported(
index: usize,
kind: AggregateKind,
field: impl Into<String>,
) -> Self {
Self::DistinctAggregateFieldTargetUnsupported {
index,
kind,
field: field.into(),
}
}
pub(in crate::db::query) fn field_target_aggregates_unsupported(
index: usize,
kind: AggregateKind,
field: impl Into<String>,
) -> Self {
Self::FieldTargetAggregatesUnsupported {
index,
kind,
field: field.into(),
}
}
pub(in crate::db::query) fn global_distinct_sum_target_not_numeric(
index: usize,
field: impl Into<String>,
) -> Self {
Self::GlobalDistinctSumTargetNotNumeric {
index,
field: field.into(),
}
}
pub(in crate::db::query) fn unknown_aggregate_target_field(
index: usize,
field: impl Into<String>,
) -> Self {
Self::UnknownAggregateTargetField {
index,
field: field.into(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ExprPlanTypeClass {
Blob,
Bool,
Collection,
#[cfg(test)]
Null,
Numeric,
Opaque,
Structured,
Text,
Unknown,
}
impl ExprPlanTypeClass {
pub(in crate::db) const fn from_expr_type(expr_type: &ExprType) -> Self {
match expr_type {
ExprType::Blob => Self::Blob,
ExprType::Bool => Self::Bool,
ExprType::Collection => Self::Collection,
#[cfg(test)]
ExprType::Null => Self::Null,
ExprType::Numeric(_) => Self::Numeric,
ExprType::Opaque => Self::Opaque,
ExprType::Structured => Self::Structured,
ExprType::Text => Self::Text,
ExprType::Unknown => Self::Unknown,
}
}
const fn diagnostic_kind(self) -> DiagnosticTypeFamily {
match self {
Self::Blob => DiagnosticTypeFamily::Blob,
Self::Bool => DiagnosticTypeFamily::Bool,
Self::Collection => DiagnosticTypeFamily::Collection,
#[cfg(test)]
Self::Null => DiagnosticTypeFamily::Null,
Self::Numeric => DiagnosticTypeFamily::Numeric,
Self::Opaque => DiagnosticTypeFamily::Opaque,
Self::Structured => DiagnosticTypeFamily::Structured,
Self::Text => DiagnosticTypeFamily::Text,
Self::Unknown => DiagnosticTypeFamily::Unknown,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExprPlanUnaryOpCode(DiagnosticOperatorKind);
impl ExprPlanUnaryOpCode {
pub const NOT: Self = Self(DiagnosticOperatorKind::Not);
pub(in crate::db) const fn from_unary_op(op: UnaryOp) -> Self {
match op {
UnaryOp::Not => Self::NOT,
}
}
const fn diagnostic_kind(self) -> DiagnosticOperatorKind {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ExprPlanBinaryOpCode(DiagnosticOperatorKind);
impl ExprPlanBinaryOpCode {
pub const ADD: Self = Self(DiagnosticOperatorKind::Add);
pub const AND: Self = Self(DiagnosticOperatorKind::And);
pub const DIV: Self = Self(DiagnosticOperatorKind::Div);
pub const EQ: Self = Self(DiagnosticOperatorKind::Eq);
pub const GT: Self = Self(DiagnosticOperatorKind::Gt);
pub const GTE: Self = Self(DiagnosticOperatorKind::Gte);
pub const LT: Self = Self(DiagnosticOperatorKind::Lt);
pub const LTE: Self = Self(DiagnosticOperatorKind::Lte);
pub const MUL: Self = Self(DiagnosticOperatorKind::Mul);
pub const NE: Self = Self(DiagnosticOperatorKind::Ne);
pub const OR: Self = Self(DiagnosticOperatorKind::Or);
pub const SUB: Self = Self(DiagnosticOperatorKind::Sub);
pub(in crate::db) const fn from_binary_op(op: BinaryOp) -> Self {
match op {
BinaryOp::Add => Self::ADD,
BinaryOp::And => Self::AND,
BinaryOp::Div => Self::DIV,
BinaryOp::Eq => Self::EQ,
BinaryOp::Gt => Self::GT,
BinaryOp::Gte => Self::GTE,
BinaryOp::Lt => Self::LT,
BinaryOp::Lte => Self::LTE,
BinaryOp::Mul => Self::MUL,
BinaryOp::Ne => Self::NE,
BinaryOp::Or => Self::OR,
BinaryOp::Sub => Self::SUB,
}
}
const fn diagnostic_kind(self) -> DiagnosticOperatorKind {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ExprPlanFunctionCode(DiagnosticFunctionKind);
impl ExprPlanFunctionCode {
pub const ABS: Self = Self(DiagnosticFunctionKind::Abs);
pub const CBRT: Self = Self(DiagnosticFunctionKind::Cbrt);
pub const CEILING: Self = Self(DiagnosticFunctionKind::Ceiling);
pub const COALESCE: Self = Self(DiagnosticFunctionKind::Coalesce);
pub const COLLECTION_CONTAINS: Self = Self(DiagnosticFunctionKind::CollectionContains);
pub const CONTAINS: Self = Self(DiagnosticFunctionKind::Contains);
pub const ENDS_WITH: Self = Self(DiagnosticFunctionKind::EndsWith);
pub const EXP: Self = Self(DiagnosticFunctionKind::Exp);
pub const FLOOR: Self = Self(DiagnosticFunctionKind::Floor);
pub const IN_LIST: Self = Self(DiagnosticFunctionKind::InList);
pub const IS_EMPTY: Self = Self(DiagnosticFunctionKind::IsEmpty);
pub const IS_MISSING: Self = Self(DiagnosticFunctionKind::IsMissing);
pub const IS_NOT_EMPTY: Self = Self(DiagnosticFunctionKind::IsNotEmpty);
pub const IS_NOT_NULL: Self = Self(DiagnosticFunctionKind::IsNotNull);
pub const IS_NULL: Self = Self(DiagnosticFunctionKind::IsNull);
pub const LEFT: Self = Self(DiagnosticFunctionKind::Left);
pub const LENGTH: Self = Self(DiagnosticFunctionKind::Length);
pub const LN: Self = Self(DiagnosticFunctionKind::Ln);
pub const LOG: Self = Self(DiagnosticFunctionKind::Log);
pub const LOG2: Self = Self(DiagnosticFunctionKind::Log2);
pub const LOG10: Self = Self(DiagnosticFunctionKind::Log10);
pub const LOWER: Self = Self(DiagnosticFunctionKind::Lower);
pub const LTRIM: Self = Self(DiagnosticFunctionKind::Ltrim);
pub const MOD: Self = Self(DiagnosticFunctionKind::Mod);
pub const NULLIF: Self = Self(DiagnosticFunctionKind::NullIf);
pub const OCTET_LENGTH: Self = Self(DiagnosticFunctionKind::OctetLength);
pub const POSITION: Self = Self(DiagnosticFunctionKind::Position);
pub const POWER: Self = Self(DiagnosticFunctionKind::Power);
pub const REPLACE: Self = Self(DiagnosticFunctionKind::Replace);
pub const RIGHT: Self = Self(DiagnosticFunctionKind::Right);
pub const ROUND: Self = Self(DiagnosticFunctionKind::Round);
pub const RTRIM: Self = Self(DiagnosticFunctionKind::Rtrim);
pub const SIGN: Self = Self(DiagnosticFunctionKind::Sign);
pub const SQRT: Self = Self(DiagnosticFunctionKind::Sqrt);
pub const STARTS_WITH: Self = Self(DiagnosticFunctionKind::StartsWith);
pub const SUBSTRING: Self = Self(DiagnosticFunctionKind::Substring);
pub const TRIM: Self = Self(DiagnosticFunctionKind::Trim);
pub const TRUNC: Self = Self(DiagnosticFunctionKind::Trunc);
pub const UPPER: Self = Self(DiagnosticFunctionKind::Upper);
pub(in crate::db) const fn from_function(function: Function) -> Self {
match function {
Function::Abs => Self::ABS,
Function::Cbrt => Self::CBRT,
Function::Ceiling => Self::CEILING,
Function::Coalesce => Self::COALESCE,
Function::CollectionContains => Self::COLLECTION_CONTAINS,
Function::Contains => Self::CONTAINS,
Function::EndsWith => Self::ENDS_WITH,
Function::Exp => Self::EXP,
Function::Floor => Self::FLOOR,
Function::InList => Self::IN_LIST,
Function::IsEmpty => Self::IS_EMPTY,
Function::IsMissing => Self::IS_MISSING,
Function::IsNotEmpty => Self::IS_NOT_EMPTY,
Function::IsNotNull => Self::IS_NOT_NULL,
Function::IsNull => Self::IS_NULL,
Function::Left => Self::LEFT,
Function::Length => Self::LENGTH,
Function::Ln => Self::LN,
Function::Log => Self::LOG,
Function::Log2 => Self::LOG2,
Function::Log10 => Self::LOG10,
Function::Lower => Self::LOWER,
Function::Ltrim => Self::LTRIM,
Function::Mod => Self::MOD,
Function::NullIf => Self::NULLIF,
Function::OctetLength => Self::OCTET_LENGTH,
Function::Position => Self::POSITION,
Function::Power => Self::POWER,
Function::Replace => Self::REPLACE,
Function::Right => Self::RIGHT,
Function::Round => Self::ROUND,
Function::Rtrim => Self::RTRIM,
Function::Sign => Self::SIGN,
Function::Sqrt => Self::SQRT,
Function::StartsWith => Self::STARTS_WITH,
Function::Substring => Self::SUBSTRING,
Function::Trim => Self::TRIM,
Function::Trunc => Self::TRUNC,
Function::Upper => Self::UPPER,
}
}
const fn diagnostic_kind(self) -> DiagnosticFunctionKind {
self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExprPlanError {
UnknownField { field: String },
UnknownExprField { field: String },
NonNumericAggregateTarget {
kind: AggregateKind,
found: ExprPlanTypeClass,
},
AggregateTargetRequired { kind: AggregateKind },
InvalidFunctionArity {
function: ExprPlanFunctionCode,
expected: usize,
actual: usize,
},
InvalidFunctionArgument {
function: ExprPlanFunctionCode,
argument_index: usize,
found: ExprPlanTypeClass,
},
IncompatibleFunctionArguments {
function: ExprPlanFunctionCode,
left_argument_index: usize,
right_argument_index: usize,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
},
InvalidUnaryOperand {
op: ExprPlanUnaryOpCode,
found: ExprPlanTypeClass,
},
InvalidCaseConditionType {
arm_index: usize,
found: ExprPlanTypeClass,
},
IncompatibleCaseBranchTypes {
left_branch_index: Option<usize>,
right_branch_index: Option<usize>,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
},
InvalidBinaryOperands {
op: ExprPlanBinaryOpCode,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
},
GroupedProjectionReferencesNonGroupField { index: usize },
}
impl ExprPlanError {
fn diagnostic_facts(&self) -> DiagnosticFacts {
match self {
Self::NonNumericAggregateTarget { kind, found } => vec![
(
DiagnosticFactTag::AggregateKind,
diagnostic_aggregate_kind(*kind).raw(),
),
(DiagnosticFactTag::TypeFamily, found.diagnostic_kind().raw()),
],
Self::AggregateTargetRequired { kind } => vec![(
DiagnosticFactTag::AggregateKind,
diagnostic_aggregate_kind(*kind).raw(),
)],
Self::InvalidFunctionArity {
function,
expected,
actual,
} => vec![
(
DiagnosticFactTag::FunctionKind,
function.diagnostic_kind().raw(),
),
(
DiagnosticFactTag::ExpectedArity,
diagnostic_index(*expected),
),
(DiagnosticFactTag::ActualArity, diagnostic_index(*actual)),
],
Self::InvalidFunctionArgument {
function,
argument_index,
found,
} => vec![
(
DiagnosticFactTag::FunctionKind,
function.diagnostic_kind().raw(),
),
(
DiagnosticFactTag::ArgumentIndex,
diagnostic_index(*argument_index),
),
(DiagnosticFactTag::TypeFamily, found.diagnostic_kind().raw()),
],
Self::IncompatibleFunctionArguments {
function,
left_argument_index,
right_argument_index,
left,
right,
} => vec![
(
DiagnosticFactTag::FunctionKind,
function.diagnostic_kind().raw(),
),
(
DiagnosticFactTag::ArgumentIndex,
diagnostic_index(*left_argument_index),
),
(DiagnosticFactTag::TypeFamily, left.diagnostic_kind().raw()),
(
DiagnosticFactTag::ArgumentIndex,
diagnostic_index(*right_argument_index),
),
(DiagnosticFactTag::TypeFamily, right.diagnostic_kind().raw()),
],
Self::InvalidUnaryOperand { op, found } => vec![
(DiagnosticFactTag::OperatorKind, op.diagnostic_kind().raw()),
(DiagnosticFactTag::TypeFamily, found.diagnostic_kind().raw()),
],
Self::InvalidCaseConditionType { arm_index, found } => vec![
(DiagnosticFactTag::BranchIndex, diagnostic_index(*arm_index)),
(DiagnosticFactTag::TypeFamily, found.diagnostic_kind().raw()),
],
Self::IncompatibleCaseBranchTypes {
left_branch_index,
right_branch_index,
left,
right,
} => {
let mut facts = Vec::with_capacity(4);
if let Some(index) = left_branch_index {
facts.push((DiagnosticFactTag::BranchIndex, diagnostic_index(*index)));
}
facts.push((DiagnosticFactTag::TypeFamily, left.diagnostic_kind().raw()));
if let Some(index) = right_branch_index {
facts.push((DiagnosticFactTag::BranchIndex, diagnostic_index(*index)));
}
facts.push((DiagnosticFactTag::TypeFamily, right.diagnostic_kind().raw()));
facts
}
Self::InvalidBinaryOperands { op, left, right } => vec![
(DiagnosticFactTag::OperatorKind, op.diagnostic_kind().raw()),
(DiagnosticFactTag::TypeFamily, left.diagnostic_kind().raw()),
(DiagnosticFactTag::TypeFamily, right.diagnostic_kind().raw()),
],
Self::GroupedProjectionReferencesNonGroupField { index } => {
vec![(DiagnosticFactTag::ProjectionIndex, diagnostic_index(*index))]
}
Self::UnknownField { .. } | Self::UnknownExprField { .. } => Vec::new(),
}
}
pub(in crate::db::query) fn unknown_field(field: impl Into<String>) -> Self {
Self::UnknownField {
field: field.into(),
}
}
pub(in crate::db::query) fn unknown_expr_field(field: impl Into<String>) -> Self {
Self::UnknownExprField {
field: field.into(),
}
}
pub(in crate::db::query) const fn aggregate_target_required(kind: AggregateKind) -> Self {
Self::AggregateTargetRequired { kind }
}
pub(in crate::db::query) const fn non_numeric_aggregate_target(
kind: AggregateKind,
found: ExprPlanTypeClass,
) -> Self {
Self::NonNumericAggregateTarget { kind, found }
}
pub(in crate::db::query) const fn invalid_function_arity(
function: Function,
expected: usize,
actual: usize,
) -> Self {
Self::InvalidFunctionArity {
function: ExprPlanFunctionCode::from_function(function),
expected,
actual,
}
}
pub(in crate::db::query) const fn invalid_function_argument(
function: Function,
argument_index: usize,
found: ExprPlanTypeClass,
) -> Self {
Self::InvalidFunctionArgument {
function: ExprPlanFunctionCode::from_function(function),
argument_index,
found,
}
}
pub(in crate::db::query) const fn incompatible_function_arguments(
function: Function,
left_argument_index: usize,
right_argument_index: usize,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
) -> Self {
Self::IncompatibleFunctionArguments {
function: ExprPlanFunctionCode::from_function(function),
left_argument_index,
right_argument_index,
left,
right,
}
}
pub(in crate::db::query) const fn invalid_unary_operand(
op: UnaryOp,
found: ExprPlanTypeClass,
) -> Self {
Self::InvalidUnaryOperand {
op: ExprPlanUnaryOpCode::from_unary_op(op),
found,
}
}
pub(in crate::db::query) const fn invalid_case_condition_type(
arm_index: usize,
found: ExprPlanTypeClass,
) -> Self {
Self::InvalidCaseConditionType { arm_index, found }
}
pub(in crate::db::query) const fn incompatible_case_branch_types(
left_branch_index: Option<usize>,
right_branch_index: Option<usize>,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
) -> Self {
Self::IncompatibleCaseBranchTypes {
left_branch_index,
right_branch_index,
left,
right,
}
}
pub(in crate::db::query) const fn invalid_binary_operands(
op: BinaryOp,
left: ExprPlanTypeClass,
right: ExprPlanTypeClass,
) -> Self {
Self::InvalidBinaryOperands {
op: ExprPlanBinaryOpCode::from_binary_op(op),
left,
right,
}
}
pub(in crate::db::query) const fn grouped_projection_references_non_group_field(
index: usize,
) -> Self {
Self::GroupedProjectionReferencesNonGroupField { index }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum CursorOrderPlanShapeError {
MissingExplicitOrder,
EmptyOrderSpec,
}
impl CursorOrderPlanShapeError {
pub(in crate::db) const fn missing_explicit_order() -> Self {
Self::MissingExplicitOrder
}
pub(in crate::db) const fn empty_order_spec() -> Self {
Self::EmptyOrderSpec
}
pub(in crate::db) const fn to_cursor_plan_error(self) -> CursorPlanError {
match self {
Self::MissingExplicitOrder => CursorPlanError::continuation_cursor_invariant(),
Self::EmptyOrderSpec => CursorPlanError::cursor_requires_non_empty_order(),
}
}
}
impl From<ValidateError> for PlanError {
fn from(err: ValidateError) -> Self {
Self::from(PlanUserError::from(err))
}
}
impl From<OrderPlanError> for PlanError {
fn from(err: OrderPlanError) -> Self {
Self::from(PlanUserError::from(err))
}
}
impl From<AccessPlanError> for PlanError {
fn from(err: AccessPlanError) -> Self {
Self::from(PlanUserError::from(err))
}
}
impl From<PolicyPlanError> for PlanError {
fn from(err: PolicyPlanError) -> Self {
Self::from(PlanPolicyError::from(err))
}
}
impl From<CursorPlanError> for PlanError {
fn from(err: CursorPlanError) -> Self {
Self::Cursor(Box::new(err))
}
}
impl From<GroupPlanError> for PlanError {
fn from(err: GroupPlanError) -> Self {
if err.belongs_to_policy_axis() {
return Self::from(PlanPolicyError::from(err));
}
Self::from(PlanUserError::from(err))
}
}
impl From<ExprPlanError> for PlanError {
fn from(err: ExprPlanError) -> Self {
Self::from(PlanUserError::from(err))
}
}
impl From<PlanUserError> for PlanError {
fn from(err: PlanUserError) -> Self {
Self::User(Box::new(err))
}
}
impl From<PlanPolicyError> for PlanError {
fn from(err: PlanPolicyError) -> Self {
Self::Policy(Box::new(err))
}
}
impl From<ValidateError> for PlanUserError {
fn from(err: ValidateError) -> Self {
Self::PredicateInvalid(Box::new(err))
}
}
impl From<OrderPlanError> for PlanUserError {
fn from(err: OrderPlanError) -> Self {
Self::Order(Box::new(err))
}
}
impl From<AccessPlanError> for PlanUserError {
fn from(err: AccessPlanError) -> Self {
Self::Access(Box::new(err))
}
}
impl From<GroupPlanError> for PlanUserError {
fn from(err: GroupPlanError) -> Self {
Self::Group(Box::new(err))
}
}
impl From<ExprPlanError> for PlanUserError {
fn from(err: ExprPlanError) -> Self {
Self::Expr(Box::new(err))
}
}
impl From<PolicyPlanError> for PlanPolicyError {
fn from(err: PolicyPlanError) -> Self {
Self::Policy(Box::new(err))
}
}
impl From<GroupPlanError> for PlanPolicyError {
fn from(err: GroupPlanError) -> Self {
Self::Group(Box::new(err))
}
}
impl GroupPlanError {
const fn belongs_to_policy_axis(&self) -> bool {
matches!(
self,
Self::GlobalDistinctAggregateShapeUnsupported
| Self::DistinctAdjacencyEligibilityRequired
| Self::OrderPrefixNotAlignedWithGroupKeys
| Self::OrderExpressionNotAdmissible { .. }
| Self::OrderRequiresLimit
| Self::DistinctHavingUnsupported
| Self::HavingUnsupportedCompareOp { .. }
| Self::DistinctAggregateKindUnsupported { .. }
| Self::DistinctAggregateFieldTargetUnsupported { .. }
| Self::FieldTargetAggregatesUnsupported { .. }
)
}
}
#[cfg(test)]
mod tests {
use super::{
ExprPlanError, ExprPlanTypeClass, GroupPlanError, OrderPlanError, PlanError,
diagnostic_index,
};
use crate::db::{
QueryError,
predicate::CompareOp,
query::plan::{
AggregateKind,
expr::{BinaryOp, Function},
},
};
use icydb_diagnostic_code::{
DiagnosticAggregateKind, DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticOperatorKind,
DiagnosticTypeFamily,
};
#[test]
fn order_diagnostics_retain_exact_term_and_component_positions() {
assert_eq!(
OrderPlanError::duplicate_order_field(2, 5).diagnostic_facts(),
vec![
(DiagnosticFactTag::FirstTermIndex, 2),
(DiagnosticFactTag::DuplicateTermIndex, 5),
],
);
assert_eq!(
OrderPlanError::missing_primary_key_tie_break(3).diagnostic_facts(),
vec![(DiagnosticFactTag::ComponentIndex, 3)],
);
assert_eq!(diagnostic_index(usize::MAX), usize::MAX as u64);
let query_error = QueryError::from(PlanError::from(OrderPlanError::unknown_field(7)));
assert_eq!(
query_error.diagnostic_facts(),
vec![(DiagnosticFactTag::TermIndex, 7)],
);
}
#[test]
fn group_diagnostics_retain_clause_aggregate_count_and_kind() {
assert_eq!(
GroupPlanError::having_aggregate_index_out_of_bounds(1, 4, 3).diagnostic_facts(),
vec![
(DiagnosticFactTag::ClauseIndex, 1),
(DiagnosticFactTag::AggregateIndex, 4),
(DiagnosticFactTag::ActualCount, 3),
],
);
assert_eq!(
GroupPlanError::having_unsupported_compare_op(2, CompareOp::NotIn).diagnostic_facts(),
vec![
(DiagnosticFactTag::ClauseIndex, 2),
(
DiagnosticFactTag::OperatorKind,
DiagnosticOperatorKind::NotIn.raw(),
),
],
);
assert_eq!(
GroupPlanError::field_target_aggregates_unsupported(
6,
AggregateKind::Last,
"private-name",
)
.diagnostic_facts(),
vec![
(DiagnosticFactTag::AggregateIndex, 6),
(
DiagnosticFactTag::AggregateKind,
DiagnosticAggregateKind::Last.raw(),
),
],
);
assert_eq!(
GroupPlanError::unknown_group_field_at(3, "private-name").diagnostic_facts(),
vec![(DiagnosticFactTag::GroupIndex, 3)],
);
assert_eq!(
GroupPlanError::duplicate_group_field(4, "private-name").diagnostic_facts(),
vec![(DiagnosticFactTag::GroupIndex, 4)],
);
}
#[test]
fn expression_diagnostics_retain_function_arity_and_argument_types() {
assert_eq!(
ExprPlanError::invalid_function_arity(Function::InList, 2, 3).diagnostic_facts(),
vec![
(
DiagnosticFactTag::FunctionKind,
DiagnosticFunctionKind::InList.raw(),
),
(DiagnosticFactTag::ExpectedArity, 2),
(DiagnosticFactTag::ActualArity, 3),
],
);
assert_eq!(
ExprPlanError::incompatible_function_arguments(
Function::Coalesce,
0,
2,
ExprPlanTypeClass::Text,
ExprPlanTypeClass::Numeric,
)
.diagnostic_facts(),
vec![
(
DiagnosticFactTag::FunctionKind,
DiagnosticFunctionKind::Coalesce.raw(),
),
(DiagnosticFactTag::ArgumentIndex, 0),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Text.raw(),
),
(DiagnosticFactTag::ArgumentIndex, 2),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Numeric.raw(),
),
],
);
}
#[test]
fn expression_diagnostics_retain_operator_and_branch_positions() {
assert_eq!(
ExprPlanError::invalid_binary_operands(
BinaryOp::Add,
ExprPlanTypeClass::Text,
ExprPlanTypeClass::Bool,
)
.diagnostic_facts(),
vec![
(
DiagnosticFactTag::OperatorKind,
DiagnosticOperatorKind::Add.raw(),
),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Text.raw(),
),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Bool.raw(),
),
],
);
assert_eq!(
ExprPlanError::incompatible_case_branch_types(
Some(1),
None,
ExprPlanTypeClass::Blob,
ExprPlanTypeClass::Structured,
)
.diagnostic_facts(),
vec![
(DiagnosticFactTag::BranchIndex, 1),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Blob.raw(),
),
(
DiagnosticFactTag::TypeFamily,
DiagnosticTypeFamily::Structured.raw(),
),
],
);
}
}