Skip to main content

uqa_core/cancel/
cancelled.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The error of a canceled statement and why it was canceled.
8
9use thiserror::Error;
10
11/// Why a statement was canceled, which decides the message and SQLSTATE `PostgreSQL` reports.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum CancellationReason {
14    /// A client's cancel request or [`super::CancellationToken::cancel`] (`57014`).
15    #[default]
16    UserRequest,
17    /// The session's `statement_timeout` elapsed (`57014`).
18    StatementTimeout,
19    /// A lock wait outlasted the session's `lock_timeout` (`55P03`).
20    LockTimeout,
21    /// The session stayed idle in a transaction longer than its `idle_in_transaction_session_timeout` (`25P03`), which terminates it.
22    IdleInTransactionSessionTimeout,
23    /// The session stayed idle outside a transaction longer than its `idle_session_timeout` (`57P05`), which terminates it.
24    IdleSessionTimeout,
25    /// A transaction outlasted the session's `transaction_timeout` (`25P04`), which terminates the session.
26    TransactionTimeout,
27}
28
29impl CancellationReason {
30    /// The message `PostgreSQL` reports for a statement canceled for this reason.
31    pub const fn message(self) -> &'static str {
32        match self {
33            Self::UserRequest => "canceling statement due to user request",
34            Self::StatementTimeout => "canceling statement due to statement timeout",
35            Self::LockTimeout => "canceling statement due to lock timeout",
36            Self::IdleInTransactionSessionTimeout => {
37                "terminating connection due to idle-in-transaction timeout"
38            }
39            Self::IdleSessionTimeout => "terminating connection due to idle-session timeout",
40            Self::TransactionTimeout => "terminating connection due to transaction timeout",
41        }
42    }
43
44    /// Whether the reason terminates the session, which `PostgreSQL` reports at `FATAL` and no exception handler catches.
45    pub const fn terminates_session(self) -> bool {
46        matches!(
47            self,
48            Self::IdleInTransactionSessionTimeout
49                | Self::IdleSessionTimeout
50                | Self::TransactionTimeout
51        )
52    }
53
54    /// The SQLSTATE `PostgreSQL` reports: `57014` (`query_canceled`), or `55P03` (`lock_not_available`) for a lock timeout.
55    pub const fn sqlstate(self) -> &'static str {
56        match self {
57            Self::UserRequest | Self::StatementTimeout => super::SQLSTATE_QUERY_CANCELED,
58            Self::LockTimeout => "55P03",
59            Self::IdleInTransactionSessionTimeout => "25P03",
60            Self::IdleSessionTimeout => "57P05",
61            Self::TransactionTimeout => "25P04",
62        }
63    }
64
65    pub(super) const fn code(self) -> u8 {
66        match self {
67            Self::UserRequest => 1,
68            Self::StatementTimeout => 2,
69            Self::LockTimeout => 3,
70            Self::IdleInTransactionSessionTimeout => 4,
71            Self::IdleSessionTimeout => 5,
72            Self::TransactionTimeout => 6,
73        }
74    }
75
76    pub(super) const fn from_code(code: u8) -> Self {
77        match code {
78            2 => Self::StatementTimeout,
79            3 => Self::LockTimeout,
80            4 => Self::IdleInTransactionSessionTimeout,
81            5 => Self::IdleSessionTimeout,
82            6 => Self::TransactionTimeout,
83            _ => Self::UserRequest,
84        }
85    }
86}
87
88/// Raised when a statement is canceled; its `Display` payload is the message `PostgreSQL` reports for the reason.
89#[derive(Debug, Clone, Copy, Error, PartialEq, Eq, Default)]
90#[error("{}", .reason.message())]
91pub struct QueryCancelled {
92    pub reason: CancellationReason,
93}
94
95impl QueryCancelled {
96    /// A cancellation a client requested.
97    pub const USER_REQUEST: Self = Self::new(CancellationReason::UserRequest);
98
99    pub const fn new(reason: CancellationReason) -> Self {
100        Self { reason }
101    }
102
103    pub const fn sqlstate(&self) -> &'static str {
104        self.reason.sqlstate()
105    }
106}