Skip to main content

mysql_handler/engine/
error.rs

1// Copyright (C) 2026 ren-yamanashi
2//
3// This program is free software; you can redistribute it and/or modify
4// it under the terms of the GNU General Public License, version 2.0,
5// as published by the Free Software Foundation.
6//
7// This program is designed to work with certain software (including
8// but not limited to OpenSSL) that is licensed under separate terms,
9// as designated in a particular file or component or in included license
10// documentation. The authors of this program hereby grant you an additional
11// permission to link the program and your derivative works with the
12// separately licensed software that they have either included with
13// the program or referenced in the documentation.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, see <https://www.gnu.org/licenses/>.
22
23//! Error type and result alias for the storage-engine interface.
24
25use crate::sys;
26
27/// Errors a storage engine can return; each maps to a MySQL `HA_ERR_*` code
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum EngineError {
31    /// End of a table or index scan, returned from [`StorageEngine::rnd_next`]
32    /// when the scan is exhausted.
33    ///
34    /// [`StorageEngine::rnd_next`]: crate::engine::StorageEngine::rnd_next
35    EndOfFile,
36    /// The engine does not support the requested operation
37    WrongCommand,
38    /// The requested operation is unsupported on this engine, mapped to
39    /// `HA_ERR_UNSUPPORTED`. Distinct from [`WrongCommand`](Self::WrongCommand):
40    /// MySQL uses this code for capability gaps such as bulk-load execution
41    /// that the engine has not opted into.
42    Unsupported,
43    /// The supplied table or schema name is not valid UTF-8 or otherwise
44    /// unusable. Mapped to `HA_ERR_WRONG_TABLE_NAME` so operators see a
45    /// name-level diagnostic instead of a generic internal error.
46    InvalidName,
47    /// Generic internal error; prefer a more specific variant when possible
48    Internal,
49}
50
51impl EngineError {
52    /// Convert to the matching MySQL `HA_ERR_*` integer expected at the
53    /// `extern "C"` boundary.
54    #[must_use]
55    pub fn to_mysql_errno(self) -> i32 {
56        match self {
57            Self::EndOfFile => sys::HA_ERR_END_OF_FILE,
58            Self::WrongCommand => sys::HA_ERR_WRONG_COMMAND,
59            Self::Unsupported => sys::HA_ERR_UNSUPPORTED,
60            Self::InvalidName => sys::HA_ERR_WRONG_TABLE_NAME,
61            Self::Internal => sys::HA_ERR_INTERNAL_ERROR,
62        }
63    }
64}
65
66impl core::fmt::Display for EngineError {
67    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        let message = match self {
69            Self::EndOfFile => "end of table or index scan",
70            Self::WrongCommand => "operation not supported by the engine",
71            Self::Unsupported => "operation unsupported on this engine",
72            Self::InvalidName => "invalid table or schema name",
73            Self::Internal => "internal engine error",
74        };
75        f.write_str(message)
76    }
77}
78
79impl std::error::Error for EngineError {}
80
81/// Result alias used throughout the [`StorageEngine`](crate::engine::StorageEngine) trait
82pub type EngineResult<T = ()> = Result<T, EngineError>;
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn errno_mapping_matches_ha_err_codes() {
90        assert_eq!(
91            EngineError::EndOfFile.to_mysql_errno(),
92            sys::HA_ERR_END_OF_FILE
93        );
94        assert_eq!(
95            EngineError::WrongCommand.to_mysql_errno(),
96            sys::HA_ERR_WRONG_COMMAND
97        );
98        assert_eq!(
99            EngineError::Unsupported.to_mysql_errno(),
100            sys::HA_ERR_UNSUPPORTED
101        );
102        assert_eq!(
103            EngineError::InvalidName.to_mysql_errno(),
104            sys::HA_ERR_WRONG_TABLE_NAME
105        );
106        assert_eq!(
107            EngineError::Internal.to_mysql_errno(),
108            sys::HA_ERR_INTERNAL_ERROR
109        );
110    }
111
112    #[test]
113    fn display_renders_a_distinct_message_per_variant() {
114        let variants = [
115            EngineError::EndOfFile,
116            EngineError::WrongCommand,
117            EngineError::Unsupported,
118            EngineError::InvalidName,
119            EngineError::Internal,
120        ];
121        let messages: Vec<String> = variants.iter().map(ToString::to_string).collect();
122        assert!(messages.iter().all(|m| !m.is_empty()));
123        for (i, a) in messages.iter().enumerate() {
124            for b in &messages[i + 1..] {
125                assert_ne!(a, b);
126            }
127        }
128    }
129
130    #[test]
131    fn usable_as_std_error() {
132        fn take(_: &dyn std::error::Error) {}
133        take(&EngineError::Internal);
134    }
135}