use codespan_reporting::diagnostic::Diagnostic as CsDiagnostic;
use thiserror::Error;
use crate::catalog::CatalogError;
use crate::common::{
BUG_URL, Diagnostic, FileId, InternalError, Span, primary_label, secondary_label,
};
use crate::parser::AggregationOperator;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum PlanError {
#[error("unknown head variable `{var}`")]
UnknownHeadVariable {
head_span: Span,
rule_span: Span,
var: String,
},
#[error(
"inconsistent aggregation for relation `{rel}` within a stratum: \
`{found_op:?}` at position {found_pos} conflicts with previously seen \
`{existing_op:?}` at position {existing_pos}"
)]
InconsistentAggregation {
rule_span: Span,
prior_span: Span,
rel: String,
existing_op: AggregationOperator,
existing_pos: usize,
found_op: AggregationOperator,
found_pos: usize,
},
#[error("rule head for `{rel}` contains {count} aggregations; at most one is allowed")]
MultipleAggregationsInHead {
head_span: Span,
rule_span: Span,
rel: String,
count: usize,
},
#[error(transparent)]
Catalog(#[from] CatalogError),
#[error(transparent)]
Internal(#[from] InternalError),
}
impl PlanError {
pub(crate) fn internal(detail: impl Into<String>) -> Self {
Self::Internal(InternalError::new("planner", detail, BUG_URL))
}
}
impl Diagnostic for PlanError {
fn to_diagnostic(&self) -> CsDiagnostic<FileId> {
match self {
PlanError::UnknownHeadVariable {
head_span,
rule_span,
var,
} => {
let mut labels = Vec::new();
if let Some(l) = primary_label(*head_span) {
labels.push(l.with_message(format!(
"`{var}` is referenced here but never bound by a positive body atom"
)));
}
if let Some(l) = secondary_label(*rule_span) {
labels.push(l.with_message("in this rule"));
}
CsDiagnostic::error()
.with_message(self.to_string())
.with_labels(labels)
.with_notes(vec![
"every variable in the rule head must appear in a positive body \
atom so its value is determined during evaluation"
.into(),
])
}
PlanError::InconsistentAggregation {
rule_span,
prior_span,
..
} => {
let mut labels = Vec::new();
if let Some(l) = primary_label(*rule_span) {
labels.push(l.with_message("conflicting aggregation declared here"));
}
if let Some(l) = secondary_label(*prior_span) {
labels.push(l.with_message("first aggregation declared here"));
}
CsDiagnostic::error()
.with_message(self.to_string())
.with_labels(labels)
.with_notes(vec![
"rules producing the same relation within a stratum must agree \
on the aggregation operator and its position in the head"
.into(),
])
}
PlanError::MultipleAggregationsInHead {
head_span,
rule_span,
..
} => {
let mut labels = Vec::new();
if let Some(l) = primary_label(*head_span) {
labels.push(l.with_message("multiple aggregations declared here"));
}
if let Some(l) = secondary_label(*rule_span) {
labels.push(l.with_message("in this rule"));
}
CsDiagnostic::error()
.with_message(self.to_string())
.with_labels(labels)
.with_notes(vec![
"split the head into multiple rules — each producing a separate \
relation — if you need several aggregated columns"
.into(),
])
}
PlanError::Catalog(e) => e.to_diagnostic(),
PlanError::Internal(ie) => ie.to_diagnostic(),
}
}
fn is_internal(&self) -> bool {
match self {
PlanError::Internal(_) => true,
PlanError::Catalog(e) => e.is_internal(),
_ => false,
}
}
}