#[cfg(test)]
mod tests;
#[cfg(feature = "sql")]
use crate::db::sql::{lowering::SqlLoweringError, parser::SqlParseError};
use crate::{
db::{
cursor::CursorPlanError,
executor::ExecutorPlanError,
query::{
expr::SortLowerError,
plan::{
CursorPagingPolicyError, FluentLoadPolicyViolation, IntentKeyAccessPolicyViolation,
PlanError, PlannerError, PolicyPlanError,
},
},
response::ResponseError,
schema::ValidateError,
},
error::{ErrorClass, InternalError},
};
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum QueryError {
#[error("{0}")]
Validate(Box<ValidateError>),
#[error("{0}")]
Plan(Box<PlanError>),
#[error("{0}")]
Intent(#[from] IntentError),
#[error("{0}")]
Response(#[from] ResponseError),
#[error("{0}")]
Execute(#[from] QueryExecutionError),
}
impl QueryError {
pub(crate) fn validate(err: ValidateError) -> Self {
Self::Validate(Box::new(err))
}
pub(crate) fn execute(err: InternalError) -> Self {
Self::Execute(QueryExecutionError::from(err))
}
pub(crate) fn invariant(message: impl Into<String>) -> Self {
Self::execute(InternalError::query_executor_invariant(message))
}
pub(crate) const fn intent(err: IntentError) -> Self {
Self::Intent(err)
}
pub(crate) fn unsupported_query(message: impl Into<String>) -> Self {
Self::execute(InternalError::query_unsupported(message))
}
pub(crate) fn serialize_internal(message: impl Into<String>) -> Self {
Self::execute(InternalError::serialize_internal(message))
}
pub(in crate::db) fn from_executor_plan_error(err: ExecutorPlanError) -> Self {
match err {
ExecutorPlanError::Cursor(err) => Self::from_cursor_plan_error(*err),
}
}
pub(in crate::db) fn from_cursor_plan_error(err: CursorPlanError) -> Self {
Self::from(PlanError::from(err))
}
#[cfg(feature = "sql")]
pub(crate) fn unsupported_sql_feature(feature: &'static str) -> Self {
Self::execute(InternalError::query_unsupported_sql_feature(feature))
}
#[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)
}
SqlLoweringError::UnexpectedQueryLaneStatement => {
Self::unsupported_query_lane_sql_statement()
}
other => Self::unsupported_query(format!(
"SQL query is not executable in this release: {other}"
)),
}
}
#[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(crate) fn unsupported_query_lane_sql_statement() -> Self {
Self::unsupported_query(
"query-lane SQL execution only accepts SELECT, DELETE, and EXPLAIN statements",
)
}
pub(crate) fn unknown_aggregate_target_field(field: &str) -> Self {
Self::unsupported_query(format!("unknown aggregate target field: {field}"))
}
pub(crate) fn scalar_paged_emitted_grouped_continuation() -> Self {
Self::invariant("scalar load pagination emitted grouped continuation token")
}
pub(crate) fn grouped_paged_emitted_scalar_continuation() -> Self {
Self::invariant("grouped pagination emitted scalar continuation token")
}
}
impl From<ValidateError> for QueryError {
fn from(err: ValidateError) -> Self {
Self::validate(err)
}
}
#[derive(Debug, ThisError)]
pub enum QueryExecutionError {
#[error("{0}")]
Corruption(InternalError),
#[error("{0}")]
IncompatiblePersistedFormat(InternalError),
#[error("{0}")]
InvariantViolation(InternalError),
#[error("{0}")]
Conflict(InternalError),
#[error("{0}")]
NotFound(InternalError),
#[error("{0}")]
Unsupported(InternalError),
#[error("{0}")]
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,
}
}
}
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 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))
}
}
impl From<SortLowerError> for QueryError {
fn from(err: SortLowerError) -> Self {
match err {
SortLowerError::Validate(err) => Self::validate(*err),
SortLowerError::Plan(err) => Self::from(*err),
}
}
}
#[derive(Clone, Copy, Debug, ThisError)]
pub enum IntentError {
#[error("{0}")]
PlanShape(#[from] PolicyPlanError),
#[error("by_ids() cannot be combined with predicates")]
ByIdsWithPredicate,
#[error("only() cannot be combined with predicates")]
OnlyWithPredicate,
#[error("multiple key access methods were used on the same query")]
KeyAccessConflict,
#[error("{0}")]
InvalidPagingShape(#[from] PagingIntentError),
#[error("grouped queries execute via execute()")]
GroupedRequiresDirectExecute,
#[error("HAVING requires GROUP BY")]
HavingRequiresGroupBy,
#[error("HAVING references an unknown grouped aggregate output")]
HavingReferencesUnknownAggregate,
}
impl IntentError {
pub(crate) const fn by_ids_with_predicate() -> Self {
Self::ByIdsWithPredicate
}
pub(crate) const fn only_with_predicate() -> Self {
Self::OnlyWithPredicate
}
pub(crate) const fn key_access_conflict() -> Self {
Self::KeyAccessConflict
}
pub(crate) const fn invalid_paging_shape(err: PagingIntentError) -> Self {
Self::InvalidPagingShape(err)
}
pub(crate) const fn cursor_requires_order() -> Self {
Self::invalid_paging_shape(PagingIntentError::cursor_requires_order())
}
pub(crate) const fn cursor_requires_limit() -> Self {
Self::invalid_paging_shape(PagingIntentError::cursor_requires_limit())
}
pub(crate) const fn cursor_requires_paged_execution() -> Self {
Self::invalid_paging_shape(PagingIntentError::cursor_requires_paged_execution())
}
pub(crate) const fn grouped_requires_direct_execute() -> Self {
Self::GroupedRequiresDirectExecute
}
pub(crate) const fn having_requires_group_by() -> Self {
Self::HavingRequiresGroupBy
}
pub(crate) const fn having_references_unknown_aggregate() -> Self {
Self::HavingReferencesUnknownAggregate
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ThisError)]
#[expect(clippy::enum_variant_names)]
pub enum PagingIntentError {
#[error(
"{message}",
message = CursorPlanError::cursor_requires_order_message()
)]
CursorRequiresOrder,
#[error(
"{message}",
message = CursorPlanError::cursor_requires_limit_message()
)]
CursorRequiresLimit,
#[error("cursor tokens can only be used with .page().execute()")]
CursorRequiresPagedExecution,
}
impl PagingIntentError {
pub(crate) const fn cursor_requires_order() -> Self {
Self::CursorRequiresOrder
}
pub(crate) const fn cursor_requires_limit() -> Self {
Self::CursorRequiresLimit
}
pub(crate) const fn cursor_requires_paged_execution() -> Self {
Self::CursorRequiresPagedExecution
}
}
impl From<CursorPagingPolicyError> for PagingIntentError {
fn from(err: CursorPagingPolicyError) -> Self {
match err {
CursorPagingPolicyError::CursorRequiresOrder => Self::cursor_requires_order(),
CursorPagingPolicyError::CursorRequiresLimit => Self::cursor_requires_limit(),
}
}
}
impl From<CursorPagingPolicyError> for IntentError {
fn from(err: CursorPagingPolicyError) -> Self {
match err {
CursorPagingPolicyError::CursorRequiresOrder => Self::cursor_requires_order(),
CursorPagingPolicyError::CursorRequiresLimit => Self::cursor_requires_limit(),
}
}
}
impl From<IntentKeyAccessPolicyViolation> for IntentError {
fn from(err: IntentKeyAccessPolicyViolation) -> Self {
match err {
IntentKeyAccessPolicyViolation::KeyAccessConflict => Self::key_access_conflict(),
IntentKeyAccessPolicyViolation::ByIdsWithPredicate => Self::by_ids_with_predicate(),
IntentKeyAccessPolicyViolation::OnlyWithPredicate => Self::only_with_predicate(),
}
}
}
impl From<FluentLoadPolicyViolation> for IntentError {
fn from(err: FluentLoadPolicyViolation) -> Self {
match err {
FluentLoadPolicyViolation::CursorRequiresPagedExecution => {
Self::cursor_requires_paged_execution()
}
FluentLoadPolicyViolation::GroupedRequiresDirectExecute => {
Self::grouped_requires_direct_execute()
}
FluentLoadPolicyViolation::CursorRequiresOrder => Self::cursor_requires_order(),
FluentLoadPolicyViolation::CursorRequiresLimit => Self::cursor_requires_limit(),
}
}
}