use super::AccessRequirementError;
#[cfg(feature = "sql")]
use crate::db::query::plan::validate::ExprPlanError;
#[cfg(feature = "sql")]
use crate::db::sql::{ddl::SqlDdlPrepareError, lowering::SqlLoweringError, parser::SqlParseError};
#[cfg(feature = "sql")]
use crate::error::{ErrorDetail, SchemaDdlAdmissionError, StoreError};
use crate::{
db::{
cursor::CursorPlanError,
numeric::NumericEvalError,
query::plan::{PlanError, PlannerError, PolicyPlanError},
schema::ValidateError,
},
error::{COMPACT_QUERY_DIAGNOSTIC_MESSAGE, ErrorClass, InternalError},
};
use icydb_diagnostic_code as diagnostic_code;
use std::fmt;
#[derive(Debug)]
pub enum QueryError {
Validate(Box<ValidateError>),
Plan(Box<PlanError>),
Intent(IntentError),
AccessRequirement(Box<AccessRequirementError>),
Execute(QueryExecutionError),
}
impl QueryError {
pub(in crate::db) fn validate(err: ValidateError) -> Self {
Self::Validate(Box::new(err))
}
pub(in crate::db) fn execute(err: InternalError) -> Self {
Self::Execute(QueryExecutionError::from(err))
}
#[must_use]
pub fn diagnostic(&self) -> diagnostic_code::Diagnostic {
if let Self::Execute(error) = self {
return error.diagnostic();
}
diagnostic_code::Diagnostic::new(
self.diagnostic_code(),
self.diagnostic_origin(),
self.diagnostic_detail(),
)
}
#[must_use]
pub fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
match self {
Self::Validate(_) => diagnostic_code::DiagnosticCode::QueryValidate,
Self::Plan(error) if error.is_invalid_continuation_cursor() => {
diagnostic_code::DiagnosticCode::QueryInvalidContinuationCursor
}
Self::Plan(error) if error.is_unordered_pagination() => {
diagnostic_code::DiagnosticCode::QueryUnorderedPagination
}
Self::Plan(_) => diagnostic_code::DiagnosticCode::QueryPlan,
Self::Intent(_) => diagnostic_code::DiagnosticCode::QueryIntent,
Self::AccessRequirement(_) => diagnostic_code::DiagnosticCode::QueryAccessRequirement,
Self::Execute(error) => error.diagnostic_code(),
}
}
fn diagnostic_origin(&self) -> diagnostic_code::ErrorOrigin {
match self {
Self::Execute(error) => error.as_internal().origin().diagnostic_origin(),
Self::Plan(error) if error.is_invalid_continuation_cursor() => {
diagnostic_code::ErrorOrigin::Cursor
}
Self::Validate(_) | Self::Plan(_) | Self::Intent(_) | Self::AccessRequirement(_) => {
diagnostic_code::ErrorOrigin::Query
}
}
}
fn diagnostic_detail(&self) -> Option<diagnostic_code::DiagnosticDetail> {
let kind = match self {
Self::Validate(_) => diagnostic_code::QueryErrorKind::Validate,
Self::Plan(error) if error.is_invalid_continuation_cursor() => {
diagnostic_code::QueryErrorKind::InvalidContinuationCursor
}
Self::Plan(error) if error.is_unordered_pagination() => {
diagnostic_code::QueryErrorKind::UnorderedPagination
}
Self::Plan(_) => diagnostic_code::QueryErrorKind::Plan,
Self::Intent(_) => diagnostic_code::QueryErrorKind::Intent,
Self::AccessRequirement(_) => diagnostic_code::QueryErrorKind::AccessRequirement,
Self::Execute(_) => return None,
};
Some(diagnostic_code::DiagnosticDetail::QueryKind { kind })
}
pub(in crate::db) fn invariant() -> Self {
Self::execute(InternalError::query_executor_invariant())
}
#[cfg(feature = "sql")]
pub(in crate::db) fn prepared_sql_select_lane_mismatch() -> Self {
Self::invariant()
}
#[cfg(feature = "sql")]
pub(in crate::db) fn prepared_sql_delete_lane_mismatch() -> Self {
Self::invariant()
}
#[cfg(feature = "sql")]
pub(in crate::db) fn prepared_sql_insert_lane_mismatch() -> Self {
Self::invariant()
}
#[cfg(feature = "sql")]
pub(in crate::db) fn prepared_sql_update_lane_mismatch() -> Self {
Self::invariant()
}
pub(in crate::db) const fn intent(err: IntentError) -> Self {
Self::Intent(err)
}
pub(in crate::db) fn unsupported_query() -> Self {
Self::execute(InternalError::query_unsupported())
}
#[cfg(feature = "sql")]
pub(in crate::db) fn sql_write_boundary(
boundary: diagnostic_code::SqlWriteBoundaryCode,
) -> Self {
Self::execute(InternalError::query_sql_write_boundary(boundary))
}
pub(in crate::db) fn from_numeric_eval_error(err: NumericEvalError) -> Self {
Self::execute(err.into_internal_error())
}
pub(in crate::db) fn serialize_internal() -> Self {
Self::execute(InternalError::serialize_internal())
}
pub(in crate::db) fn from_cursor_plan_error(err: CursorPlanError) -> Self {
Self::from(PlanError::from(err))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn unsupported_sql_feature(feature: diagnostic_code::SqlFeatureCode) -> Self {
Self::execute(InternalError::query_unsupported_sql_feature(feature))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn sql_lowering(reason: diagnostic_code::SqlLoweringCode) -> Self {
Self::execute(InternalError::query_sql_lowering(reason))
}
pub(in crate::db) fn unsupported_projection(
reason: diagnostic_code::QueryProjectionCode,
) -> Self {
Self::execute(InternalError::query_unsupported_projection(reason))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn sql_surface_mismatch(
mismatch: diagnostic_code::SqlSurfaceMismatchCode,
) -> Self {
Self::execute(InternalError::query_sql_surface_mismatch(mismatch))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn from_sql_lowering_error(err: SqlLoweringError) -> Self {
match err {
SqlLoweringError::Query(err) => *err,
SqlLoweringError::Parse(SqlParseError::UnsupportedFeature { feature }) => {
Self::unsupported_sql_feature(feature)
}
#[cfg(feature = "sql-explain")]
SqlLoweringError::UnexpectedQueryLaneStatement => {
Self::unsupported_query_lane_sql_statement()
}
SqlLoweringError::UnknownField { field } => {
Self::from(PlanError::from(ExprPlanError::unknown_field(field)))
}
err if let Some(reason) = err.compact_diagnostic_code() => Self::sql_lowering(reason),
_ => Self::unsupported_query(),
}
}
#[cfg(feature = "sql")]
pub(in crate::db) fn from_sql_parse_error(err: SqlParseError) -> Self {
Self::from_sql_lowering_error(SqlLoweringError::Parse(err))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn from_sql_ddl_prepare_error(err: SqlDdlPrepareError) -> Self {
let reason = err.admission_error();
Self::execute(InternalError::query_schema_ddl_admission(reason))
}
#[cfg(feature = "sql")]
pub(in crate::db) fn from_sql_ddl_execution_error(err: InternalError) -> Self {
if matches!(
err.detail(),
Some(ErrorDetail::Store(StoreError::SchemaDdlPublicationRaceLost))
) {
return Self::execute(InternalError::query_schema_ddl_admission(
SchemaDdlAdmissionError::PublicationRaceLost,
));
}
if matches!(
err.detail(),
Some(ErrorDetail::Store(
StoreError::SchemaDdlRewriteRequiresMigration
))
) {
return Self::execute(InternalError::query_schema_ddl_admission(
SchemaDdlAdmissionError::SchemaRewriteRequiresMigration,
));
}
if let Some(ErrorDetail::Store(StoreError::SchemaTransitionBudgetExceeded { resource })) =
err.detail()
{
return Self::execute(InternalError::query_schema_ddl_admission(
SchemaDdlAdmissionError::SchemaTransitionBudgetExceeded {
resource: *resource,
},
));
}
Self::execute(err)
}
#[cfg(feature = "sql-explain")]
pub(in crate::db) fn unsupported_query_lane_sql_statement() -> Self {
Self::unsupported_query()
}
pub(in crate::db) fn unknown_aggregate_target_field(_field: &str) -> Self {
Self::execute(InternalError::query_unknown_aggregate_target_field())
}
}
impl From<IntentError> for QueryError {
fn from(err: IntentError) -> Self {
Self::Intent(err)
}
}
impl From<QueryExecutionError> for QueryError {
fn from(err: QueryExecutionError) -> Self {
Self::Execute(err)
}
}
impl From<diagnostic_code::QueryReadAdmissionCode> for QueryError {
fn from(reason: diagnostic_code::QueryReadAdmissionCode) -> Self {
Self::execute(InternalError::from(reason))
}
}
impl From<AccessRequirementError> for QueryError {
fn from(err: AccessRequirementError) -> Self {
Self::AccessRequirement(Box::new(err))
}
}
impl From<ValidateError> for QueryError {
fn from(err: ValidateError) -> Self {
Self::validate(err)
}
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(COMPACT_QUERY_DIAGNOSTIC_MESSAGE)
}
}
impl std::error::Error for QueryError {}
#[derive(Debug)]
pub enum QueryExecutionError {
Corruption(InternalError),
IncompatiblePersistedFormat(InternalError),
InvariantViolation(InternalError),
Conflict(InternalError),
NotFound(InternalError),
Unsupported(InternalError),
Internal(InternalError),
}
impl QueryExecutionError {
#[must_use]
pub const fn as_internal(&self) -> &InternalError {
match self {
Self::Corruption(err)
| Self::IncompatiblePersistedFormat(err)
| Self::InvariantViolation(err)
| Self::Conflict(err)
| Self::NotFound(err)
| Self::Unsupported(err)
| Self::Internal(err) => err,
}
}
#[must_use]
pub fn diagnostic(&self) -> diagnostic_code::Diagnostic {
self.as_internal().diagnostic()
}
#[must_use]
pub fn diagnostic_code(&self) -> diagnostic_code::DiagnosticCode {
self.as_internal().diagnostic_code()
}
}
impl From<InternalError> for QueryExecutionError {
fn from(err: InternalError) -> Self {
match err.class {
ErrorClass::Corruption => Self::Corruption(err),
ErrorClass::IncompatiblePersistedFormat => Self::IncompatiblePersistedFormat(err),
ErrorClass::InvariantViolation => Self::InvariantViolation(err),
ErrorClass::Conflict => Self::Conflict(err),
ErrorClass::NotFound => Self::NotFound(err),
ErrorClass::Unsupported => Self::Unsupported(err),
ErrorClass::Internal => Self::Internal(err),
}
}
}
impl fmt::Display for QueryExecutionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(COMPACT_QUERY_DIAGNOSTIC_MESSAGE)
}
}
impl std::error::Error for QueryExecutionError {}
impl From<PlannerError> for QueryError {
fn from(err: PlannerError) -> Self {
match err {
PlannerError::Plan(err) => Self::from(*err),
PlannerError::Internal(err) => Self::execute(*err),
}
}
}
impl From<PlanError> for QueryError {
fn from(err: PlanError) -> Self {
Self::Plan(Box::new(err))
}
}
#[derive(Clone, Copy, Debug)]
pub enum IntentError {
PlanShape(PolicyPlanError),
HavingRequiresGroupBy,
}
impl From<PolicyPlanError> for IntentError {
fn from(err: PolicyPlanError) -> Self {
Self::PlanShape(err)
}
}
impl IntentError {
pub(in crate::db::query) const fn having_requires_group_by() -> Self {
Self::HavingRequiresGroupBy
}
}