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}