Skip to main content

mysql_handler/
engine.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//! Safe storage-engine interface for downstream implementations
24
25use core::ffi::c_void;
26use std::ffi::CStr;
27
28use crate::sys;
29
30mod bulk_access;
31mod cost_estimate;
32mod error;
33mod parallel_scan_init;
34mod range_key;
35mod reset_cached_state;
36mod rkey_function;
37mod sampling_method;
38
39pub use bulk_access::BulkAccess;
40pub use cost_estimate::CostEstimate;
41pub use error::{EngineError, EngineResult};
42pub use parallel_scan_init::ParallelScanInit;
43pub use range_key::RangeKey;
44pub use reset_cached_state::ResetCachedState;
45pub use rkey_function::RKeyFunction;
46pub use sampling_method::SamplingMethod;
47
48/// The safe interface every storage engine implements.
49///
50/// MySQL constructs one instance per opened table per session worker thread,
51/// so the trait requires `Send`. The `EngineContext` that owns a
52/// `Box<dyn StorageEngine>` crosses the C++ FFI boundary as a raw pointer;
53/// the `Send` bound is the only compile-time guarantee that this stays sound.
54#[allow(clippy::missing_errors_doc)]
55pub trait StorageEngine: Send {
56    /// Engine display name shown by `SHOW ENGINES` and used as the `ENGINE=`
57    /// value in `CREATE TABLE`. Must be a null-terminated `'static` C string
58    /// (e.g. `c"RUSTY"`) because the pointer is handed straight to MySQL.
59    fn table_type(&self) -> &'static CStr;
60
61    /// `HA_*` capability bitfield advertised to the optimizer
62    fn table_flags(&self) -> u64;
63
64    /// Per-index capability bitfield. `idx` is the index, `part` the key part;
65    /// when `all_parts` is set MySQL wants the combined flags up to and
66    /// including `part`.
67    fn index_flags(&self, idx: u32, part: u32, all_parts: bool) -> u32;
68
69    /// Create the on-disk representation for a new table named `name`.
70    /// `table_def` is the data-dictionary descriptor of the table being
71    /// created; it carries column and key metadata the engine should snapshot
72    /// out, since the borrow is only valid for the duration of the call.
73    ///
74    /// # Errors
75    /// Implementation-defined.
76    fn create(&mut self, name: &str, table_def: Option<&sys::DdTable>) -> EngineResult;
77
78    /// Open an existing table named `name` in the given `mode`. `table_def`
79    /// is the data-dictionary descriptor for the table; it carries the same
80    /// column / key metadata as on [`create`](Self::create) and is similarly
81    /// borrowed only for the call.
82    ///
83    /// # Errors
84    /// Implementation-defined.
85    fn open(&mut self, name: &str, mode: i32, table_def: Option<&sys::DdTable>) -> EngineResult;
86
87    /// Release any resources acquired by [`open`](Self::open).
88    /// Errors are implementation-defined.
89    fn close(&mut self) -> EngineResult;
90
91    /// Begin a full table scan. `scan == false` indicates the optimizer will
92    /// only use positioned access (`rnd_pos`). Errors are implementation-defined.
93    fn rnd_init(&mut self, scan: bool) -> EngineResult;
94
95    /// End the full table scan started by [`rnd_init`](Self::rnd_init),
96    /// releasing any cursor state. MySQL may call [`rnd_init`](Self::rnd_init)
97    /// again without an intervening `rnd_end`, so implementations must tolerate
98    /// a re-init.
99    ///
100    /// # Errors
101    /// The default returns `Ok(())`, matching the handler base.
102    fn rnd_end(&mut self) -> EngineResult {
103        Ok(())
104    }
105
106    /// Fetch the next row into `buf`.
107    ///
108    /// # Errors
109    /// Returns [`EngineError::EndOfFile`] once the scan is exhausted; other
110    /// variants are implementation-defined.
111    fn rnd_next(&mut self, buf: &mut [u8]) -> EngineResult;
112
113    /// Fetch a row by the position previously recorded with
114    /// [`position`](Self::position).
115    ///
116    /// # Errors
117    /// Returns [`EngineError::WrongCommand`] when the engine has no positioned
118    /// access path; other variants are implementation-defined.
119    fn rnd_pos(&mut self, buf: &mut [u8], pos: &[u8]) -> EngineResult;
120
121    /// Record the position of the row just read so a later
122    /// [`rnd_pos`](Self::rnd_pos) can replay it. `ref_out` is MySQL's
123    /// `handler::ref` buffer (`ref_length` bytes); write the engine's rowid or
124    /// primary-key encoding for `record` into it, and `rnd_pos` receives the
125    /// same bytes back. `record` holds the row in MySQL's internal record
126    /// format; neither borrow may be retained past the call.
127    fn position(&mut self, record: &[u8], ref_out: &mut [u8]);
128
129    /// Read the row whose primary key matches the one encoded in `record` (in
130    /// MySQL's internal record format), overwriting `record` with the full row.
131    /// Only meaningful for engines that advertise
132    /// `HA_PRIMARY_KEY_REQUIRED_FOR_POSITION`.
133    ///
134    /// The handler base implements this by orchestrating
135    /// [`rnd_init`](Self::rnd_init) / [`position`](Self::position) /
136    /// [`rnd_pos`](Self::rnd_pos) / [`rnd_end`](Self::rnd_end) across several
137    /// calls through its internal `ref` buffer; the binding hands the whole
138    /// operation to the engine in one call instead of replicating that
139    /// orchestration. The borrow may not be retained past the call.
140    ///
141    /// # Errors
142    /// The default returns [`EngineError::WrongCommand`].
143    fn rnd_pos_by_record(&mut self, _record: &mut [u8]) -> EngineResult {
144        Err(EngineError::WrongCommand)
145    }
146
147    /// Refresh statistics (rows, deleted rows, data length, ...) for the
148    /// optimizer. Errors are implementation-defined.
149    fn info(&mut self, flag: u32) -> EngineResult;
150
151    /// Drop a table. `table_def` is the data-dictionary descriptor of the
152    /// table being deleted; it may be `None` for temporary tables created
153    /// by the optimizer.
154    ///
155    /// May be invoked twice per `DROP TABLE` of a temporary table: once
156    /// directly, and once as part of the `handler::drop_table` chain (close +
157    /// delete_table with `table_def = None`) that fires before
158    /// [`drop_table`](Self::drop_table). Implementations that count calls
159    /// must tolerate the repeat.
160    ///
161    /// # Errors
162    /// The default returns [`EngineError::WrongCommand`]. This deliberately
163    /// diverges from MySQL's `handler::delete_table` base, which deletes the
164    /// on-disk artefact via `my_delete`; the binding leaves any artefact
165    /// cleanup to the engine implementation.
166    fn delete_table(&mut self, _name: &str, _table_def: Option<&sys::DdTable>) -> EngineResult {
167        Err(EngineError::WrongCommand)
168    }
169
170    /// Rename a table from `from` to `to`. `from_table_def` and `to_table_def`
171    /// are the data-dictionary descriptors before and after the rename.
172    ///
173    /// # Errors
174    /// The default returns [`EngineError::WrongCommand`].
175    fn rename_table(
176        &mut self,
177        _from: &str,
178        _to: &str,
179        _from_table_def: Option<&sys::DdTable>,
180        _to_table_def: Option<&sys::DdTable>,
181    ) -> EngineResult {
182        Err(EngineError::WrongCommand)
183    }
184
185    /// Notification that MySQL is dropping the table, invoked from
186    /// `ha_drop_table` on temporary-table cleanup paths. The binding mirrors
187    /// upstream's `handler::drop_table` chain (`close()` then
188    /// [`delete_table`](Self::delete_table) with `table_def = None`) on the
189    /// C++ side, so this callback fires after the chain completes and serves
190    /// purely as a post-cleanup hook. Default is a no-op.
191    ///
192    /// Any error returned by the in-chain [`delete_table`](Self::delete_table)
193    /// is swallowed by MySQL's void `handler::drop_table`; engines that need
194    /// to surface a failure during cleanup must do so out-of-band.
195    fn drop_table(&mut self, _name: &str) {}
196
197    /// Reset the table to an empty state without dropping it.
198    ///
199    /// # Errors
200    /// The default returns [`EngineError::WrongCommand`], matching the
201    /// MySQL handler base implementation.
202    fn truncate(&mut self, _table_def: Option<&sys::DdTable>) -> EngineResult {
203        Err(EngineError::WrongCommand)
204    }
205
206    /// Notification that MySQL has reassigned the underlying `TABLE` and
207    /// `TABLE_SHARE`. The base C++ handler updates its own pointers; this
208    /// callback lets the engine react if it caches per-table state. Default
209    /// is a no-op.
210    fn change_table_ptr(&mut self, _table: Option<&sys::TABLE>, _share: Option<&sys::TABLE_SHARE>) {
211    }
212
213    /// Populate engine-private metadata in `dd_table`. `reset` distinguishes
214    /// the case where the data-dictionary entry has been reset and any cached
215    /// state must be re-emitted. Returns `true` when private data was written.
216    /// The default returns `false`.
217    fn se_private_data(
218        &mut self,
219        _dd_table: Option<&sys::DdTable>,
220        _reset: ResetCachedState,
221    ) -> bool {
222        false
223    }
224
225    /// Inject implicit columns and indexes the engine requires for `table_obj`
226    /// to be created.
227    ///
228    /// # Errors
229    /// The default never errors; overrides choose which [`EngineError`]
230    /// variants they emit.
231    fn extra_columns_and_keys(
232        &mut self,
233        _create_info: Option<&sys::HA_CREATE_INFO>,
234        _create_list: Option<&sys::ListCreateField>,
235        _key_info: Option<&sys::KEY>,
236        _key_count: u32,
237        _table_obj: Option<&sys::DdTable>,
238    ) -> EngineResult {
239        Ok(())
240    }
241
242    /// Adjust the data-dictionary entry of an old-format table during a server
243    /// upgrade. Returning `Err` aborts the upgrade (mapped to C++ `bool true`).
244    ///
245    /// # Errors
246    /// The default returns `Ok(())`; overrides surface an [`EngineError`] to
247    /// abort the upgrade.
248    fn upgrade_table(
249        &mut self,
250        _thd: Option<&sys::THD>,
251        _dbname: &str,
252        _table_name: &str,
253        _dd_table: Option<&sys::DdTable>,
254    ) -> EngineResult {
255        Ok(())
256    }
257
258    /// Insert the row held in `buf`, encoded in MySQL's internal record format
259    /// (the contents of `record[0]`). The engine must copy out whatever it
260    /// needs during the call; the borrow may not be retained afterwards.
261    ///
262    /// # Errors
263    /// The default returns [`EngineError::WrongCommand`], matching the MySQL
264    /// handler base which rejects writes on engines that do not support them.
265    fn write_row(&mut self, _buf: &[u8]) -> EngineResult {
266        Err(EngineError::WrongCommand)
267    }
268
269    /// Replace the row whose existing image is `old` with the new image `new`,
270    /// both in MySQL's internal record format. Neither borrow may be retained
271    /// past the call.
272    ///
273    /// # Errors
274    /// The default returns [`EngineError::WrongCommand`].
275    fn update_row(&mut self, _old: &[u8], _new: &[u8]) -> EngineResult {
276        Err(EngineError::WrongCommand)
277    }
278
279    /// Delete the row whose current image is `buf`, in MySQL's internal record
280    /// format. The borrow may not be retained past the call.
281    ///
282    /// # Errors
283    /// The default returns [`EngineError::WrongCommand`].
284    fn delete_row(&mut self, _buf: &[u8]) -> EngineResult {
285        Err(EngineError::WrongCommand)
286    }
287
288    /// Delete every row in the table in a single operation, the fast path MySQL
289    /// takes for an unqualified `DELETE` when the engine advertises support.
290    ///
291    /// # Errors
292    /// The default returns [`EngineError::WrongCommand`].
293    fn delete_all_rows(&mut self) -> EngineResult {
294        Err(EngineError::WrongCommand)
295    }
296
297    /// Hint that a multi-row INSERT is about to begin; `rows` is MySQL's
298    /// estimate of how many rows will be written (`0` when unknown). Engines may
299    /// pre-size buffers here. The default is a no-op, matching the handler base.
300    fn start_bulk_insert(&mut self, _rows: u64) {}
301
302    /// Flush any rows buffered since
303    /// [`start_bulk_insert`](Self::start_bulk_insert).
304    ///
305    /// # Errors
306    /// The default returns `Ok(())`, matching the handler base which always
307    /// succeeds.
308    fn end_bulk_insert(&mut self) -> EngineResult {
309        Ok(())
310    }
311
312    /// Decide whether to batch the rows of a multi-row UPDATE.
313    /// [`BulkAccess::Batched`] routes subsequent rows through
314    /// [`bulk_update_row`](Self::bulk_update_row) and
315    /// [`exec_bulk_update`](Self::exec_bulk_update); [`BulkAccess::PerRow`] keeps
316    /// MySQL on the per-row [`update_row`](Self::update_row) path. The default is
317    /// [`BulkAccess::PerRow`], matching the handler base.
318    fn start_bulk_update(&mut self) -> BulkAccess {
319        BulkAccess::PerRow
320    }
321
322    /// Apply all updates buffered since
323    /// [`start_bulk_update`](Self::start_bulk_update), returning the number of
324    /// duplicate-key collisions encountered. MySQL may continue batching after
325    /// this call until [`end_bulk_update`](Self::end_bulk_update).
326    ///
327    /// # Errors
328    /// The default returns [`EngineError::WrongCommand`], matching the handler
329    /// base which rejects the bulk path unless the engine opts in.
330    fn exec_bulk_update(&mut self) -> EngineResult<u32> {
331        Err(EngineError::WrongCommand)
332    }
333
334    /// Release any state held for the bulk-update batch, called once the
335    /// statement's updates are concluded. The default is a no-op.
336    fn end_bulk_update(&mut self) {}
337
338    /// Buffer one row update for a later
339    /// [`exec_bulk_update`](Self::exec_bulk_update), replacing the image `old`
340    /// with `new` (both in MySQL's internal record format). Returns the running
341    /// count of duplicate-key collisions. Neither borrow may be retained past
342    /// the call.
343    ///
344    /// # Errors
345    /// The default returns [`EngineError::WrongCommand`], matching the handler
346    /// base.
347    fn bulk_update_row(&mut self, _old: &[u8], _new: &[u8]) -> EngineResult<u32> {
348        Err(EngineError::WrongCommand)
349    }
350
351    /// Decide whether to batch the rows of a multi-row DELETE.
352    /// [`BulkAccess::Batched`] routes the deletes through the bulk path closed
353    /// by [`end_bulk_delete`](Self::end_bulk_delete); [`BulkAccess::PerRow`]
354    /// keeps MySQL on [`delete_row`](Self::delete_row). The default is
355    /// [`BulkAccess::PerRow`], matching the handler base.
356    fn start_bulk_delete(&mut self) -> BulkAccess {
357        BulkAccess::PerRow
358    }
359
360    /// Execute all buffered deletes and close the bulk-delete batch.
361    ///
362    /// # Errors
363    /// The default returns [`EngineError::WrongCommand`], matching the handler
364    /// base.
365    fn end_bulk_delete(&mut self) -> EngineResult {
366        Err(EngineError::WrongCommand)
367    }
368
369    /// Begin an index scan on index `idx`. `sorted` requests that subsequent
370    /// reads return rows in index order. The base handler merely records the
371    /// active index and returns success.
372    ///
373    /// # Errors
374    /// The default returns `Ok(())`, matching the MySQL handler base.
375    fn index_init(&mut self, _idx: u32, _sorted: bool) -> EngineResult {
376        Ok(())
377    }
378
379    /// End the index scan started by [`index_init`](Self::index_init).
380    ///
381    /// # Errors
382    /// The default returns `Ok(())`, matching the MySQL handler base.
383    fn index_end(&mut self) -> EngineResult {
384        Ok(())
385    }
386
387    /// Position the index cursor at `key` according to `find_flag` and read the
388    /// matching row into `buf`. `key` is the leading key bytes whose length the
389    /// shim resolved from the original `key_part_map`; it is empty when MySQL
390    /// passed a null key (begin at the first key of the index). Neither borrow
391    /// may be retained past the call.
392    ///
393    /// # Errors
394    /// The default returns [`EngineError::WrongCommand`]; engines return
395    /// [`EngineError::EndOfFile`] when no row matches.
396    fn index_read_map(
397        &mut self,
398        _buf: &mut [u8],
399        _key: &[u8],
400        _find_flag: RKeyFunction,
401    ) -> EngineResult {
402        Err(EngineError::WrongCommand)
403    }
404
405    /// Read the next row in the index scan into `buf`.
406    ///
407    /// # Errors
408    /// The default returns [`EngineError::WrongCommand`]; engines return
409    /// [`EngineError::EndOfFile`] once the scan is exhausted.
410    fn index_next(&mut self, _buf: &mut [u8]) -> EngineResult {
411        Err(EngineError::WrongCommand)
412    }
413
414    /// Read the previous row in the index scan into `buf`.
415    ///
416    /// # Errors
417    /// The default returns [`EngineError::WrongCommand`]; engines return
418    /// [`EngineError::EndOfFile`] once the scan is exhausted.
419    fn index_prev(&mut self, _buf: &mut [u8]) -> EngineResult {
420        Err(EngineError::WrongCommand)
421    }
422
423    /// Read the first row of the index into `buf`.
424    ///
425    /// # Errors
426    /// The default returns [`EngineError::WrongCommand`]; engines return
427    /// [`EngineError::EndOfFile`] when the index is empty.
428    fn index_first(&mut self, _buf: &mut [u8]) -> EngineResult {
429        Err(EngineError::WrongCommand)
430    }
431
432    /// Read the last row of the index into `buf`.
433    ///
434    /// # Errors
435    /// The default returns [`EngineError::WrongCommand`]; engines return
436    /// [`EngineError::EndOfFile`] when the index is empty.
437    fn index_last(&mut self, _buf: &mut [u8]) -> EngineResult {
438        Err(EngineError::WrongCommand)
439    }
440
441    /// Read the next row that shares the leading `key` bytes with the current
442    /// position, into `buf`. The borrow on `key` may not be retained past the
443    /// call.
444    ///
445    /// # Errors
446    /// The default returns [`EngineError::WrongCommand`]; engines return
447    /// [`EngineError::EndOfFile`] when no further row shares the key.
448    fn index_next_same(&mut self, _buf: &mut [u8], _key: &[u8]) -> EngineResult {
449        Err(EngineError::WrongCommand)
450    }
451
452    /// Position the index cursor at `key` according to `find_flag` and read the
453    /// matching row into `buf`. This is the explicit-length sibling of
454    /// [`index_read_map`](Self::index_read_map): MySQL supplied the key length
455    /// directly rather than as a `key_part_map`, but the shim resolves both to
456    /// the same leading key bytes. `key` is empty when MySQL passed a null key
457    /// (begin at the first key). Neither borrow may be retained past the call.
458    ///
459    /// # Errors
460    /// The default returns [`EngineError::WrongCommand`]; engines return
461    /// [`EngineError::EndOfFile`] when no row matches.
462    fn index_read(
463        &mut self,
464        _buf: &mut [u8],
465        _key: &[u8],
466        _find_flag: RKeyFunction,
467    ) -> EngineResult {
468        Err(EngineError::WrongCommand)
469    }
470
471    /// Read from index `index` (rather than the active index) at `key` per
472    /// `find_flag`, into `buf`. The base handler brackets this with an
473    /// `index_init` / `index_end` pair; the binding instead passes `index`
474    /// explicitly so the engine never has to track an implicit active index.
475    /// `key` is empty for a null key. Neither borrow may be retained past the
476    /// call.
477    ///
478    /// # Errors
479    /// The default returns [`EngineError::WrongCommand`]; engines return
480    /// [`EngineError::EndOfFile`] when no row matches.
481    fn index_read_idx_map(
482        &mut self,
483        _buf: &mut [u8],
484        _index: u32,
485        _key: &[u8],
486        _find_flag: RKeyFunction,
487    ) -> EngineResult {
488        Err(EngineError::WrongCommand)
489    }
490
491    /// Read the last row matching `key` (or its prefix) on the active index
492    /// into `buf`. The explicit-length counterpart of
493    /// [`index_read_last_map`](Self::index_read_last_map). `key` is empty for a
494    /// null key. Neither borrow may be retained past the call.
495    ///
496    /// # Errors
497    /// The default returns [`EngineError::WrongCommand`]; engines return
498    /// [`EngineError::EndOfFile`] when no row matches.
499    fn index_read_last(&mut self, _buf: &mut [u8], _key: &[u8]) -> EngineResult {
500        Err(EngineError::WrongCommand)
501    }
502
503    /// Read the last row matching `key` (or its prefix) on the active index
504    /// into `buf`, with the key length resolved from the original
505    /// `key_part_map`. `key` is empty for a null key. Neither borrow may be
506    /// retained past the call.
507    ///
508    /// # Errors
509    /// The default returns [`EngineError::WrongCommand`]; engines return
510    /// [`EngineError::EndOfFile`] when no row matches.
511    fn index_read_last_map(&mut self, _buf: &mut [u8], _key: &[u8]) -> EngineResult {
512        Err(EngineError::WrongCommand)
513    }
514
515    /// Position the index cursor at `key` (resolved from a `key_part_map` like
516    /// [`index_read_map`](Self::index_read_map)) and read the matching row into
517    /// `buf` as the root of a pushed join. Pushed-join execution is
518    /// engine-specific (NDB-style); the binding exposes the callback so a
519    /// participating engine can implement it, but there is no `find_flag` —
520    /// MySQL only ever issues an exact-key lookup here. `key` is empty for a
521    /// null key. Neither borrow may be retained past the call.
522    ///
523    /// # Errors
524    /// The default returns [`EngineError::WrongCommand`], matching the handler
525    /// base.
526    fn index_read_pushed(&mut self, _buf: &mut [u8], _key: &[u8]) -> EngineResult {
527        Err(EngineError::WrongCommand)
528    }
529
530    /// Read the next row of the pushed-join result started by
531    /// [`index_read_pushed`](Self::index_read_pushed) into `buf`.
532    ///
533    /// # Errors
534    /// The default returns [`EngineError::WrongCommand`], matching the handler
535    /// base.
536    fn index_next_pushed(&mut self, _buf: &mut [u8]) -> EngineResult {
537        Err(EngineError::WrongCommand)
538    }
539
540    /// Begin a range scan and read its first row into `buf`. `start` and `end`
541    /// are the lower and upper bounds; either is `None` for an open end.
542    /// `eq_range` marks an equality range (`start == end`), and `sorted`
543    /// requests rows in index order. The handler base implements this by
544    /// orchestrating the index read and navigation methods plus its own
545    /// end-of-range comparison; the binding hands the whole operation to the
546    /// engine, so an overriding engine owns range-boundary enforcement.
547    ///
548    /// # Errors
549    /// The default returns [`EngineError::WrongCommand`]; engines return
550    /// [`EngineError::EndOfFile`] when the range is empty.
551    fn read_range_first(
552        &mut self,
553        _buf: &mut [u8],
554        _start: Option<RangeKey<'_>>,
555        _end: Option<RangeKey<'_>>,
556        _eq_range: bool,
557        _sorted: bool,
558    ) -> EngineResult {
559        Err(EngineError::WrongCommand)
560    }
561
562    /// Read the next row of the range scan started by
563    /// [`read_range_first`](Self::read_range_first) into `buf`.
564    ///
565    /// # Errors
566    /// The default returns [`EngineError::WrongCommand`]; engines return
567    /// [`EngineError::EndOfFile`] once the range is exhausted.
568    fn read_range_next(&mut self, _buf: &mut [u8]) -> EngineResult {
569        Err(EngineError::WrongCommand)
570    }
571
572    /// Estimate the number of rows on index `inx` between `min` and `max`
573    /// (either `None` for an open end). Used by the optimizer to cost an index
574    /// access path. Return `None` to signal "cannot estimate" (MySQL's
575    /// `HA_POS_ERROR`); the default returns `Some(10)`, mirroring the handler
576    /// base's fixed guess.
577    fn records_in_range(
578        &mut self,
579        _inx: u32,
580        _min: Option<RangeKey<'_>>,
581        _max: Option<RangeKey<'_>>,
582    ) -> Option<u64> {
583        Some(10)
584    }
585
586    /// Report whether the table is ready for a bulk load on session `thd`.
587    /// The default returns `false`, matching the handler base; engines that
588    /// support `ALTER TABLE ... SECONDARY_LOAD`-style bulk loads return `true`.
589    fn bulk_load_check(&self, _thd: Option<&sys::THD>) -> bool {
590        false
591    }
592
593    /// Report the memory budget (in bytes) the engine can devote to a bulk
594    /// load on session `thd`. The default returns `0`, matching the handler
595    /// base.
596    fn bulk_load_available_memory(&self, _thd: Option<&sys::THD>) -> usize {
597        0
598    }
599
600    /// Begin a parallel bulk load, returning an engine-owned context pointer
601    /// that [`bulk_load_execute`](Self::bulk_load_execute) and
602    /// [`bulk_load_end`](Self::bulk_load_end) receive back unchanged. `data_size`
603    /// is the total bytes to load, `memory` the budget granted, `num_threads`
604    /// the concurrency. The binding round-trips the pointer through MySQL
605    /// verbatim and never dereferences it; the engine owns its lifetime and
606    /// must free it in [`bulk_load_end`](Self::bulk_load_end). The default
607    /// returns a null pointer, matching the handler base (load not started).
608    fn bulk_load_begin(
609        &mut self,
610        _thd: Option<&sys::THD>,
611        _data_size: usize,
612        _memory: usize,
613        _num_threads: usize,
614    ) -> *mut c_void {
615        core::ptr::null_mut()
616    }
617
618    /// Load `rows` into the table on thread `thread_idx`, using the context
619    /// from [`bulk_load_begin`](Self::bulk_load_begin). `rows` and
620    /// `stat_callbacks` are opaque MySQL handles the binding cannot yet read
621    /// into, so a functioning bulk load is not expressible until that wiring
622    /// lands; the callback exists so the surface is complete. `load_ctx` is the
623    /// engine's own pointer and must be dereferenced only by the engine.
624    ///
625    /// # Errors
626    /// The default returns [`EngineError::Unsupported`], matching the handler
627    /// base which reports `HA_ERR_UNSUPPORTED` until the engine opts in.
628    fn bulk_load_execute(
629        &mut self,
630        _thd: Option<&sys::THD>,
631        _load_ctx: *mut c_void,
632        _thread_idx: usize,
633        _rows: Option<&sys::RowsMysql>,
634        _stat_callbacks: Option<&sys::BulkLoadStatCallbacks>,
635    ) -> EngineResult {
636        Err(EngineError::Unsupported)
637    }
638
639    /// End the bulk load and release the context from
640    /// [`bulk_load_begin`](Self::bulk_load_begin). Always called once after all
641    /// execute threads finish, even when `is_error` is `true`, so the engine
642    /// can free `load_ctx` on both paths.
643    ///
644    /// # Errors
645    /// The default returns `Ok(())`, matching the handler base.
646    fn bulk_load_end(
647        &mut self,
648        _thd: Option<&sys::THD>,
649        _load_ctx: *mut c_void,
650        _is_error: bool,
651    ) -> EngineResult {
652        Ok(())
653    }
654
655    /// Load `table` (opened in the primary engine) into this secondary engine;
656    /// its read-set selects which columns to load. Returns whether MySQL should
657    /// skip updating the data-dictionary metadata for this load.
658    ///
659    /// # Errors
660    /// The default returns [`EngineError::WrongCommand`]. This diverges from the
661    /// handler base, which asserts (secondary-engine-only); the binding returns
662    /// the error instead of aborting in debug builds.
663    fn load_table(&mut self, _table: Option<&sys::TABLE>) -> EngineResult<bool> {
664        Err(EngineError::WrongCommand)
665    }
666
667    /// Unload the table named `db_name`.`table_name` from this secondary engine.
668    /// When `error_if_not_loaded` is `false`, a missing table must fail
669    /// silently so a `DROP TABLE` cleanup path is not blocked.
670    ///
671    /// # Errors
672    /// The default returns [`EngineError::WrongCommand`]. This diverges from the
673    /// handler base, which asserts (secondary-engine-only); the binding returns
674    /// the error instead of aborting in debug builds.
675    fn unload_table(
676        &mut self,
677        _db_name: &str,
678        _table_name: &str,
679        _error_if_not_loaded: bool,
680    ) -> EngineResult {
681        Err(EngineError::WrongCommand)
682    }
683
684    /// Initialize a parallel scan, returning the engine-owned scan context and
685    /// the number of worker threads the engine will drive (see
686    /// [`ParallelScanInit`]). `use_reserved_threads` permits dipping into the
687    /// reserved pool when the parallel-read cap is hit; `max_desired_threads`
688    /// caps the thread count (`0` means no cap). The default returns a null
689    /// context and zero threads, matching the handler base (no parallel scan).
690    ///
691    /// # Errors
692    /// The default never errors; overrides choose their own variants.
693    fn parallel_scan_init(
694        &mut self,
695        _use_reserved_threads: bool,
696        _max_desired_threads: usize,
697    ) -> EngineResult<ParallelScanInit> {
698        Ok(ParallelScanInit::new(core::ptr::null_mut(), 0))
699    }
700
701    /// Run the parallel read using the context from
702    /// [`parallel_scan_init`](Self::parallel_scan_init). `thread_ctxs` is the
703    /// caller's per-thread context array; `init_fn` / `load_fn` / `end_fn` are
704    /// MySQL `std::function` callbacks passed as opaque pointers. The binding
705    /// cannot invoke those callbacks from Rust yet, so a functioning parallel
706    /// read is not expressible until that wiring lands; the callback exists so
707    /// the surface is complete. None of these pointers may be dereferenced
708    /// except by the code that owns them.
709    ///
710    /// # Errors
711    /// The default returns `Ok(())`, matching the handler base.
712    fn parallel_scan(
713        &mut self,
714        _scan_ctx: *mut c_void,
715        _thread_ctxs: *mut *mut c_void,
716        _init_fn: *const c_void,
717        _load_fn: *const c_void,
718        _end_fn: *const c_void,
719    ) -> EngineResult {
720        Ok(())
721    }
722
723    /// Release the parallel-scan context from
724    /// [`parallel_scan_init`](Self::parallel_scan_init). The default is a no-op.
725    fn parallel_scan_end(&mut self, _scan_ctx: *mut c_void) {}
726
727    /// Initialize sampling, returning the engine-owned scan context used by
728    /// [`sample_next`](Self::sample_next). `sampling_percentage` is the share of
729    /// rows to return (0–100), `sampling_seed` seeds the engine RNG,
730    /// `sampling_method` selects the algorithm, and `tablesample` marks an SQL
731    /// `TABLESAMPLE` rather than an internal sample. The context pointer is
732    /// round-tripped verbatim and never dereferenced by the binding.
733    ///
734    /// # Errors
735    /// The default delegates to [`rnd_init`](Self::rnd_init) with `scan = true`
736    /// and returns a null context, mirroring the handler base which samples by
737    /// scanning. The percentage filter the base applies relies on handler-
738    /// internal RNG state the binding does not expose, so the default yields
739    /// every row (an effective 100% sample) until an engine overrides this.
740    fn sample_init(
741        &mut self,
742        _sampling_percentage: f64,
743        _sampling_seed: i32,
744        _sampling_method: SamplingMethod,
745        _tablesample: bool,
746    ) -> EngineResult<*mut c_void> {
747        match self.rnd_init(true) {
748            Ok(()) => Ok(core::ptr::null_mut()),
749            Err(e) => Err(e),
750        }
751    }
752
753    /// Read the next sampled row into `buf`, using the context from
754    /// [`sample_init`](Self::sample_init).
755    ///
756    /// # Errors
757    /// The default delegates to [`rnd_next`](Self::rnd_next) (no percentage
758    /// filtering); engines return [`EngineError::EndOfFile`] once the sample is
759    /// exhausted.
760    fn sample_next(&mut self, _scan_ctx: *mut c_void, buf: &mut [u8]) -> EngineResult {
761        self.rnd_next(buf)
762    }
763
764    /// End sampling and release the context from
765    /// [`sample_init`](Self::sample_init).
766    ///
767    /// # Errors
768    /// The default delegates to [`rnd_end`](Self::rnd_end), matching the handler
769    /// base.
770    fn sample_end(&mut self, _scan_ctx: *mut c_void) -> EngineResult {
771        self.rnd_end()
772    }
773
774    /// Begin a full-text search scan.
775    ///
776    /// # Errors
777    /// The default returns [`EngineError::WrongCommand`], matching the handler
778    /// base.
779    fn ft_init(&mut self) -> EngineResult {
780        Err(EngineError::WrongCommand)
781    }
782
783    /// Create a full-text search handle for index `inx` and query `key`, with
784    /// `flags` selecting the search mode. Returns an engine-owned
785    /// `FT_INFO`-compatible pointer that MySQL drives through its vtable, or
786    /// null when the engine cannot serve the search. `key` is MySQL's `String`
787    /// query object, opaque to the binding. The pointer is round-tripped
788    /// verbatim and never dereferenced by the binding; the engine owns its
789    /// lifetime. The default returns null, matching the handler base (which
790    /// raises `ER_TABLE_CANT_HANDLE_FT`).
791    fn ft_init_ext(
792        &mut self,
793        _flags: u32,
794        _inx: u32,
795        _key: Option<&sys::MysqlString>,
796    ) -> *mut c_void {
797        core::ptr::null_mut()
798    }
799
800    /// Hint-aware variant of [`ft_init_ext`](Self::ft_init_ext). `flags` is
801    /// pre-extracted from `hints` by the shim (the binding cannot read the
802    /// opaque `hints` object from Rust); `hints` is still passed for engines
803    /// that grow richer hint handling. The default delegates to
804    /// [`ft_init_ext`](Self::ft_init_ext), mirroring the handler base.
805    fn ft_init_ext_with_hints(
806        &mut self,
807        flags: u32,
808        inx: u32,
809        key: Option<&sys::MysqlString>,
810        _hints: Option<&sys::FtHints>,
811    ) -> *mut c_void {
812        self.ft_init_ext(flags, inx, key)
813    }
814
815    /// Read the next row matching the active full-text search into `buf`.
816    ///
817    /// # Errors
818    /// The default returns [`EngineError::WrongCommand`]; engines return
819    /// [`EngineError::EndOfFile`] once the matches are exhausted.
820    fn ft_read(&mut self, _buf: &mut [u8]) -> EngineResult {
821        Err(EngineError::WrongCommand)
822    }
823
824    /// Estimate the cost of a multi-range read over a known set of ranges on
825    /// index `keyno`, for the optimizer's const-range path. `seq` is MySQL's
826    /// `RANGE_SEQ_IF` range-sequence interface, `seq_init_param` its init
827    /// argument (round-tripped without dereference), and `cost` the
828    /// `Cost_estimate` accumulator. These are opaque MySQL objects the binding
829    /// cannot drive from Rust yet, so a custom estimate is not expressible until
830    /// that wiring lands; the callback exists so the surface is complete.
831    ///
832    /// Return `None` (the default) to use the base disk-sweep MRR
833    /// implementation, which is built on
834    /// [`read_range_first`](Self::read_range_first) /
835    /// [`read_range_next`](Self::read_range_next). Engines providing a custom
836    /// multi-range read return `Some(rows)`.
837    fn multi_range_read_info_const(
838        &mut self,
839        _keyno: u32,
840        _seq: Option<&sys::RangeSeqIf>,
841        _seq_init_param: *mut c_void,
842        _n_ranges: u32,
843        _cost: Option<&sys::CostEstimate>,
844    ) -> Option<u64> {
845        None
846    }
847
848    /// Estimate the cost of a multi-range read over `n_ranges` ranges spanning
849    /// `keys` rows on index `keyno`. `cost` is the `Cost_estimate` accumulator,
850    /// an opaque MySQL object the binding cannot drive from Rust yet.
851    ///
852    /// Return `None` (the default) to use the base disk-sweep MRR
853    /// implementation; engines providing a custom multi-range read return
854    /// `Some(rows)`.
855    fn multi_range_read_info(
856        &mut self,
857        _keyno: u32,
858        _n_ranges: u32,
859        _keys: u32,
860        _cost: Option<&sys::CostEstimate>,
861    ) -> Option<u64> {
862        None
863    }
864
865    /// Initialize a multi-range read scan over the ranges from `seq` (init
866    /// argument `seq_init_param`), with `mode` carrying the `HA_MRR_*` flags and
867    /// `buf` a caller-owned `HANDLER_BUFFER` scratch area. `seq` and `buf` are
868    /// opaque MySQL objects the binding cannot drive from Rust yet.
869    ///
870    /// Return `None` (the default) to use the base disk-sweep MRR
871    /// implementation, which drives
872    /// [`read_range_first`](Self::read_range_first) /
873    /// [`read_range_next`](Self::read_range_next). Engines providing a custom
874    /// multi-range read return `Some(result)`.
875    fn multi_range_read_init(
876        &mut self,
877        _seq: Option<&sys::RangeSeqIf>,
878        _seq_init_param: *mut c_void,
879        _n_ranges: u32,
880        _mode: u32,
881        _buf: Option<&sys::HandlerBuffer>,
882    ) -> Option<EngineResult> {
883        None
884    }
885
886    /// Read the next row of the multi-range read scan into `buf`, writing the
887    /// range association through `range_info` (an opaque `char**` out-pointer
888    /// the binding round-trips without dereference).
889    ///
890    /// Return `None` (the default) to use the base disk-sweep MRR
891    /// implementation; engines providing a custom multi-range read return
892    /// `Some(result)`, where [`EngineError::EndOfFile`] marks the end of the
893    /// scan.
894    fn multi_range_read_next(
895        &mut self,
896        _buf: &mut [u8],
897        _range_info: *mut *mut c_void,
898    ) -> Option<EngineResult> {
899        None
900    }
901
902    /// Maximum row length the engine supports, in bytes. Return `None` (the
903    /// default) to use the handler base (`HA_MAX_REC_LENGTH`); engines with a
904    /// tighter cap return `Some(len)`.
905    fn max_supported_record_length(&self) -> Option<u32> {
906        None
907    }
908
909    /// Maximum number of indexes the engine supports on one table. Return `None`
910    /// (the default) to use the handler base (`0`, i.e. no indexes); engines
911    /// that support indexes return `Some(count)` — this is the gate MySQL checks
912    /// before allowing `CREATE TABLE ... KEY(...)`.
913    fn max_supported_keys(&self) -> Option<u32> {
914        None
915    }
916
917    /// Maximum number of key parts in one index. Return `None` (the default) to
918    /// use the handler base (`MAX_REF_PARTS`); engines with a tighter cap return
919    /// `Some(parts)`.
920    fn max_supported_key_parts(&self) -> Option<u32> {
921        None
922    }
923
924    /// Maximum total key length in bytes. Return `None` (the default) to use the
925    /// handler base (`MAX_KEY_LENGTH`); engines with a tighter cap return
926    /// `Some(len)`.
927    fn max_supported_key_length(&self) -> Option<u32> {
928        None
929    }
930
931    /// Maximum length in bytes of a single key part for the table described by
932    /// `create_info` (an opaque MySQL `HA_CREATE_INFO`). Return `None` (the
933    /// default) to use the handler base (`255`); engines with a different cap
934    /// return `Some(len)`.
935    fn max_supported_key_part_length(
936        &self,
937        _create_info: Option<&sys::HA_CREATE_INFO>,
938    ) -> Option<u32> {
939        None
940    }
941
942    /// Minimum row length in bytes for a table created with `options` (the
943    /// `HA_CREATE_INFO` table-option bitfield). Return `None` (the default) to
944    /// use the handler base (`1`); engines with a larger floor return
945    /// `Some(len)`.
946    fn min_record_length(&self, _options: u32) -> Option<u32> {
947        None
948    }
949
950    /// Extra per-record buffer space the engine needs beyond the row image, in
951    /// bytes. Return `None` (the default) to use the handler base (`0`); engines
952    /// needing scratch space return `Some(len)`.
953    fn extra_rec_buf_length(&self) -> Option<u32> {
954        None
955    }
956
957    /// In-memory buffer size the engine reports to the optimizer, in bytes, or a
958    /// negative value when not applicable. Return `None` (the default) to use the
959    /// handler base (`-1`); engines return `Some(bytes)`.
960    fn memory_buffer_size(&self) -> Option<i64> {
961        None
962    }
963
964    /// Whether the engine stores multi-byte values low byte first
965    /// (little-endian). Return `None` (the default) to use the handler base
966    /// (`true`); engines return `Some(flag)`.
967    fn low_byte_first(&self) -> Option<bool> {
968        None
969    }
970
971    /// Live checksum of the table, or `None` (the default) to use the handler
972    /// base (`0`, no checksum). Engines that maintain one return `Some(sum)`.
973    fn checksum(&self) -> Option<u32> {
974        None
975    }
976
977    /// Whether the table is marked crashed and needs repair. Return `None` (the
978    /// default) to use the handler base (`false`); engines return `Some(flag)`.
979    fn is_crashed(&self) -> Option<bool> {
980        None
981    }
982
983    /// Whether MySQL should attempt automatic repair when the table is found
984    /// crashed on open. Return `None` (the default) to use the handler base
985    /// (`false`); engines return `Some(flag)`.
986    fn auto_repair(&self) -> Option<bool> {
987        None
988    }
989
990    /// Whether the primary key is clustered (rows stored in PK order). Return
991    /// `None` (the default) to use the handler base (`false`); engines return
992    /// `Some(flag)`.
993    fn primary_key_is_clustered(&self) -> Option<bool> {
994        None
995    }
996
997    /// Resolve the real `row_type` for a table created from `create_info` (an
998    /// opaque MySQL `HA_CREATE_INFO`), as the raw `enum row_type` integer.
999    /// Return `None` (the default) to use the handler base, which derives the
1000    /// type from the create options; engines return `Some(row_type)`.
1001    fn real_row_type(&self, _create_info: Option<&sys::HA_CREATE_INFO>) -> Option<i32> {
1002        None
1003    }
1004
1005    /// Default index algorithm as the raw `enum ha_key_alg` integer, used when
1006    /// the user did not specify one. Return `None` (the default) to use the
1007    /// handler base (`HA_KEY_ALG_SE_SPECIFIC`); engines return `Some(alg)`.
1008    fn default_index_algorithm(&self) -> Option<i32> {
1009        None
1010    }
1011
1012    /// Whether the engine supports index algorithm `key_alg` (a raw
1013    /// `enum ha_key_alg` integer). Return `None` (the default) to use the
1014    /// handler base (supports only its default algorithm); engines return
1015    /// `Some(flag)`.
1016    fn is_index_algorithm_supported(&self, _key_alg: i32) -> Option<bool> {
1017        None
1018    }
1019
1020    /// Whether the engine wants MySQL to allocate a record buffer for
1021    /// prefetching, and for how many rows. Return `Some(max_rows)` to request a
1022    /// buffer sized for `max_rows`; `None` (the default) uses the handler base
1023    /// (no buffer wanted).
1024    fn record_buffer_wanted(&self) -> Option<u64> {
1025        None
1026    }
1027
1028    /// Engine-specific text appended to the `Extra` column of `EXPLAIN`. Return
1029    /// `None` (the default) to use the handler base (empty string); engines
1030    /// return `Some(text)`.
1031    fn explain_extra(&self) -> Option<String> {
1032        None
1033    }
1034
1035    /// Whether indexes are currently disabled (e.g. after `ALTER TABLE ...
1036    /// DISABLE KEYS`), as the raw handler int (`0` = enabled). Return `None`
1037    /// (the default) to use the handler base (`0`); engines return `Some(code)`.
1038    fn indexes_are_disabled(&mut self) -> Option<i32> {
1039        None
1040    }
1041
1042    /// Estimated cost of a full table scan, in MySQL's legacy cost unit. Return
1043    /// `None` (the default) to use the handler base, which derives it from
1044    /// `stats.data_file_length`; engines return `Some(time)`.
1045    ///
1046    /// MySQL recommends overriding this rather than
1047    /// [`table_scan_cost`](Self::table_scan_cost), whose base implementation is
1048    /// built from this value.
1049    fn scan_time(&mut self) -> Option<f64> {
1050        None
1051    }
1052
1053    /// Estimated cost of reading `ranges` ranges totalling `rows` rows through
1054    /// index `index`, in MySQL's legacy cost unit. Return `None` (the default)
1055    /// to use the handler base; engines return `Some(time)`.
1056    fn read_time(&mut self, _index: u32, _ranges: u32, _rows: u64) -> Option<f64> {
1057        None
1058    }
1059
1060    /// Estimated cost of an index-only read of `records` rows through index
1061    /// `keynr`, in MySQL's legacy cost unit. Return `None` (the default) to use
1062    /// the handler base; engines return `Some(time)`.
1063    fn index_only_read_time(&mut self, _keynr: u32, _records: f64) -> Option<f64> {
1064        None
1065    }
1066
1067    /// Cost estimate for a full table scan. Return `None` (the default) to use
1068    /// the handler base, which derives it from [`scan_time`](Self::scan_time);
1069    /// engines return `Some(cost)`.
1070    fn table_scan_cost(&mut self) -> Option<CostEstimate> {
1071        None
1072    }
1073
1074    /// Cost estimate for reading `ranges` ranges spanning `rows` rows from index
1075    /// `index` without fetching the full row. Return `None` (the default) to use
1076    /// the handler base, derived from
1077    /// [`index_only_read_time`](Self::index_only_read_time); engines return
1078    /// `Some(cost)`.
1079    fn index_scan_cost(&mut self, _index: u32, _ranges: f64, _rows: f64) -> Option<CostEstimate> {
1080        None
1081    }
1082
1083    /// Cost estimate for reading `ranges` ranges spanning `rows` rows from index
1084    /// `index`, including fetching the full rows. Return `None` (the default) to
1085    /// use the handler base, derived from [`read_time`](Self::read_time); engines
1086    /// return `Some(cost)`.
1087    fn read_cost(&mut self, _index: u32, _ranges: f64, _rows: f64) -> Option<CostEstimate> {
1088        None
1089    }
1090
1091    /// Estimated cost of `reads` non-sequential accesses against index `index`,
1092    /// in the same unit as [`worst_seek_times`](Self::worst_seek_times). Return
1093    /// `None` (the default) to use the handler base (`Cost_model::page_read_cost`);
1094    /// engines return `Some(cost)`.
1095    fn page_read_cost(&mut self, _index: u32, _reads: f64) -> Option<f64> {
1096        None
1097    }
1098
1099    /// Upper-bound cost of `reads` seek-and-read key lookups, in the same unit as
1100    /// [`page_read_cost`](Self::page_read_cost). Return `None` (the default) to
1101    /// use the handler base; engines return `Some(cost)`.
1102    fn worst_seek_times(&mut self, _reads: f64) -> Option<f64> {
1103        None
1104    }
1105
1106    /// Exact number of rows in the table. Return `None` (the default) to use the
1107    /// handler base, which counts rows with a full table scan; engines that can
1108    /// answer directly return `Some(Ok(rows))`, or `Some(Err(_))` to surface a
1109    /// failure.
1110    ///
1111    /// # Errors
1112    /// The error variant is implementation-defined and maps to the matching
1113    /// `HA_ERR_*` code at the FFI boundary.
1114    fn records(&mut self) -> Option<EngineResult<u64>> {
1115        None
1116    }
1117
1118    /// Exact number of rows counted through index `index`. Return `None` (the
1119    /// default) to use the handler base, which counts rows with an index scan;
1120    /// engines return `Some(Ok(rows))` or `Some(Err(_))`.
1121    ///
1122    /// # Errors
1123    /// The error variant is implementation-defined and maps to the matching
1124    /// `HA_ERR_*` code at the FFI boundary.
1125    fn records_from_index(&mut self, _index: u32) -> Option<EngineResult<u64>> {
1126        None
1127    }
1128
1129    /// Upper bound on the number of rows a full table scan may return. Return
1130    /// `None` (the default) to use the handler base (`stats.records` plus a
1131    /// margin); engines return `Some(rows)`.
1132    fn estimate_rows_upper_bound(&mut self) -> Option<u64> {
1133        None
1134    }
1135
1136    /// Hash value of the key columns in `field_array` for hash partitioning.
1137    /// `field_array` is a null-terminated `Field**` the binding round-trips as an
1138    /// opaque pointer valid for the call only (it cannot yet drive `Field` from
1139    /// Rust). Return `None` (the default) to use the handler base, which asserts
1140    /// — so only engines advertising hash partitioning should override and return
1141    /// `Some(hash)`.
1142    fn calculate_key_hash_value(&mut self, _field_array: *const c_void) -> Option<u32> {
1143        None
1144    }
1145
1146    /// Acquire or release a table-level lock for the session `thd`. `lock_type`
1147    /// is the raw `F_RDLCK` / `F_WRLCK` / `F_UNLCK` integer.
1148    ///
1149    /// # Errors
1150    /// The default returns `Ok(())`, matching the handler base (always succeeds).
1151    fn external_lock(&mut self, _thd: Option<&sys::THD>, _lock_type: i32) -> EngineResult {
1152        Ok(())
1153    }
1154
1155    /// Number of `THR_LOCK` entries the engine hands MySQL via `store_lock`. The
1156    /// default is `1`, matching the handler base.
1157    fn lock_count(&self) -> u32 {
1158        1
1159    }
1160
1161    /// Choose the `THR_LOCK_DATA` lock type for this handler given the server's
1162    /// proposal. `requested` is the raw `enum thr_lock_type` (`TL_READ`,
1163    /// `TL_WRITE`, …). Returning the request unchanged is the default,
1164    /// matching the canonical upgrade the shim performs for engines that do
1165    /// not override. Engines that need different priorities (e.g. always
1166    /// `TL_WRITE_LOW_PRIORITY`) can return their own choice; returning the
1167    /// `TL_IGNORE` sentinel keeps the current lock type as-is.
1168    fn store_lock(&mut self, requested: i32) -> i32 {
1169        requested
1170    }
1171
1172    /// Release the lock held on the most recently read row. The default is a
1173    /// no-op, matching the handler base.
1174    fn unlock_row(&mut self) {}
1175
1176    /// Begin a statement while the table is already locked (called instead of
1177    /// [`external_lock`](Self::external_lock) under `LOCK TABLES`). `lock_type`
1178    /// is the raw `thr_lock_type` integer.
1179    ///
1180    /// # Errors
1181    /// The default returns `Ok(())`, matching the handler base.
1182    fn start_stmt(&mut self, _thd: Option<&sys::THD>, _lock_type: i32) -> EngineResult {
1183        Ok(())
1184    }
1185
1186    /// Whether the last row was read with a semi-consistent read (skipped under
1187    /// an existing lock rather than waiting). The default is `false`, matching
1188    /// the handler base.
1189    fn was_semi_consistent_read(&mut self) -> bool {
1190        false
1191    }
1192
1193    /// Enable or disable semi-consistent reads for subsequent row reads. The
1194    /// default is a no-op, matching the handler base.
1195    fn try_semi_consistent_read(&mut self, _enable: bool) {}
1196
1197    /// Begin read-before-write removal (`HA_READ_BEFORE_WRITE_REMOVAL`). Return
1198    /// `None` (the default) to use the handler base, which asserts — only engines
1199    /// advertising the capability should override and return `Some(active)`.
1200    fn start_read_removal(&mut self) -> Option<bool> {
1201        None
1202    }
1203
1204    /// End read-before-write removal and report the number of rows actually
1205    /// written. Return `None` (the default) to use the handler base, which
1206    /// asserts; engines advertising the capability return `Some(rows)`.
1207    fn end_read_removal(&mut self) -> Option<u64> {
1208        None
1209    }
1210
1211    /// Reserve a block of auto-increment values. `offset` and `increment` define
1212    /// the value series and `nb_desired` how many values MySQL wants. Return
1213    /// `Some((first_value, nb_reserved))` to supply the block, or `None` (the
1214    /// default) to use the handler base, which derives values from table stats.
1215    fn get_auto_increment(
1216        &mut self,
1217        _offset: u64,
1218        _increment: u64,
1219        _nb_desired: u64,
1220    ) -> Option<(u64, u64)> {
1221        None
1222    }
1223
1224    /// Release auto-increment values reserved by
1225    /// [`get_auto_increment`](Self::get_auto_increment) but not used. The default
1226    /// is a no-op, matching the handler base.
1227    fn release_auto_increment(&mut self) {}
1228
1229    /// Print a diagnostic for handler error code `error` (`errflag` carries the
1230    /// `myf` formatting flags). Return `true` when the engine emitted its own
1231    /// message; `false` (the default) lets the handler base print the standard
1232    /// `HA_ERR_*` diagnostic.
1233    fn print_error(&mut self, _error: i32, _errflag: u64) -> bool {
1234        false
1235    }
1236
1237    /// Engine-specific message for handler error code `error`, paired with a
1238    /// flag marking the error as transient. Return `Some((message, temporary))`
1239    /// to surface `message` to the client — formatted as a temporary error when
1240    /// `temporary` is `true` — or `None` (the default) to use the handler base
1241    /// (no engine message).
1242    fn error_message(&mut self, _error: i32) -> Option<(String, bool)> {
1243        None
1244    }
1245
1246    /// Names of the child table and key for the most recent
1247    /// `HA_ERR_FOREIGN_DUPLICATE_KEY`. Return `Some((table, key))` to report
1248    /// them, or `None` (the default) to use the handler base (names unavailable).
1249    fn foreign_dup_key(&mut self) -> Option<(String, String)> {
1250        None
1251    }
1252
1253    /// Whether handler error code `error` may be ignored (e.g. duplicate-key
1254    /// under `INSERT IGNORE`). Return `None` (the default) to use the handler
1255    /// base classification; engines return `Some(flag)` to override it.
1256    fn is_ignorable_error(&mut self, _error: i32) -> Option<bool> {
1257        None
1258    }
1259
1260    /// Whether handler error code `error` is fatal to the running statement.
1261    /// Return `None` (the default) to use the handler base classification;
1262    /// engines return `Some(flag)` to override it.
1263    fn is_fatal_error(&mut self, _error: i32) -> Option<bool> {
1264        None
1265    }
1266
1267    /// Perform an `HA_EXTRA_*` hint operation (`operation` is the raw
1268    /// `ha_extra_function` integer). Hints are advisory.
1269    ///
1270    /// # Errors
1271    /// The default returns `Ok(())`, matching the handler base (hints ignored).
1272    fn extra(&mut self, _operation: i32) -> EngineResult {
1273        Ok(())
1274    }
1275
1276    /// Perform an `HA_EXTRA_*` hint with a size argument (`cache_size`). The
1277    /// default forwards to [`extra`](Self::extra), matching the handler base.
1278    ///
1279    /// # Errors
1280    /// Propagates whatever [`extra`](Self::extra) returns.
1281    fn extra_opt(&mut self, operation: i32, _cache_size: u64) -> EngineResult {
1282        self.extra(operation)
1283    }
1284
1285    /// Reset per-statement state so the handler can be reused for the next
1286    /// statement (clears hints, range state, etc.).
1287    ///
1288    /// # Errors
1289    /// The default returns `Ok(())`, matching the handler base.
1290    fn reset(&mut self) -> EngineResult {
1291        Ok(())
1292    }
1293
1294    /// Notify the engine that MySQL changed the read/write column bitmaps. The
1295    /// default is a no-op, matching the handler base.
1296    fn column_bitmaps_signal(&mut self) {}
1297
1298    /// Prepare engine state for use through the SQL `HANDLER` interface. The
1299    /// default is a no-op, matching the handler base.
1300    fn init_table_handle_for_handler(&mut self) {}
1301
1302    /// Report which in-place `ALTER TABLE` algorithm the engine supports for the
1303    /// change described by `alter_info` on `altered_table`, as the raw
1304    /// `enum_alter_inplace_result` integer. Return `None` (the default) to use
1305    /// the handler base, which classifies the change from the alter flags;
1306    /// engines return `Some(result)` to override.
1307    fn check_if_supported_inplace_alter(
1308        &mut self,
1309        _altered_table: Option<&sys::TABLE>,
1310        _alter_info: Option<&sys::AlterInplaceInfo>,
1311    ) -> Option<i32> {
1312        None
1313    }
1314
1315    /// Prepare an in-place `ALTER TABLE` (allocate resources, validate) before
1316    /// the change is applied. Return `Some(true)` on error, `Some(false)` on
1317    /// success, or `None` (the default) to use the handler base (success).
1318    fn prepare_inplace_alter_table(
1319        &mut self,
1320        _altered_table: Option<&sys::TABLE>,
1321        _alter_info: Option<&sys::AlterInplaceInfo>,
1322        _old_table_def: Option<&sys::DdTable>,
1323        _new_table_def: Option<&sys::DdTable>,
1324    ) -> Option<bool> {
1325        None
1326    }
1327
1328    /// Apply an in-place `ALTER TABLE` change. Return `Some(true)` on error,
1329    /// `Some(false)` on success, or `None` (the default) to use the handler base
1330    /// (success / no-op).
1331    fn inplace_alter_table(
1332        &mut self,
1333        _altered_table: Option<&sys::TABLE>,
1334        _alter_info: Option<&sys::AlterInplaceInfo>,
1335        _old_table_def: Option<&sys::DdTable>,
1336        _new_table_def: Option<&sys::DdTable>,
1337    ) -> Option<bool> {
1338        None
1339    }
1340
1341    /// Commit (`commit == true`) or roll back an in-place `ALTER TABLE`. Return
1342    /// `Some(true)` on error, `Some(false)` on success, or `None` (the default)
1343    /// to use the handler base, which clears the group-commit context.
1344    fn commit_inplace_alter_table(
1345        &mut self,
1346        _altered_table: Option<&sys::TABLE>,
1347        _alter_info: Option<&sys::AlterInplaceInfo>,
1348        _commit: bool,
1349        _old_table_def: Option<&sys::DdTable>,
1350        _new_table_def: Option<&sys::DdTable>,
1351    ) -> Option<bool> {
1352        None
1353    }
1354
1355    /// Notify the engine that an in-place `ALTER TABLE` finished and the table
1356    /// definition was updated. The default is a no-op, matching the handler
1357    /// base. No error may be reported here.
1358    fn notify_table_changed(&mut self, _alter_info: Option<&sys::AlterInplaceInfo>) {}
1359
1360    /// Whether the create options in `create_info` (with `table_changes` flags)
1361    /// are incompatible with the existing data, for the deprecated copy-based
1362    /// ALTER path. Return `None` (the default) to use the handler base
1363    /// (`COMPATIBLE_DATA_NO`, i.e. incompatible); engines return `Some(flag)`.
1364    fn check_if_incompatible_data(
1365        &mut self,
1366        _create_info: Option<&sys::HA_CREATE_INFO>,
1367        _table_changes: u32,
1368    ) -> Option<bool> {
1369        None
1370    }
1371
1372    /// Run `CHECK TABLE` for `check_opt`, returning a raw `HA_ADMIN_*` code.
1373    /// Return `None` (the default) to use the handler base
1374    /// (`HA_ADMIN_NOT_IMPLEMENTED`); engines return `Some(code)`.
1375    fn check(
1376        &mut self,
1377        _thd: Option<&sys::THD>,
1378        _check_opt: Option<&sys::HaCheckOpt>,
1379    ) -> Option<i32> {
1380        None
1381    }
1382
1383    /// Run `REPAIR TABLE` for `check_opt`, returning a raw `HA_ADMIN_*` code.
1384    /// Return `None` (the default) to use the handler base
1385    /// (`HA_ADMIN_NOT_IMPLEMENTED`); engines that advertise `HA_CAN_REPAIR`
1386    /// return `Some(code)`.
1387    fn repair(
1388        &mut self,
1389        _thd: Option<&sys::THD>,
1390        _check_opt: Option<&sys::HaCheckOpt>,
1391    ) -> Option<i32> {
1392        None
1393    }
1394
1395    /// Run `OPTIMIZE TABLE` for `check_opt`, returning a raw `HA_ADMIN_*` code.
1396    /// Return `None` (the default) to use the handler base
1397    /// (`HA_ADMIN_NOT_IMPLEMENTED`); engines return `Some(code)`.
1398    fn optimize(
1399        &mut self,
1400        _thd: Option<&sys::THD>,
1401        _check_opt: Option<&sys::HaCheckOpt>,
1402    ) -> Option<i32> {
1403        None
1404    }
1405
1406    /// Run `ANALYZE TABLE` for `check_opt`, returning a raw `HA_ADMIN_*` code.
1407    /// Return `None` (the default) to use the handler base
1408    /// (`HA_ADMIN_NOT_IMPLEMENTED`); engines return `Some(code)`.
1409    fn analyze(
1410        &mut self,
1411        _thd: Option<&sys::THD>,
1412        _check_opt: Option<&sys::HaCheckOpt>,
1413    ) -> Option<i32> {
1414        None
1415    }
1416
1417    /// Check and, if needed, repair the table on crash recovery. Return
1418    /// `Some(true)` on error / not supported, `Some(false)` on success, or
1419    /// `None` (the default) to use the handler base (`true`).
1420    fn check_and_repair(&mut self, _thd: Option<&sys::THD>) -> Option<bool> {
1421        None
1422    }
1423
1424    /// Check whether the table needs upgrading, returning a raw `HA_ADMIN_*`
1425    /// code. Return `None` (the default) to use the handler base (`0`, no
1426    /// upgrade needed); engines return `Some(code)`.
1427    fn check_for_upgrade(&mut self, _check_opt: Option<&sys::HaCheckOpt>) -> Option<i32> {
1428        None
1429    }
1430
1431    /// Preload indexes into a named key cache (`ASSIGN_TO_KEYCACHE`), returning a
1432    /// raw `HA_ADMIN_*` code. Return `None` (the default) to use the handler base
1433    /// (`HA_ADMIN_NOT_IMPLEMENTED`); engines return `Some(code)`.
1434    fn assign_to_keycache(
1435        &mut self,
1436        _thd: Option<&sys::THD>,
1437        _check_opt: Option<&sys::HaCheckOpt>,
1438    ) -> Option<i32> {
1439        None
1440    }
1441
1442    /// Preload index blocks into the default key cache (`LOAD INDEX`), returning
1443    /// a raw `HA_ADMIN_*` code. Return `None` (the default) to use the handler
1444    /// base (`HA_ADMIN_NOT_IMPLEMENTED`); engines return `Some(code)`.
1445    fn preload_keys(
1446        &mut self,
1447        _thd: Option<&sys::THD>,
1448        _check_opt: Option<&sys::HaCheckOpt>,
1449    ) -> Option<i32> {
1450        None
1451    }
1452
1453    /// Disable indexes in the given `mode` (`ALTER TABLE ... DISABLE KEYS`),
1454    /// returning a raw handler code. Return `None` (the default) to use the
1455    /// handler base (`HA_ERR_WRONG_COMMAND`); engines return `Some(code)`.
1456    fn disable_indexes(&mut self, _mode: u32) -> Option<i32> {
1457        None
1458    }
1459
1460    /// Enable indexes in the given `mode` (`ALTER TABLE ... ENABLE KEYS`),
1461    /// returning a raw handler code. Return `None` (the default) to use the
1462    /// handler base (`HA_ERR_WRONG_COMMAND`); engines return `Some(code)`.
1463    fn enable_indexes(&mut self, _mode: u32) -> Option<i32> {
1464        None
1465    }
1466
1467    /// Discard (`discard == true`) or import the tablespace for `table_def`,
1468    /// returning a raw handler code. Return `None` (the default) to use the
1469    /// handler base (`HA_ERR_WRONG_COMMAND`); engines return `Some(code)`.
1470    fn discard_or_import_tablespace(
1471        &mut self,
1472        _discard: bool,
1473        _table_def: Option<&sys::DdTable>,
1474    ) -> Option<i32> {
1475        None
1476    }
1477
1478    /// Offer the WHERE condition `cond` (an opaque `Item *` the binding
1479    /// round-trips without dereference) for engine-side evaluation. Return the
1480    /// part the engine will *not* handle: `cond` (the default) means no
1481    /// pushdown, a null pointer means the engine took the whole condition.
1482    /// Engines cannot yet construct `Item`s, so only pass-through or null are
1483    /// expressible.
1484    fn cond_push(&mut self, cond: *const c_void) -> *const c_void {
1485        cond
1486    }
1487
1488    /// Offer the index condition `idx_cond` on index `keyno` for engine-side
1489    /// evaluation (an opaque `Item *` the binding round-trips without
1490    /// dereference). Return the part not handled: `idx_cond` (the default) means
1491    /// no pushdown, null means fully handled.
1492    fn idx_cond_push(&mut self, _keyno: u32, idx_cond: *mut c_void) -> *mut c_void {
1493        idx_cond
1494    }
1495
1496    /// Discard any index condition previously accepted via
1497    /// [`idx_cond_push`](Self::idx_cond_push). The default is a no-op; the shim
1498    /// always resets the handler base's pushed-condition state regardless.
1499    fn cancel_pushed_idx_cond(&mut self) {}
1500
1501    /// The `handlerton *` of the secondary engine this handler can push work
1502    /// down to, as an opaque pointer. Return null (the default) when the engine
1503    /// supports no pushdown; round-trip a handlerton pointer otherwise.
1504    fn hton_supporting_engine_pushdown(&mut self) -> *const c_void {
1505        core::ptr::null()
1506    }
1507
1508    /// Number of joins pushed down to the engine for the current query. The
1509    /// default is `0`, matching the handler base.
1510    fn number_of_pushed_joins(&self) -> u32 {
1511        0
1512    }
1513
1514    /// The `TABLE *` of this handler's member in a pushed join, as an opaque
1515    /// pointer, or null (the default) when not part of a pushed join.
1516    fn member_of_pushed_join(&self) -> *const c_void {
1517        core::ptr::null()
1518    }
1519
1520    /// The `TABLE *` of the root of this handler's pushed join, as an opaque
1521    /// pointer, or null (the default) when not part of a pushed join.
1522    fn parent_of_pushed_join(&self) -> *const c_void {
1523        core::ptr::null()
1524    }
1525
1526    /// Bitmap (`table_map`) of the tables in this handler's pushed join. The
1527    /// default is `0`, matching the handler base.
1528    fn tables_in_pushed_join(&self) -> u64 {
1529        0
1530    }
1531
1532    /// Populate engine-specific fields of `create_info` (an opaque MySQL
1533    /// `HA_CREATE_INFO`) before `SHOW CREATE TABLE`. The default is a no-op,
1534    /// matching the handler base; the binding cannot mutate `HA_CREATE_INFO`
1535    /// from Rust yet, so this is a notification.
1536    fn update_create_info(&mut self, _create_info: Option<&sys::HA_CREATE_INFO>) {}
1537
1538    /// Engine-specific text appended to the `CREATE TABLE` statement (after the
1539    /// closing paren). Return `Some(text)` to append it, or `None` (the default)
1540    /// to append nothing, matching the handler base.
1541    fn append_create_info(&mut self) -> Option<String> {
1542        None
1543    }
1544
1545    /// Prepare the handler to position rows by a hidden primary key. The default
1546    /// is a no-op notification; the shim always runs the handler base, which
1547    /// sets up the hidden-key iteration state.
1548    fn use_hidden_primary_key(&mut self) {}
1549
1550    /// Adopt the shared `Handler_share` state (`arg` is an opaque
1551    /// `Handler_share **` the binding round-trips). Return `Some(false)` on
1552    /// success, `Some(true)` on error, or `None` (the default) to use the handler
1553    /// base, which stores the reference for cross-handler sharing.
1554    fn set_ha_share_ref(&mut self, _arg: *mut c_void) -> Option<bool> {
1555        None
1556    }
1557
1558    /// Compare two row-position references `ref1` and `ref2` (each the handler's
1559    /// `ref_length` bytes). Return `None` (the default) to use the handler base
1560    /// (`memcmp`); engines with a structured position return
1561    /// `Some(ordering)`.
1562    fn cmp_ref(&mut self, _ref1: &[u8], _ref2: &[u8]) -> Option<core::cmp::Ordering> {
1563        None
1564    }
1565
1566    /// Record `reason` as the error to raise for a failed external (secondary)
1567    /// engine offload. The default is a no-op, matching the handler base.
1568    fn set_external_table_offload_error(&mut self, _reason: &str) {}
1569
1570    /// Raise the error previously recorded by
1571    /// [`set_external_table_offload_error`](Self::set_external_table_offload_error).
1572    /// The default is a no-op, matching the handler base.
1573    fn external_table_offload_error(&self) {}
1574
1575    /// Create a clone of this handler for `name` allocated in `mem_root` (an
1576    /// opaque `MEM_ROOT *`), returning an opaque `handler *`. Return a null
1577    /// pointer (the default) to use the handler base, which builds a fresh
1578    /// handler of the same type — engines cannot construct a `handler` from Rust.
1579    fn clone_handler(&mut self, _name: &str, _mem_root: *mut c_void) -> *mut c_void {
1580        core::ptr::null_mut()
1581    }
1582
1583    /// Capacity for multi-valued index keys as `(max_keys, max_total_bytes)`.
1584    /// Return `None` (the default) to use the handler base (`(0, 0)`, no
1585    /// multi-valued index support); engines return `Some((keys, bytes))`.
1586    fn mv_key_capacity(&self) -> Option<(u32, u64)> {
1587        None
1588    }
1589
1590    /// The engine's `Partition_handler *` as an opaque pointer, or null (the
1591    /// default) when the engine does not implement native partitioning.
1592    fn get_partition_handler(&mut self) -> *mut c_void {
1593        core::ptr::null_mut()
1594    }
1595}