Skip to main content

mysql_handler/
hton.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//! Engine-level handlerton interface.
24//!
25//! Where [`StorageEngine`](crate::engine::StorageEngine) is per-table, the
26//! handlerton is a per-engine singleton: one instance serves every connection.
27//! An engine opts into engine-level behaviour by implementing [`Handlerton`]
28//! and registering it with
29//! [`register_handlerton`](crate::runtime::register_handlerton). Engines that
30//! need nothing beyond table handling skip registration and keep the
31//! zero-config defaults.
32
33#[doc(hidden)]
34pub mod binlog;
35mod binlog_kind;
36mod capabilities;
37#[doc(hidden)]
38pub mod capability_ffi;
39#[doc(hidden)]
40pub mod clone;
41mod clone_kind;
42mod cost_constants;
43#[doc(hidden)]
44pub mod database;
45#[doc(hidden)]
46pub mod dict;
47mod dict_kind;
48#[doc(hidden)]
49pub mod discovery;
50#[doc(hidden)]
51pub mod engine_log;
52#[doc(hidden)]
53pub mod ffi;
54#[doc(hidden)]
55pub mod fk_hooks;
56mod flags;
57#[doc(hidden)]
58pub mod lifecycle;
59#[doc(hidden)]
60pub mod misc_optimizer;
61#[doc(hidden)]
62pub mod misc_stats;
63mod notification_kind;
64#[doc(hidden)]
65pub mod notifications;
66#[doc(hidden)]
67pub mod page_track;
68mod panic_function;
69mod recover_xa_state;
70mod result;
71#[doc(hidden)]
72pub mod savepoint_ffi;
73#[doc(hidden)]
74pub mod sdi;
75#[doc(hidden)]
76pub mod secondary_engine;
77#[doc(hidden)]
78pub mod secondary_engine_fail_reason;
79mod secondary_engine_kind;
80mod stat_print_sink;
81mod stat_type;
82#[doc(hidden)]
83pub mod statistics_callbacks;
84#[doc(hidden)]
85pub mod status;
86mod table_statistics;
87#[doc(hidden)]
88pub mod tablespace;
89mod tablespace_kind;
90mod tablespace_statistics;
91mod transaction;
92#[doc(hidden)]
93pub mod txn_context;
94#[doc(hidden)]
95pub mod txn_ffi;
96#[doc(hidden)]
97pub mod txn_row_ffi;
98#[doc(hidden)]
99pub mod xa;
100mod xa_recover_collector;
101mod xa_state_list_collector;
102
103pub use binlog_kind::{BinlogCommand, BinlogFunc};
104pub use capabilities::HtonCapabilities;
105pub use clone_kind::{HaCloneMode, HaCloneType};
106pub use cost_constants::CostConstants;
107pub use dict_kind::{DictInitMode, DictRecoveryMode};
108pub use flags::HtonFlags;
109pub use notification_kind::{HaNotificationType, SelectExecutedIn};
110pub use panic_function::HaPanicFunction;
111pub use recover_xa_state::RecoverXaState;
112pub use secondary_engine_kind::{
113    SecondaryEngineGraphSimplificationRequest, SecondaryEngineOptimizerRequest,
114};
115pub use stat_print_sink::StatPrintSink;
116pub use stat_type::HaStatType;
117pub use table_statistics::TableStatistics;
118pub use tablespace_kind::{TablespaceType, TsCommandType};
119pub use tablespace_statistics::TablespaceStatistics;
120pub use transaction::TxnSession;
121pub use xa_recover_collector::XaRecoverCollector;
122pub use xa_state_list_collector::XaStateListCollector;
123
124use crate::engine::EngineResult;
125use crate::sys;
126
127/// The engine-level handlerton interface: the capabilities and `handlerton`
128/// struct fields that apply to the engine as a whole rather than to a single
129/// table.
130///
131/// Every method has a default, so an engine implements only what it needs and
132/// an empty `impl Handlerton for MyEngine {}` is a valid handler-only
133/// handlerton. The singleton is shared across all connection threads, hence
134/// the `Send + Sync` bound — do not relax it.
135///
136/// # Examples
137///
138/// ```
139/// use mysql_handler::hton::{Handlerton, HtonCapabilities, HtonFlags};
140///
141/// struct MyHandlerton;
142/// impl Handlerton for MyHandlerton {
143///     fn capabilities(&self) -> HtonCapabilities {
144///         HtonCapabilities::TRANSACTIONS
145///     }
146/// }
147///
148/// assert!(MyHandlerton.capabilities().contains(HtonCapabilities::TRANSACTIONS));
149/// assert_eq!(MyHandlerton.flags(), HtonFlags::CAN_RECREATE);
150/// ```
151pub trait Handlerton: Send + Sync {
152    /// The engine-level callback groups this handlerton implements.
153    ///
154    /// Each capability gates a group of `handlerton` callbacks; a group is
155    /// wired into MySQL only when its bit is set here. Defaults to
156    /// [`HtonCapabilities::empty`] — a handler-only engine.
157    fn capabilities(&self) -> HtonCapabilities {
158        HtonCapabilities::empty()
159    }
160
161    /// The `handlerton` flags (`HTON_*`).
162    ///
163    /// Defaults to [`HtonFlags::CAN_RECREATE`], matching the flag the
164    /// zero-config engine sets today. Return [`HtonFlags::NONE`] to opt out.
165    fn flags(&self) -> HtonFlags {
166        HtonFlags::CAN_RECREATE
167    }
168
169    /// Bytes of per-savepoint scratch the engine needs (`handlerton`'s
170    /// `savepoint_offset`). MySQL allocates this much per savepoint and hands it
171    /// to the savepoint callbacks as their `sv` buffer. Only consulted when the
172    /// engine declares [`HtonCapabilities::SAVEPOINTS`]; defaults to 0.
173    fn savepoint_offset(&self) -> u32 {
174        0
175    }
176
177    /// Called when a connection that has touched this engine closes, so the
178    /// engine can release per-connection state.
179    ///
180    /// MySQL only invokes this for a connection whose `thd->ha_data` slot is
181    /// non-empty, so a handler-only engine that never stores per-connection
182    /// state does not see it. Defaults to success (nothing to release).
183    ///
184    /// # Errors
185    /// Returns an [`EngineError`](crate::engine::EngineError) if releasing the
186    /// connection's state fails; MySQL logs it and the connection still closes.
187    fn close_connection(&self, _thd: Option<&sys::THD>) -> EngineResult {
188        Ok(())
189    }
190
191    /// Notification that a connection or its current statement is being
192    /// terminated (`KILL`). Defaults to no-op.
193    fn kill_connection(&self, _thd: Option<&sys::THD>) {}
194
195    /// Called before the data dictionary shuts down so the engine can stop
196    /// background tasks that might still access it. Defaults to no-op.
197    fn pre_dd_shutdown(&self) {}
198
199    /// Reset session-scoped plugin variables before the connection ends.
200    /// Defaults to no-op.
201    fn reset_plugin_vars(&self, _thd: Option<&sys::THD>) {}
202
203    /// Create the per-connection [`TxnSession`] for a new transaction.
204    ///
205    /// Invoked (through the shim) when a connection first joins a transaction,
206    /// but only for an engine that declares
207    /// [`HtonCapabilities::TRANSACTIONS`]. The returned session is stored in
208    /// the connection's `ha_data` and driven through `commit` / `rollback`
209    /// until the transaction ends. The default returns an inert session, so an
210    /// engine declaring `TRANSACTIONS` must override this to do real work.
211    fn begin_transaction(&self) -> Box<dyn TxnSession> {
212        Box::new(NoopTxnSession)
213    }
214
215    /// Commit the prepared XA transaction identified by `xid`, found during
216    /// recovery. Wired only under [`HtonCapabilities::XA`]; `xid` is opaque
217    /// (inspect only the bytes the engine needs). Defaults to unsupported.
218    ///
219    /// # Errors
220    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
221    /// by default; an XA engine overrides this and errors only on a real failure.
222    fn commit_by_xid(&self, xid: Option<&sys::XID>) -> EngineResult {
223        let _ = xid;
224        Err(crate::engine::EngineError::Unsupported)
225    }
226
227    /// Roll back the prepared XA transaction identified by `xid`. Wired only
228    /// under [`HtonCapabilities::XA`]. Defaults to unsupported.
229    ///
230    /// # Errors
231    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
232    /// by default.
233    fn rollback_by_xid(&self, xid: Option<&sys::XID>) -> EngineResult {
234        let _ = xid;
235        Err(crate::engine::EngineError::Unsupported)
236    }
237
238    /// Mark the connection's externally-coordinated transactions as prepared in
239    /// the server transaction coordinator. Wired only under
240    /// [`HtonCapabilities::XA`]. Defaults to unsupported.
241    ///
242    /// # Errors
243    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
244    /// by default.
245    fn set_prepared_in_tc(&self, thd: Option<&sys::THD>) -> EngineResult {
246        let _ = thd;
247        Err(crate::engine::EngineError::Unsupported)
248    }
249
250    /// Mark the prepared XA transaction identified by `xid` as prepared in the
251    /// server transaction coordinator. Wired only under
252    /// [`HtonCapabilities::XA`]. Defaults to unsupported.
253    ///
254    /// # Errors
255    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
256    /// by default.
257    fn set_prepared_in_tc_by_xid(&self, xid: Option<&sys::XID>) -> EngineResult {
258        let _ = xid;
259        Err(crate::engine::EngineError::Unsupported)
260    }
261
262    /// Report every externally-coordinated XA transaction the engine considers
263    /// prepared, along with its [`RecoverXaState`]. Push each entry into
264    /// `collector`; the shim forwards them to MySQL's `Xa_state_list::add`.
265    /// Wired only under [`HtonCapabilities::XA`]; the default reports nothing.
266    ///
267    /// The receiver is `&self` to match the other handlerton accessors;
268    /// engines that mutate state during recovery should use interior
269    /// mutability (e.g. a `Mutex`).
270    ///
271    /// # Errors
272    /// Returns an [`EngineError`](crate::engine::EngineError) if the engine
273    /// cannot enumerate its prepared transactions.
274    fn recover_prepared_in_tc(&self, collector: &mut XaStateListCollector) -> EngineResult {
275        let _ = collector;
276        Ok(())
277    }
278
279    /// Recover every prepared XA transaction the engine knows about. Push
280    /// each one into `collector` up to its `capacity`; the shim writes
281    /// each pushed entry into MySQL's `XA_recover_txn[]` array. Returning
282    /// from this method, the shim hands `collector.filled()` back to
283    /// MySQL as the recovered-transaction count.
284    ///
285    /// Wired only under [`HtonCapabilities::XA`]; the default reports
286    /// nothing recovered, matching the previous NULL handlerton pointer.
287    fn recover(&self, collector: &mut XaRecoverCollector) {
288        let _ = collector;
289    }
290
291    /// Shutdown notification: the server is invoking `ha_panic` to wind every
292    /// engine down. Defaults to success — the engine has nothing to flush on
293    /// process exit.
294    ///
295    /// # Errors
296    /// Returns an [`EngineError`](crate::engine::EngineError) if the engine
297    /// fails to shut down cleanly; MySQL logs it and continues stopping.
298    fn panic(&self, flag: HaPanicFunction) -> EngineResult {
299        let _ = flag;
300        Ok(())
301    }
302
303    /// Start a consistent-snapshot read for the connection, as requested by
304    /// `START TRANSACTION WITH CONSISTENT SNAPSHOT`. Wired only when the engine
305    /// declares [`HtonCapabilities::TRANSACTIONS`]; defaults to success (the
306    /// engine has no snapshot semantics to install).
307    ///
308    /// # Errors
309    /// Returns an [`EngineError`](crate::engine::EngineError) if the snapshot
310    /// cannot be set up.
311    fn start_consistent_snapshot(&self, _thd: Option<&sys::THD>) -> EngineResult {
312        Ok(())
313    }
314
315    /// Flush durable state to disk. `binlog_group_flush` is true when the
316    /// invocation comes from the binary log group-commit flush stage, false
317    /// from `FLUSH LOGS` or shutdown. Defaults to success (nothing to flush).
318    ///
319    /// # Errors
320    /// Returns an [`EngineError`](crate::engine::EngineError) if the flush
321    /// fails; MySQL reports the failure to the client.
322    fn flush_logs(&self, _binlog_group_flush: bool) -> EngineResult {
323        Ok(())
324    }
325
326    /// Populate `SHOW ENGINE <name> STATUS` / `LOGS` / `MUTEX` output by
327    /// emitting rows through `sink`. The trait default emits no rows, leaving
328    /// the engine status empty.
329    ///
330    /// # Errors
331    /// Returns an [`EngineError`](crate::engine::EngineError) if collecting or
332    /// emitting status fails.
333    fn show_status(
334        &self,
335        _thd: Option<&sys::THD>,
336        _sink: &StatPrintSink<'_>,
337        _stat: HaStatType,
338    ) -> EngineResult {
339        Ok(())
340    }
341
342    /// Per-partition capability bitfield (`HA_*_PARTITION_*` flags from
343    /// `sql_partition.h`). Consulted only when the engine declares
344    /// [`HtonCapabilities::PARTITIONING`]; defaults to 0 — no special
345    /// partitioning behaviour.
346    fn partition_flags(&self) -> u32 {
347        0
348    }
349
350    /// Fill engine-defined rows of an `INFORMATION_SCHEMA` table. The default
351    /// adds no rows, which is correct for an engine with no engine-only I_S
352    /// surface.
353    ///
354    /// # Errors
355    /// Returns an [`EngineError`](crate::engine::EngineError) if collecting
356    /// rows fails.
357    fn fill_is_table(&self, _thd: Option<&sys::THD>) -> EngineResult {
358        Ok(())
359    }
360
361    /// Roll the engine's log files forward as part of an in-place server
362    /// upgrade. Defaults to success — the engine has no upgrade-specific log
363    /// work to do.
364    ///
365    /// # Errors
366    /// Returns an [`EngineError`](crate::engine::EngineError) if the upgrade
367    /// step fails; MySQL aborts the upgrade.
368    fn upgrade_logs(&self, _thd: Option<&sys::THD>) -> EngineResult {
369        Ok(())
370    }
371
372    /// Finalize upgrade-specific state, called regardless of whether the
373    /// upgrade succeeded. `failed_upgrade` is true when MySQL rolled back the
374    /// upgrade. Defaults to success.
375    ///
376    /// # Errors
377    /// Returns an [`EngineError`](crate::engine::EngineError) if the cleanup
378    /// step fails.
379    fn finish_upgrade(&self, _thd: Option<&sys::THD>, _failed_upgrade: bool) -> EngineResult {
380        Ok(())
381    }
382
383    /// Whether the supplied database name is reserved by the engine and must
384    /// not be created at the SQL layer. Defaults to `false` — the engine
385    /// reserves no names.
386    fn is_reserved_db_name(&self, _name: &str) -> bool {
387        false
388    }
389
390    /// Recover a table whose dictionary entry is missing by reading the
391    /// engine-side description back into `db.name`. Defaults to "not found"
392    /// (the trait returns `Unsupported`, which the shim translates to
393    /// `HA_ERR_NO_SUCH_TABLE`).
394    ///
395    /// The shim does not yet marshal the engine's SDI blob back through the
396    /// `frmblob` / `frmlen` output parameters, so overriding this to return
397    /// `Ok(())` would claim "found" without supplying the table definition and
398    /// MySQL would fail downstream with `ER_NO_SUCH_TABLE` anyway. Keep the
399    /// default until that return path is wired.
400    ///
401    /// # Errors
402    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
403    /// by default to report "no such table".
404    fn discover(&self, _thd: Option<&sys::THD>, _db: &str, _name: &str) -> EngineResult {
405        Err(crate::engine::EngineError::Unsupported)
406    }
407
408    /// List the tables (or directory entries) the engine knows about under
409    /// `db` / `path`. `wild` is an optional shell-style filter; `dir` is true
410    /// when MySQL is asking for sub-directories. Defaults to success with no
411    /// entries reported.
412    ///
413    /// # Errors
414    /// Returns an [`EngineError`](crate::engine::EngineError) if enumeration
415    /// fails.
416    fn find_files(
417        &self,
418        _thd: Option<&sys::THD>,
419        _db: &str,
420        _path: &str,
421        _wild: Option<&str>,
422        _dir: bool,
423    ) -> EngineResult {
424        Ok(())
425    }
426
427    /// Whether `db.name` exists in the engine. The handler.cc caller maps
428    /// `true` to `HA_ERR_TABLE_EXIST` and `false` to `HA_ERR_NO_SUCH_TABLE`,
429    /// so the default `false` matches "engine has no such table".
430    fn table_exists_in_engine(&self, _thd: Option<&sys::THD>, _db: &str, _name: &str) -> bool {
431        false
432    }
433
434    /// Whether `db.table_name` is a system table this engine supports.
435    /// `is_sql_layer_system_table` is `true` when the table is an SQL-layer
436    /// system table (such as `mysql.*`); the engine should answer `false`
437    /// unless it specifically supports those at the engine layer. Defaults to
438    /// `false` — the engine supports no system tables.
439    fn is_supported_system_table(
440        &self,
441        _db: &str,
442        _table_name: &str,
443        _is_sql_layer_system_table: bool,
444    ) -> bool {
445        false
446    }
447
448    /// Notification fired after a `SELECT` completed, with the engine that
449    /// actually executed it. Defaults to no-op.
450    fn notify_after_select(&self, _thd: Option<&sys::THD>, _executed_in: SelectExecutedIn) {}
451
452    /// Notification fired when a table is created, with the bare `db` /
453    /// `table_name` strings. The `HA_CREATE_INFO` parameter is opaque to Rust
454    /// today and is not surfaced. Defaults to no-op.
455    fn notify_create_table(&self, _db: &str, _table_name: &str) {}
456
457    /// Notification fired when a table is dropped. The `Table_ref` parameter
458    /// is opaque to Rust and is not surfaced. Defaults to no-op.
459    fn notify_drop_table(&self) {}
460
461    /// Pre/post notification around an exclusive metadata lock acquisition.
462    /// Returning an error from the pre-event hook tells MySQL to abort
463    /// acquisition (the shim translates `Err` to `true`, matching MySQL's
464    /// "failure" convention). Defaults to success.
465    ///
466    /// # Errors
467    /// Returns an [`EngineError`](crate::engine::EngineError) to veto the
468    /// pre-event lock acquisition. Returning `Err` from a post-event
469    /// notification is logged by MySQL but does not undo the lock.
470    fn notify_exclusive_mdl(
471        &self,
472        _thd: Option<&sys::THD>,
473        _mdl_key: Option<&sys::MdlKey>,
474        _kind: HaNotificationType,
475    ) -> EngineResult {
476        Ok(())
477    }
478
479    /// Pre/post notification around an `ALTER TABLE`. Defaults to success.
480    ///
481    /// # Errors
482    /// Returns an [`EngineError`](crate::engine::EngineError) to veto a
483    /// pre-event alter.
484    fn notify_alter_table(
485        &self,
486        _thd: Option<&sys::THD>,
487        _mdl_key: Option<&sys::MdlKey>,
488        _kind: HaNotificationType,
489    ) -> EngineResult {
490        Ok(())
491    }
492
493    /// Pre/post notification around a `RENAME TABLE`. The old / new
494    /// db.table names are passed as borrowed `&str`. Defaults to success.
495    ///
496    /// # Errors
497    /// Returns an [`EngineError`](crate::engine::EngineError) to veto a
498    /// pre-event rename.
499    #[allow(clippy::too_many_arguments)]
500    fn notify_rename_table(
501        &self,
502        _thd: Option<&sys::THD>,
503        _mdl_key: Option<&sys::MdlKey>,
504        _kind: HaNotificationType,
505        _old_db: &str,
506        _old_name: &str,
507        _new_db: &str,
508        _new_name: &str,
509    ) -> EngineResult {
510        Ok(())
511    }
512
513    /// Pre/post notification around a `TRUNCATE TABLE`. Defaults to success.
514    ///
515    /// # Errors
516    /// Returns an [`EngineError`](crate::engine::EngineError) to veto a
517    /// pre-event truncate.
518    fn notify_truncate_table(
519        &self,
520        _thd: Option<&sys::THD>,
521        _mdl_key: Option<&sys::MdlKey>,
522        _kind: HaNotificationType,
523    ) -> EngineResult {
524        Ok(())
525    }
526
527    /// Binlog-related operation MySQL is asking the engine to handle
528    /// (`BFN_*`). Defaults to success (engine is binlog-agnostic).
529    ///
530    /// # Errors
531    /// Returns an [`EngineError`](crate::engine::EngineError) if the operation
532    /// fails.
533    fn binlog_func(&self, _thd: Option<&sys::THD>, _func: BinlogFunc) -> EngineResult {
534        Ok(())
535    }
536
537    /// Notification of a DDL command being written to the binary log. The
538    /// `query` text is bounded; per the security rule the trait must not log
539    /// it. Defaults to no-op.
540    fn binlog_log_query(
541        &self,
542        _thd: Option<&sys::THD>,
543        _command: BinlogCommand,
544        _query: &str,
545        _db: &str,
546        _table: &str,
547    ) {
548    }
549
550    /// Notification of an ACL change (`GRANT` / `REVOKE` / privilege table
551    /// modification). The `Acl_change_notification` parameter is opaque to
552    /// Rust today and is not surfaced. Defaults to no-op.
553    fn acl_notify(&self, _thd: Option<&sys::THD>) {}
554
555    /// Notification of `DROP DATABASE`. `path` is the schema's storage path
556    /// (typically `./<dbname>`). Always wired on a registered handlerton.
557    /// Defaults to no-op.
558    fn drop_database(&self, _path: &str) {}
559
560    /// Whether `tablespace_name` is acceptable for the given DDL command. Wired
561    /// only under [`HtonCapabilities::TABLESPACES`]; defaults to `true` so the
562    /// engine does not reject names for an unrelated reason.
563    fn is_valid_tablespace_name(&self, _cmd: TsCommandType, _tablespace_name: &str) -> bool {
564        true
565    }
566
567    /// Look up the tablespace name that holds `db.table_name`. The current
568    /// binding does not yet round-trip the output back to MySQL — the shim
569    /// leaves the `LEX_CSTRING*` empty — so an override returning `Ok(())` is
570    /// equivalent to "no tablespace information available".
571    ///
572    /// # Errors
573    /// Returns an [`EngineError`](crate::engine::EngineError) on lookup
574    /// failure.
575    fn get_tablespace(
576        &self,
577        _thd: Option<&sys::THD>,
578        _db_name: &str,
579        _table_name: &str,
580    ) -> EngineResult {
581        Ok(())
582    }
583
584    /// Apply a tablespace DDL (`CREATE TABLESPACE`, `ALTER TABLESPACE`, ...).
585    /// `ts_info` is opaque today. Wired only under
586    /// [`HtonCapabilities::TABLESPACES`]; defaults to success.
587    ///
588    /// # Errors
589    /// Returns an [`EngineError`](crate::engine::EngineError) if the DDL fails.
590    fn alter_tablespace(
591        &self,
592        _thd: Option<&sys::THD>,
593        _ts_info: Option<&sys::StAlterTablespace>,
594    ) -> EngineResult {
595        Ok(())
596    }
597
598    /// Default file-extension MySQL appends to tablespace data files. Wired
599    /// only under [`HtonCapabilities::TABLESPACES`]; default `None` produces a
600    /// NULL pointer at the C boundary (no extension).
601    fn tablespace_filename_ext(&self) -> Option<&'static core::ffi::CStr> {
602        None
603    }
604
605    /// Upgrade tablespace-level state from a previous server version.
606    /// Defaults to success.
607    ///
608    /// # Errors
609    /// Returns an [`EngineError`](crate::engine::EngineError) if the upgrade
610    /// step fails.
611    fn upgrade_tablespace(&self, _thd: Option<&sys::THD>) -> EngineResult {
612        Ok(())
613    }
614
615    /// Upgrade the on-disk version of `tablespace`. Defaults to success.
616    ///
617    /// # Errors
618    /// Returns an [`EngineError`](crate::engine::EngineError) if the upgrade
619    /// step fails.
620    fn upgrade_space_version(&self, _tablespace: Option<&sys::DdTablespace>) -> EngineResult {
621        Ok(())
622    }
623
624    /// Classification of the given tablespace. Defaults to `None`, which the
625    /// shim reports back to MySQL as failure to determine the type (so MySQL
626    /// keeps whatever it already knows).
627    fn get_tablespace_type(
628        &self,
629        _tablespace: Option<&sys::DdTablespace>,
630    ) -> Option<TablespaceType> {
631        None
632    }
633
634    /// Classification of the tablespace identified by `tablespace_name`.
635    /// Defaults to `None` (see [`Self::get_tablespace_type`]).
636    fn get_tablespace_type_by_name(&self, _tablespace_name: &str) -> Option<TablespaceType> {
637        None
638    }
639
640    /// Initialise the engine as the data-dictionary backend. The DD-tables /
641    /// DD-tablespaces output lists are not surfaced today (they are produced
642    /// only by the DD backend). Wired only under
643    /// [`HtonCapabilities::DICT_BACKEND`]; defaults to success.
644    ///
645    /// # Errors
646    /// Returns an [`EngineError`](crate::engine::EngineError) on init failure.
647    fn dict_init(&self, _mode: DictInitMode, _version: u32) -> EngineResult {
648        Ok(())
649    }
650
651    /// DD-backend variant of [`Self::dict_init`] used by the DDSE-specific
652    /// startup path. Defaults to success.
653    ///
654    /// # Errors
655    /// Returns an [`EngineError`](crate::engine::EngineError) on init failure.
656    fn ddse_dict_init(&self, _mode: DictInitMode, _version: u32) -> EngineResult {
657        Ok(())
658    }
659
660    /// Register the hard-coded DD table-id range with the engine. `table_id`
661    /// is `dd::Object_id` (a 64-bit integer). Defaults to no-op.
662    fn dict_register_dd_table_id(&self, _table_id: u64) {}
663
664    /// Invalidate the engine's local cache entry for `schema.table`.
665    /// Defaults to no-op.
666    fn dict_cache_reset(&self, _schema_name: &str, _table_name: &str) {}
667
668    /// Invalidate every table and tablespace entry in the engine's local
669    /// dictionary cache. Defaults to no-op.
670    fn dict_cache_reset_tables_and_tablespaces(&self) {}
671
672    /// Perform engine-side recovery work as part of dictionary initialisation.
673    /// Defaults to success.
674    ///
675    /// # Errors
676    /// Returns an [`EngineError`](crate::engine::EngineError) if recovery
677    /// fails.
678    fn dict_recover(&self, _mode: DictRecoveryMode, _version: u32) -> EngineResult {
679        Ok(())
680    }
681
682    /// Read back the server version stored in the dictionary tablespace
683    /// header. Defaults to `None`, which the shim reports back as failure.
684    fn dict_get_server_version(&self) -> Option<u32> {
685        None
686    }
687
688    /// Persist the current server version into the dictionary tablespace
689    /// header. Defaults to success.
690    ///
691    /// # Errors
692    /// Returns an [`EngineError`](crate::engine::EngineError) on write
693    /// failure.
694    fn dict_set_server_version(&self) -> EngineResult {
695        Ok(())
696    }
697
698    /// Create the SDI store for `tablespace`. Wired only under
699    /// [`HtonCapabilities::SDI`]; defaults to unsupported.
700    ///
701    /// # Errors
702    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
703    /// by default.
704    fn sdi_create(&self, _tablespace: Option<&sys::DdTablespace>) -> EngineResult {
705        Err(crate::engine::EngineError::Unsupported)
706    }
707
708    /// Drop the SDI store from `tablespace`. Defaults to unsupported.
709    ///
710    /// # Errors
711    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
712    /// by default.
713    fn sdi_drop(&self, _tablespace: Option<&sys::DdTablespace>) -> EngineResult {
714        Err(crate::engine::EngineError::Unsupported)
715    }
716
717    /// Populate `vector` with the SDI keys present in `tablespace`. The
718    /// `sdi_vector_t` output cannot be filled through the opaque
719    /// pass-through today; defaults to unsupported.
720    ///
721    /// # Errors
722    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
723    /// by default.
724    fn sdi_get_keys(
725        &self,
726        _tablespace: Option<&sys::DdTablespace>,
727        _vector: Option<&sys::SdiVector>,
728    ) -> EngineResult {
729        Err(crate::engine::EngineError::Unsupported)
730    }
731
732    /// Look up the SDI payload identified by `key` and write it into `buf`.
733    /// On success the engine must set `*len_out` to the bytes actually
734    /// written. The shim treats `buf` as a `&mut [u8]` whose length is the
735    /// caller-provided capacity. Defaults to unsupported.
736    ///
737    /// MySQL distinguishes the two error paths by inspecting `*len_out`:
738    /// - **Buffer too small** — return an error and set `*len_out` to the
739    ///   required payload size so the caller can retry with a larger buffer.
740    /// - **Genuine error** (key not found, I/O failure, ...) — return an
741    ///   error and set `*len_out = u64::MAX`. Without this sentinel MySQL
742    ///   re-enters the call expecting a retry and may loop indefinitely.
743    ///
744    /// # Errors
745    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
746    /// by default. Engines that store SDI follow the two-path convention
747    /// above.
748    fn sdi_get(
749        &self,
750        _tablespace: Option<&sys::DdTablespace>,
751        _key: Option<&sys::SdiKey>,
752        _buf: &mut [u8],
753        _len_out: &mut u64,
754    ) -> EngineResult {
755        Err(crate::engine::EngineError::Unsupported)
756    }
757
758    /// Store `payload` as the SDI value for `key` against `table` /
759    /// `tablespace`. Defaults to unsupported.
760    ///
761    /// # Errors
762    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
763    /// by default.
764    fn sdi_set(
765        &self,
766        _tablespace: Option<&sys::DdTablespace>,
767        _table: Option<&sys::DdTable>,
768        _key: Option<&sys::SdiKey>,
769        _payload: &[u8],
770    ) -> EngineResult {
771        Err(crate::engine::EngineError::Unsupported)
772    }
773
774    /// Delete the SDI value identified by `key`. Defaults to unsupported.
775    ///
776    /// # Errors
777    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
778    /// by default.
779    fn sdi_delete(
780        &self,
781        _tablespace: Option<&sys::DdTablespace>,
782        _table: Option<&sys::DdTable>,
783        _key: Option<&sys::SdiKey>,
784    ) -> EngineResult {
785        Err(crate::engine::EngineError::Unsupported)
786    }
787
788    /// Acquire the engine-log mutex so a backup tool can snapshot a
789    /// consistent log state. Wired only under
790    /// [`HtonCapabilities::ENGINE_LOG`]; defaults to success.
791    ///
792    /// # Errors
793    /// Returns an [`EngineError`](crate::engine::EngineError) when the lock
794    /// cannot be taken.
795    fn lock_hton_log(&self) -> EngineResult {
796        Ok(())
797    }
798
799    /// Release the mutex taken by [`Self::lock_hton_log`]. Defaults to
800    /// success.
801    ///
802    /// # Errors
803    /// Returns an [`EngineError`](crate::engine::EngineError) on release
804    /// failure.
805    fn unlock_hton_log(&self) -> EngineResult {
806        Ok(())
807    }
808
809    /// Append the engine's redo / transaction log status into the
810    /// `performance_schema.log_status` collector. The `Json_dom` parameter is
811    /// opaque today; an engine with log info will need a reverse callback to
812    /// populate the JSON tree. Defaults to no-op success.
813    ///
814    /// # Errors
815    /// Returns an [`EngineError`](crate::engine::EngineError) on collection
816    /// failure.
817    fn collect_hton_log_info(&self, _json: Option<&sys::JsonDom>) -> EngineResult {
818        Ok(())
819    }
820
821    /// Whether the engine considers the two foreign-key column type
822    /// descriptors compatible (used when MySQL validates an `ADD FOREIGN KEY`
823    /// referencing this engine's tables). `Ha_fk_column_type` is opaque
824    /// today; the trait default returns `true` (accept any), which matches the
825    /// permissive base-engine behaviour. Override to enforce stricter typing.
826    fn check_fk_column_compat(
827        &self,
828        _child: Option<&sys::HaFkColumnType>,
829        _parent: Option<&sys::HaFkColumnType>,
830        _check_charsets: bool,
831    ) -> bool {
832        true
833    }
834
835    /// Plugin-observer hook fired before a transaction commits. The shim
836    /// discards the observer's `void*` argument because it belongs to the
837    /// observer plugin that registered the hook, not to the storage engine;
838    /// engines that need observer data must coordinate with the observer
839    /// through a separate channel. Defaults to no-op.
840    fn se_before_commit(&self) {}
841
842    /// Plugin-observer hook fired after a transaction commits. See
843    /// [`Self::se_before_commit`] for the observer-arg discussion. Defaults
844    /// to no-op.
845    fn se_after_commit(&self) {}
846
847    /// Plugin-observer hook fired before a transaction rolls back. See
848    /// [`Self::se_before_commit`] for the observer-arg discussion. Defaults
849    /// to no-op.
850    fn se_before_rollback(&self) {}
851
852    /// Prepare the secondary engine for executing a statement. `lex` is
853    /// opaque today. Wired only under
854    /// [`HtonCapabilities::SECONDARY_ENGINE`]; defaults to success.
855    ///
856    /// # Errors
857    /// Returns an [`EngineError`](crate::engine::EngineError) if preparation
858    /// fails; MySQL falls back to the primary engine.
859    fn prepare_secondary_engine(
860        &self,
861        _thd: Option<&sys::THD>,
862        _lex: Option<&sys::Lex>,
863    ) -> EngineResult {
864        Ok(())
865    }
866
867    /// Optimize a statement for execution on the secondary engine. `lex` is
868    /// opaque today. Defaults to success.
869    ///
870    /// # Errors
871    /// Returns an [`EngineError`](crate::engine::EngineError) if the
872    /// optimization fails; MySQL reprepares for the primary engine.
873    fn optimize_secondary_engine(
874        &self,
875        _thd: Option<&sys::THD>,
876        _lex: Option<&sys::Lex>,
877    ) -> EngineResult {
878        Ok(())
879    }
880
881    /// Compare the cost of `join` against the best plan seen so far. The
882    /// trait returns `(use_best_so_far, cheaper, secondary_engine_cost)`;
883    /// `None` defaults the triple to `(false, false, optimizer_cost)`, which
884    /// the shim writes back to the C `bool*` / `double*` outputs.
885    ///
886    /// # Errors
887    /// Returns an [`EngineError`](crate::engine::EngineError) on error; the
888    /// optimizer drops the candidate plan.
889    fn compare_secondary_engine_cost(
890        &self,
891        _thd: Option<&sys::THD>,
892        _join: Option<&sys::Join>,
893        _optimizer_cost: f64,
894    ) -> EngineResult<Option<(bool, bool, f64)>> {
895        Ok(None)
896    }
897
898    /// Evaluate (and potentially modify) the cost estimates on `access_path`
899    /// from the hypergraph optimizer. `access_path` is opaque to Rust today,
900    /// so the default cannot modify costs; returning `Ok(())` accepts the
901    /// path unchanged.
902    ///
903    /// # Errors
904    /// Returns an [`EngineError`](crate::engine::EngineError) to reject the
905    /// access path entirely.
906    fn secondary_engine_modify_access_path_cost(
907        &self,
908        _thd: Option<&sys::THD>,
909        _hypergraph: Option<&sys::JoinHypergraph>,
910        _access_path: Option<&sys::AccessPath>,
911    ) -> EngineResult {
912        Ok(())
913    }
914
915    /// Whether `EXPLAIN` references tables not loaded into the secondary
916    /// engine. Defaults to `false` (all referenced tables are loaded).
917    fn external_engine_explain_check(&self, _thd: Option<&sys::THD>) -> bool {
918        false
919    }
920
921    // `get_secondary_engine_offload_or_exec_fail_reason` and
922    // `find_secondary_engine_offload_fail_reason` are wired at the FFI layer
923    // but intentionally not surfaced as trait methods today: returning
924    // engine-owned bytes by value would drop the buffer before MySQL wrapped
925    // it in `std::string_view`, exposing freed heap memory. The FFI returns an
926    // empty view until a future setter reverse-callback can hand
927    // statement-scoped bytes to MySQL safely. `set_*` below is unaffected.
928
929    /// Persist `reason` as the offload failure reason for the query
930    /// represented by `thd`. Defaults to success.
931    ///
932    /// # Errors
933    /// Returns an [`EngineError`](crate::engine::EngineError) if the engine
934    /// could not record the reason.
935    fn set_secondary_engine_offload_fail_reason(
936        &self,
937        _thd: Option<&sys::THD>,
938        _reason: &str,
939    ) -> EngineResult {
940        Ok(())
941    }
942
943    /// Hook the hypergraph optimizer calls after
944    /// [`Self::secondary_engine_modify_access_path_cost`] to decide whether
945    /// to keep optimizing, restart with a different subgraph budget, etc.
946    /// Defaults to [`SecondaryEngineOptimizerRequest::keep_going`]. The
947    /// `JoinHypergraph` / `AccessPath` / `trace` parameters are opaque.
948    fn secondary_engine_check_optimizer_request(
949        &self,
950        _thd: Option<&sys::THD>,
951        _hypergraph: Option<&sys::JoinHypergraph>,
952        _access_path: Option<&sys::AccessPath>,
953        _current_subgraph_pairs: i32,
954        _current_subgraph_pairs_limit: i32,
955        _is_root_access_path: bool,
956    ) -> SecondaryEngineOptimizerRequest {
957        SecondaryEngineOptimizerRequest::keep_going()
958    }
959
960    /// Pre-prepare hook called early in optimization to decide whether the
961    /// secondary engine's full prepare path should run. Defaults to `false`
962    /// (skip the prepare path), matching the upstream "no further prepare"
963    /// signal.
964    fn secondary_engine_pre_prepare_hook(&self, _thd: Option<&sys::THD>) -> bool {
965        false
966    }
967
968    /// Whether the engine's data dictionary is currently read-only. Always
969    /// wired on a registered handlerton; defaults to `false`.
970    fn is_dict_readonly(&self) -> bool {
971        false
972    }
973
974    /// Clean up engine-owned temporary tables. The `List<LEX_STRING>*` MySQL
975    /// passes in is opaque today and dropped. Always wired; defaults to
976    /// success.
977    ///
978    /// # Errors
979    /// Returns an [`EngineError`](crate::engine::EngineError) on cleanup
980    /// failure.
981    fn rm_tmp_tables(&self, _thd: Option<&sys::THD>) -> EngineResult {
982        Ok(())
983    }
984
985    /// Provide engine-specific optimizer cost constants for
986    /// `storage_category` (raw MySQL `Cost_constants_storage_category` value).
987    /// Return [`None`] to keep MySQL's defaults; return [`Some`] to override.
988    fn get_cost_constants(&self, _storage_category: u32) -> Option<CostConstants> {
989        None
990    }
991
992    /// Notification that MySQL is swapping the connection's native engine
993    /// transaction. The `void *` / `void **` arguments are opaque to Rust
994    /// (observer-style) and are dropped at the FFI boundary. Always wired;
995    /// defaults to no-op.
996    fn replace_native_transaction_in_thd(&self, _thd: Option<&sys::THD>) {}
997
998    /// Optimizer pushdown at the handlerton layer. The `AccessPath` /
999    /// `JOIN` pointers stay opaque (dropped at the FFI boundary today —
1000    /// the engine sees only the `THD`). The handlerton pointer is wired
1001    /// unconditionally; MySQL only invokes it after the handler-level
1002    /// [`crate::engine::StorageEngine::hton_supporting_engine_pushdown`]
1003    /// reports the engine wants pushdown. The default returns
1004    /// [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1005    /// so an engine that opts into pushdown but forgets to implement
1006    /// this method gets a loud error instead of silently broken
1007    /// query results.
1008    ///
1009    /// # Errors
1010    /// Returns an [`EngineError`](crate::engine::EngineError) if pushdown
1011    /// fails or is unsupported.
1012    fn push_to_engine(&self, _thd: Option<&sys::THD>) -> EngineResult {
1013        Err(crate::engine::EngineError::Unsupported)
1014    }
1015
1016    /// Rotate the encryption master key. Wired only under
1017    /// [`HtonCapabilities::ENCRYPTION`]; defaults to success.
1018    ///
1019    /// # Errors
1020    /// Returns an [`EngineError`](crate::engine::EngineError) on rotation
1021    /// failure.
1022    fn rotate_encryption_master_key(&self) -> EngineResult {
1023        Ok(())
1024    }
1025
1026    /// Enable or disable engine redo logging. Wired only under
1027    /// [`HtonCapabilities::ENGINE_LOG`]; defaults to success.
1028    ///
1029    /// # Errors
1030    /// Returns an [`EngineError`](crate::engine::EngineError) on toggle
1031    /// failure.
1032    fn redo_log_set_state(&self, _thd: Option<&sys::THD>, _enable: bool) -> EngineResult {
1033        Ok(())
1034    }
1035
1036    /// Retrieve engine-published statistics for the (`db_name`, `table_name`)
1037    /// pair. Return `Ok(Some(stats))` to populate MySQL's `ha_statistics`,
1038    /// `Ok(None)` when the engine knows nothing about the table, or `Err` on
1039    /// a real retrieval failure. The shim copies each field of `stats` into
1040    /// the matching `ha_statistics` slot before returning to MySQL.
1041    ///
1042    /// # Errors
1043    /// Returns an [`EngineError`](crate::engine::EngineError) on retrieval
1044    /// failure.
1045    fn get_table_statistics(
1046        &self,
1047        _db_name: &str,
1048        _table_name: &str,
1049        _se_private_id: u64,
1050        _flags: u32,
1051    ) -> EngineResult<Option<TableStatistics>> {
1052        Ok(None)
1053    }
1054
1055    /// Retrieve the cardinality of a single index column. Same opaque-output
1056    /// situation as [`Self::get_table_statistics`]; the FFI pointer stays
1057    /// NULL on the handlerton today.
1058    ///
1059    /// # Errors
1060    /// Returns an [`EngineError`](crate::engine::EngineError) on retrieval
1061    /// failure.
1062    fn get_index_column_cardinality(
1063        &self,
1064        _db_name: &str,
1065        _table_name: &str,
1066        _index_name: &str,
1067        _index_ordinal_position: u32,
1068        _column_ordinal_position: u32,
1069        _se_private_id: u64,
1070    ) -> EngineResult<Option<u64>> {
1071        Ok(None)
1072    }
1073
1074    /// Retrieve engine-published statistics for the (`tablespace_name`,
1075    /// `file_name`) pair. Return `Ok(Some(stats))` to populate MySQL's
1076    /// `ha_tablespace_statistics`, `Ok(None)` when the engine knows nothing
1077    /// about the tablespace, or `Err` on a real retrieval failure. Only the
1078    /// numeric fields cross the FFI boundary today; the five string fields
1079    /// keep their default-empty values.
1080    ///
1081    /// # Errors
1082    /// Returns an [`EngineError`](crate::engine::EngineError) on retrieval
1083    /// failure.
1084    fn get_tablespace_statistics(
1085        &self,
1086        _tablespace_name: &str,
1087        _file_name: &str,
1088    ) -> EngineResult<Option<TablespaceStatistics>> {
1089        Ok(None)
1090    }
1091
1092    /// Notification fired after a DDL completes. Always wired on a registered
1093    /// handlerton; defaults to no-op.
1094    fn post_ddl(&self, _thd: Option<&sys::THD>) {}
1095
1096    /// Notification fired after server recovery completes. Always wired on a
1097    /// registered handlerton; defaults to no-op.
1098    fn post_recover(&self) {}
1099
1100    /// Report the engine's clone capability bits. Wired only under
1101    /// [`HtonCapabilities::CLONE`]; defaults to 0 (no clone features
1102    /// supported). The trait surfaces the bits as a `u64` mirroring
1103    /// `std::bitset<HA_CLONE_TYPE_MAX>` width on the C side.
1104    fn clone_capability(&self) -> u64 {
1105        0
1106    }
1107
1108    /// Begin a clone copy on the source engine. The `Ha_clone_cbk` data-
1109    /// transfer object and the in/out locator parameters are opaque to Rust
1110    /// today; the trait sees only the high-level request. Defaults to
1111    /// unsupported.
1112    ///
1113    /// # Errors
1114    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1115    /// by default.
1116    fn clone_begin(
1117        &self,
1118        _thd: Option<&sys::THD>,
1119        _clone_type: HaCloneType,
1120        _mode: HaCloneMode,
1121    ) -> EngineResult {
1122        Err(crate::engine::EngineError::Unsupported)
1123    }
1124
1125    /// Copy a chunk of clone data via the engine-owned callback. Defaults
1126    /// to unsupported.
1127    ///
1128    /// # Errors
1129    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1130    /// by default.
1131    fn clone_copy(
1132        &self,
1133        _thd: Option<&sys::THD>,
1134        _task_id: u32,
1135        _cbk: Option<&sys::HaCloneCbk>,
1136    ) -> EngineResult {
1137        Err(crate::engine::EngineError::Unsupported)
1138    }
1139
1140    /// Acknowledge clone data already transferred. Defaults to unsupported.
1141    ///
1142    /// # Errors
1143    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1144    /// by default.
1145    fn clone_ack(
1146        &self,
1147        _thd: Option<&sys::THD>,
1148        _task_id: u32,
1149        _in_err: i32,
1150        _cbk: Option<&sys::HaCloneCbk>,
1151    ) -> EngineResult {
1152        Err(crate::engine::EngineError::Unsupported)
1153    }
1154
1155    /// End a clone copy. Defaults to unsupported.
1156    ///
1157    /// # Errors
1158    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1159    /// by default.
1160    fn clone_end(&self, _thd: Option<&sys::THD>, _task_id: u32, _in_err: i32) -> EngineResult {
1161        Err(crate::engine::EngineError::Unsupported)
1162    }
1163
1164    /// Begin applying clone data on the destination engine. `data_dir` is
1165    /// the target data directory. Defaults to unsupported.
1166    ///
1167    /// # Errors
1168    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1169    /// by default.
1170    fn clone_apply_begin(
1171        &self,
1172        _thd: Option<&sys::THD>,
1173        _mode: HaCloneMode,
1174        _data_dir: &str,
1175    ) -> EngineResult {
1176        Err(crate::engine::EngineError::Unsupported)
1177    }
1178
1179    /// Apply a chunk of clone data via the engine-owned callback. Defaults
1180    /// to unsupported.
1181    ///
1182    /// # Errors
1183    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1184    /// by default.
1185    fn clone_apply(
1186        &self,
1187        _thd: Option<&sys::THD>,
1188        _task_id: u32,
1189        _in_err: i32,
1190        _cbk: Option<&sys::HaCloneCbk>,
1191    ) -> EngineResult {
1192        Err(crate::engine::EngineError::Unsupported)
1193    }
1194
1195    /// End a clone apply. Defaults to unsupported.
1196    ///
1197    /// # Errors
1198    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1199    /// by default.
1200    fn clone_apply_end(
1201        &self,
1202        _thd: Option<&sys::THD>,
1203        _task_id: u32,
1204        _in_err: i32,
1205    ) -> EngineResult {
1206        Err(crate::engine::EngineError::Unsupported)
1207    }
1208
1209    /// Start page tracking. Returns the engine's start ID (LSN-like sequence
1210    /// number). Wired only under [`HtonCapabilities::PAGE_TRACKING`];
1211    /// defaults to unsupported.
1212    ///
1213    /// # Errors
1214    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1215    /// by default.
1216    fn page_track_start(&self) -> EngineResult<u64> {
1217        Err(crate::engine::EngineError::Unsupported)
1218    }
1219
1220    /// Stop page tracking. Returns the engine's stop ID. Defaults to
1221    /// unsupported.
1222    ///
1223    /// # Errors
1224    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1225    /// by default.
1226    fn page_track_stop(&self) -> EngineResult<u64> {
1227        Err(crate::engine::EngineError::Unsupported)
1228    }
1229
1230    /// Purge page-tracking data up to `purge_id`. Returns the ID actually
1231    /// purged through. Defaults to unsupported.
1232    ///
1233    /// # Errors
1234    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1235    /// by default.
1236    fn page_track_purge(&self, _purge_id: u64) -> EngineResult<u64> {
1237        Err(crate::engine::EngineError::Unsupported)
1238    }
1239
1240    /// Fetch tracked page IDs between `start_id` and `stop_id`. The MySQL
1241    /// `Page_Track_Callback` and its context are opaque to the Rust trait
1242    /// today (engines that fetch will need a reverse-callback surface).
1243    /// Defaults to unsupported.
1244    ///
1245    /// # Errors
1246    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1247    /// by default.
1248    fn page_track_get_page_ids(
1249        &self,
1250        _start_id: u64,
1251        _stop_id: u64,
1252        _buffer: &mut [u8],
1253    ) -> EngineResult {
1254        Err(crate::engine::EngineError::Unsupported)
1255    }
1256
1257    /// Approximate the number of tracked pages between `start_id` and
1258    /// `stop_id`. Defaults to unsupported.
1259    ///
1260    /// # Errors
1261    /// Returns [`EngineError::Unsupported`](crate::engine::EngineError::Unsupported)
1262    /// by default.
1263    fn page_track_get_num_page_ids(&self, _start_id: u64, _stop_id: u64) -> EngineResult<u64> {
1264        Err(crate::engine::EngineError::Unsupported)
1265    }
1266
1267    /// Fetch the page-tracking status. The C signature returns a
1268    /// `std::vector<std::pair<uint64_t, bool>>` by value; that container
1269    /// cannot be synthesised through the opaque pass-through today, so the
1270    /// trait method is bound for completeness and the shim writes an empty
1271    /// status. Defaults to no-op.
1272    fn page_track_get_status(&self) {}
1273}
1274
1275/// Inert default session returned by [`Handlerton::begin_transaction`] (see
1276/// there). Accepts and discards transaction boundaries without doing work.
1277#[derive(Debug)]
1278struct NoopTxnSession;
1279
1280impl TxnSession for NoopTxnSession {
1281    fn commit(&mut self, _all: bool) -> EngineResult {
1282        Ok(())
1283    }
1284
1285    fn rollback(&mut self, _all: bool) -> EngineResult {
1286        Ok(())
1287    }
1288}