nodedb 0.4.0

Local-first, real-time, edge-to-cloud hybrid database for multi-modal workloads
Documentation
// SPDX-License-Identifier: BUSL-1.1

//! Data Plane `ErrorCode` to PostgreSQL SQLSTATE mapping (protocol-neutral).

use nodedb_types::error::sqlstate;

use crate::bridge::envelope::ErrorCode;

/// Map a Data Plane `ErrorCode` to SQLSTATE.
pub fn error_code_to_sqlstate(code: &ErrorCode) -> (&'static str, &'static str, String) {
    match code {
        ErrorCode::DeadlineExceeded => (
            "ERROR",
            sqlstate::QUERY_CANCELED,
            "query cancelled due to deadline".into(),
        ),
        ErrorCode::RejectedConstraint { constraint, detail } => (
            "ERROR",
            sqlstate::UNIQUE_VIOLATION,
            if detail.is_empty() {
                format!("constraint violation: {constraint}")
            } else {
                format!("constraint violation: {constraint}: {detail}")
            },
        ),
        ErrorCode::RejectedPrevalidation { reason } => (
            "ERROR",
            sqlstate::CHECK_VIOLATION,
            format!("pre-validation rejected: {reason}"),
        ),
        ErrorCode::NotFound => ("ERROR", sqlstate::NO_DATA, "not found".into()),
        ErrorCode::RejectedAuthz => (
            "ERROR",
            sqlstate::INSUFFICIENT_PRIVILEGE,
            "authorization denied".into(),
        ),
        ErrorCode::ConflictRetry => (
            "ERROR",
            sqlstate::SERIALIZATION_FAILURE,
            "write conflict, retry".into(),
        ),
        ErrorCode::FanOutExceeded => (
            "ERROR",
            sqlstate::STATEMENT_TOO_COMPLEX,
            "fan-out limit exceeded".into(),
        ),
        ErrorCode::ResourcesExhausted => (
            "ERROR",
            sqlstate::OUT_OF_MEMORY,
            "query result exceeded the scan memory budget; add a LIMIT clause \
             or a more selective filter, or raise \
             [tuning.query] max_scan_result_bytes"
                .into(),
        ),
        ErrorCode::RejectedDanglingEdge { missing_node } => (
            "ERROR",
            sqlstate::FOREIGN_KEY_VIOLATION,
            format!("edge rejected: node \"{missing_node}\" does not exist"),
        ),
        ErrorCode::DuplicateWrite => (
            "ERROR",
            sqlstate::UNIQUE_VIOLATION,
            "duplicate write detected via idempotency key".into(),
        ),
        ErrorCode::AppendOnlyViolation { collection } => (
            "ERROR",
            sqlstate::APPEND_ONLY_VIOLATION,
            format!("append-only violation: UPDATE/DELETE not allowed on {collection}"),
        ),
        ErrorCode::BalanceViolation { collection, detail } => (
            "ERROR",
            sqlstate::BALANCE_VIOLATION,
            format!("balance violation on {collection}: {detail}"),
        ),
        ErrorCode::PeriodLocked { collection } => (
            "ERROR",
            sqlstate::PERIOD_LOCKED,
            format!("period locked: writes rejected on {collection}"),
        ),
        ErrorCode::RetentionViolation { collection } => (
            "ERROR",
            sqlstate::RETENTION_VIOLATION,
            format!("retention violation: cannot delete from {collection}"),
        ),
        ErrorCode::LegalHoldActive { collection } => (
            "ERROR",
            sqlstate::LEGAL_HOLD_ACTIVE,
            format!("legal hold active: cannot delete from {collection}"),
        ),
        ErrorCode::StateTransitionViolation { collection, detail } => (
            "ERROR",
            sqlstate::STATE_TRANSITION_VIOLATION,
            format!("state transition violation on {collection}: {detail}"),
        ),
        ErrorCode::TransitionCheckViolation { collection } => (
            "ERROR",
            sqlstate::TRANSITION_CHECK_VIOLATION,
            format!("transition check violation on {collection}"),
        ),
        ErrorCode::TypeGuardViolation { collection, detail } => (
            "ERROR",
            sqlstate::TYPE_GUARD_VIOLATION,
            format!("type guard violation on {collection}: {detail}"),
        ),
        ErrorCode::TypeMismatch { collection, detail } => (
            "ERROR",
            sqlstate::CANNOT_COERCE,
            format!("type mismatch on {collection}: {detail}"),
        ),
        ErrorCode::OverflowError { collection } => (
            "ERROR",
            sqlstate::NUMERIC_VALUE_OUT_OF_RANGE,
            format!("arithmetic overflow on {collection}"),
        ),
        ErrorCode::InsufficientBalance { collection, detail } => (
            "ERROR",
            sqlstate::CHECK_VIOLATION,
            format!("insufficient balance on {collection}: {detail}"),
        ),
        ErrorCode::RateExceeded {
            gate,
            retry_after_ms,
        } => (
            "ERROR",
            sqlstate::STATEMENT_TOO_COMPLEX,
            format!("rate limit exceeded for {gate}, retry after {retry_after_ms}ms"),
        ),
        ErrorCode::CollectionDraining { collection } => (
            "ERROR",
            sqlstate::CANNOT_CONNECT_NOW,
            format!(
                "collection '{collection}' is draining for hard-delete; retry after purge completes"
            ),
        ),
        ErrorCode::RecursionDepthExceeded {
            cte_name,
            max_depth,
        } => (
            "ERROR",
            sqlstate::PROGRAM_LIMIT_EXCEEDED,
            format!(
                "WITH RECURSIVE CTE '{cte_name}' exceeded max recursion depth {max_depth}; \
                 add a stricter termination condition or raise max_recursion_depth"
            ),
        ),
        ErrorCode::Internal { detail } => ("ERROR", sqlstate::INTERNAL_ERROR, detail.clone()),
        ErrorCode::Unsupported { detail } => {
            ("ERROR", sqlstate::FEATURE_NOT_SUPPORTED, detail.clone())
        }
        ErrorCode::RollbackFailed {
            entry_index,
            detail,
        } => (
            "ERROR",
            sqlstate::INTERNAL_ERROR,
            format!(
                "transaction rollback failed at undo entry {entry_index}: {detail}; \
                 shard state is unknown — restart required"
            ),
        ),
        // OllpRetryRequired is an internal scheduler signal and should not
        // reach the pgwire layer as a user-visible error. If it does, surface
        // it as a serialization failure so clients retry automatically.
        ErrorCode::OllpRetryRequired => (
            "ERROR",
            sqlstate::SERIALIZATION_FAILURE,
            "optimistic predicate retry required; transaction will be retried".into(),
        ),
        ErrorCode::TxnOverlayMemoryExceeded { limit } => (
            "ERROR",
            sqlstate::PROGRAM_LIMIT_EXCEEDED,
            format!(
                "transaction staging overlay exceeded its {limit}-byte per-core budget; \
                 split the transaction into smaller batches"
            ),
        ),
    }
}