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