summaryrefslogtreecommitdiffstats
path: root/storage/example
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 18:00:34 +0000
commit3f619478f796eddbba6e39502fe941b285dd97b1 (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /storage/example
parentInitial commit. (diff)
downloadmariadb-upstream.tar.xz
mariadb-upstream.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/example')
-rw-r--r--storage/example/CMakeLists.txt17
-rw-r--r--storage/example/ha_example.cc1133
-rw-r--r--storage/example/ha_example.h254
-rw-r--r--storage/example/mysql-test/README2
-rw-r--r--storage/example/mysql-test/mtr/suite.pm8
-rw-r--r--storage/example/mysql-test/mtr/t/combs.combinations2
-rw-r--r--storage/example/mysql-test/mtr/t/inc.inc1
-rw-r--r--storage/example/mysql-test/mtr/t/newcomb.combinations2
-rw-r--r--storage/example/mysql-test/mtr/t/over.result4
-rw-r--r--storage/example/mysql-test/mtr/t/over.test8
-rw-r--r--storage/example/mysql-test/mtr/t/self.result6
-rw-r--r--storage/example/mysql-test/mtr/t/self.test8
-rw-r--r--storage/example/mysql-test/mtr/t/source.result3
-rw-r--r--storage/example/mysql-test/mtr/t/test2,c2.result4
-rw-r--r--storage/example/mysql-test/mtr/t/test2.opt1
-rw-r--r--storage/example/mysql-test/mtr/t/test2.rdiff8
-rw-r--r--storage/example/mysql-test/mtr/t/testsh-master.sh1
17 files changed, 1462 insertions, 0 deletions
diff --git a/storage/example/CMakeLists.txt b/storage/example/CMakeLists.txt
new file mode 100644
index 00000000..5b59d1b1
--- /dev/null
+++ b/storage/example/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
+
+SET(EXAMPLE_SOURCES ha_example.cc)
+MYSQL_ADD_PLUGIN(example ${EXAMPLE_SOURCES} STORAGE_ENGINE MODULE_ONLY COMPONENT Test)
diff --git a/storage/example/ha_example.cc b/storage/example/ha_example.cc
new file mode 100644
index 00000000..c66c33a7
--- /dev/null
+++ b/storage/example/ha_example.cc
@@ -0,0 +1,1133 @@
+/* Copyright (c) 2004, 2013, Oracle and/or its affiliates.
+ Copyright (c) 2010, 2014, SkySQL Ab.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+/**
+ @file ha_example.cc
+
+ @brief
+ The ha_example engine is a stubbed storage engine for example purposes only;
+ it does almost nothing at this point. Its purpose is to provide a source
+ code illustration of how to begin writing new storage engines; see also
+ storage/example/ha_example.h.
+
+ Additionally, this file includes an example of a daemon plugin which does
+ nothing at all - absolutely nothing, even less than example storage engine.
+ But it shows that one dll/so can contain more than one plugin.
+
+ @details
+ ha_example will let you create/open/delete tables, but
+ nothing further (for example, indexes are not supported nor can data
+ be stored in the table). It also provides new status (example_func_example)
+ and system (example_ulong_var and example_enum_var) variables.
+
+ Use this example as a template for implementing the same functionality in
+ your own storage engine. You can enable the example storage engine in your
+ build by doing the following during your build process:<br> ./configure
+ --with-example-storage-engine
+
+ Once this is done, MySQL will let you create tables with:<br>
+ CREATE TABLE <table name> (...) ENGINE=EXAMPLE;
+
+ The example storage engine is set up to use table locks. It
+ implements an example "SHARE" that is inserted into a hash by table
+ name. You can use this to store information of state that any
+ example handler object will be able to see when it is using that
+ table.
+
+ Please read the object definition in ha_example.h before reading the rest
+ of this file.
+
+ @note
+ When you create an EXAMPLE table, the MySQL Server creates a table .frm
+ (format) file in the database directory, using the table name as the file
+ name as is customary with MySQL. No other files are created. To get an idea
+ of what occurs, here is an example select that would do a scan of an entire
+ table:
+
+ @code
+ ha_example::store_lock
+ ha_example::external_lock
+ ha_example::info
+ ha_example::rnd_init
+ ha_example::extra
+ ENUM HA_EXTRA_CACHE Cache record in HA_rrnd()
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::rnd_next
+ ha_example::extra
+ ENUM HA_EXTRA_NO_CACHE End caching of records (def)
+ ha_example::external_lock
+ ha_example::extra
+ ENUM HA_EXTRA_RESET Reset database to after open
+ @endcode
+
+ Here you see that the example storage engine has 9 rows called before
+ rnd_next signals that it has reached the end of its data. Also note that
+ the table in question was already opened; had it not been open, a call to
+ ha_example::open() would also have been necessary. Calls to
+ ha_example::extra() are hints as to what will be occuring to the request.
+
+ A Longer Example can be found called the "Skeleton Engine" which can be
+ found on TangentOrg. It has both an engine and a full build environment
+ for building a pluggable storage engine.
+
+ Happy coding!<br>
+ -Brian
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include <my_global.h>
+#include <mysql/plugin.h>
+#include "ha_example.h"
+#include "sql_class.h"
+
+static handler *example_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root);
+
+handlerton *example_hton;
+
+static MYSQL_THDVAR_ULONG(varopt_default, PLUGIN_VAR_RQCMDARG,
+ "default value of the VAROPT table option", NULL, NULL, 5, 0, 100, 0);
+
+/**
+ Structure for CREATE TABLE options (table options).
+ It needs to be called ha_table_option_struct.
+
+ The option values can be specified in the CREATE TABLE at the end:
+ CREATE TABLE ( ... ) *here*
+*/
+
+struct ha_table_option_struct
+{
+ const char *strparam;
+ ulonglong ullparam;
+ uint enumparam;
+ bool boolparam;
+ ulonglong varparam;
+};
+
+
+/**
+ Structure for CREATE TABLE options (field options).
+ It needs to be called ha_field_option_struct.
+
+ The option values can be specified in the CREATE TABLE per field:
+ CREATE TABLE ( field ... *here*, ... )
+*/
+
+struct ha_field_option_struct
+{
+ const char *complex_param_to_parse_it_in_engine;
+};
+
+/*
+ no example here, but index options can be declared similarly
+ using the ha_index_option_struct structure.
+
+ Their values can be specified in the CREATE TABLE per index:
+ CREATE TABLE ( field ..., .., INDEX .... *here*, ... )
+*/
+
+ha_create_table_option example_table_option_list[]=
+{
+ /*
+ one numeric option, with the default of UINT_MAX32, valid
+ range of values 0..UINT_MAX32, and a "block size" of 10
+ (any value must be divisible by 10).
+ */
+ HA_TOPTION_NUMBER("ULL", ullparam, UINT_MAX32, 0, UINT_MAX32, 10),
+ /*
+ one option that takes an arbitrary string
+ */
+ HA_TOPTION_STRING("STR", strparam),
+ /*
+ one enum option. a valid values are strings ONE and TWO.
+ A default value is 0, that is "one".
+ */
+ HA_TOPTION_ENUM("one_or_two", enumparam, "one,two", 0),
+ /*
+ one boolean option, the valid values are YES/NO, ON/OFF, 1/0.
+ The default is 1, that is true, yes, on.
+ */
+ HA_TOPTION_BOOL("YESNO", boolparam, 1),
+ /*
+ one option defined by the system variable. The type, the range, or
+ a list of allowed values is the same as for the system variable.
+ */
+ HA_TOPTION_SYSVAR("VAROPT", varparam, varopt_default),
+
+ HA_TOPTION_END
+};
+
+ha_create_table_option example_field_option_list[]=
+{
+ /*
+ If the engine wants something more complex than a string, number, enum,
+ or boolean - for example a list - it needs to specify the option
+ as a string and parse it internally.
+ */
+ HA_FOPTION_STRING("COMPLEX", complex_param_to_parse_it_in_engine),
+ HA_FOPTION_END
+};
+
+
+/**
+ @brief
+ Function we use in the creation of our hash to get key.
+*/
+
+#ifdef HAVE_PSI_INTERFACE
+static PSI_mutex_key ex_key_mutex_Example_share_mutex;
+
+static PSI_mutex_info all_example_mutexes[]=
+{
+ { &ex_key_mutex_Example_share_mutex, "Example_share::mutex", 0}
+};
+
+static void init_example_psi_keys()
+{
+ const char* category= "example";
+ int count;
+
+ count= array_elements(all_example_mutexes);
+ mysql_mutex_register(category, all_example_mutexes, count);
+}
+#else
+static void init_example_psi_keys() { }
+#endif
+
+
+/**
+ @brief
+ If frm_error() is called then we will use this to determine
+ the file extensions that exist for the storage engine. This is also
+ used by the default rename_table and delete_table method in
+ handler.cc and by the default discover_many method.
+
+ For engines that have two file name extensions (separate meta/index file
+ and data file), the order of elements is relevant. First element of engine
+ file name extensions array should be meta/index file extention. Second
+ element - data file extention. This order is assumed by
+ prepare_for_repair() when REPAIR TABLE ... USE_FRM is issued.
+
+ @see
+ rename_table method in handler.cc and
+ delete_table method in handler.cc
+*/
+
+static const char *ha_example_exts[] = {
+ NullS
+};
+
+Example_share::Example_share()
+{
+ thr_lock_init(&lock);
+ mysql_mutex_init(ex_key_mutex_Example_share_mutex,
+ &mutex, MY_MUTEX_INIT_FAST);
+}
+
+
+static int example_init_func(void *p)
+{
+ DBUG_ENTER("example_init_func");
+
+ init_example_psi_keys();
+
+ example_hton= (handlerton *)p;
+ example_hton->create= example_create_handler;
+ example_hton->flags= HTON_CAN_RECREATE;
+ example_hton->table_options= example_table_option_list;
+ example_hton->field_options= example_field_option_list;
+ example_hton->tablefile_extensions= ha_example_exts;
+ example_hton->drop_table= [](handlerton *, const char*) { return -1; };
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Example of simple lock controls. The "share" it creates is a
+ structure we will pass to each example handler. Do you have to have
+ one of these? Well, you have pieces that are used for locking, and
+ they are needed to function.
+*/
+
+Example_share *ha_example::get_share()
+{
+ Example_share *tmp_share;
+
+ DBUG_ENTER("ha_example::get_share()");
+
+ lock_shared_ha_data();
+ if (!(tmp_share= static_cast<Example_share*>(get_ha_share_ptr())))
+ {
+ tmp_share= new Example_share;
+ if (!tmp_share)
+ goto err;
+
+ set_ha_share_ptr(static_cast<Handler_share*>(tmp_share));
+ }
+err:
+ unlock_shared_ha_data();
+ DBUG_RETURN(tmp_share);
+}
+
+static handler* example_create_handler(handlerton *hton,
+ TABLE_SHARE *table,
+ MEM_ROOT *mem_root)
+{
+ return new (mem_root) ha_example(hton, table);
+}
+
+ha_example::ha_example(handlerton *hton, TABLE_SHARE *table_arg)
+ :handler(hton, table_arg)
+{}
+
+
+/**
+ @brief
+ Used for opening tables. The name will be the name of the file.
+
+ @details
+ A table is opened when it needs to be opened; e.g. when a request comes in
+ for a SELECT on the table (tables are not open and closed for each request,
+ they are cached).
+
+ Called from handler.cc by handler::ha_open(). The server opens all tables by
+ calling ha_open() which then calls the handler specific open().
+
+ @see
+ handler::ha_open() in handler.cc
+*/
+
+int ha_example::open(const char *name, int mode, uint test_if_locked)
+{
+ DBUG_ENTER("ha_example::open");
+
+ if (!(share = get_share()))
+ DBUG_RETURN(1);
+ thr_lock_data_init(&share->lock,&lock,NULL);
+
+#ifndef DBUG_OFF
+ ha_table_option_struct *options= table->s->option_struct;
+
+ DBUG_ASSERT(options);
+ DBUG_PRINT("info", ("strparam: '%-.64s' ullparam: %llu enumparam: %u "\
+ "boolparam: %u",
+ (options->strparam ? options->strparam : "<NULL>"),
+ options->ullparam, options->enumparam, options->boolparam));
+#endif
+
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Closes a table.
+
+ @details
+ Called from sql_base.cc, sql_select.cc, and table.cc. In sql_select.cc it is
+ only used to close up temporary tables or during the process where a
+ temporary table is converted over to being a myisam table.
+
+ For sql_base.cc look at close_data_tables().
+
+ @see
+ sql_base.cc, sql_select.cc and table.cc
+*/
+
+int ha_example::close(void)
+{
+ DBUG_ENTER("ha_example::close");
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ write_row() inserts a row. No extra() hint is given currently if a bulk load
+ is happening. buf() is a byte array of data. You can use the field
+ information to extract the data from the native byte array type.
+
+ @details
+ Example of this would be:
+ @code
+ for (Field **field=table->field ; *field ; field++)
+ {
+ ...
+ }
+ @endcode
+
+ See ha_tina.cc for an example of extracting all of the data as strings.
+ ha_berekly.cc has an example of how to store it intact by "packing" it
+ for ha_berkeley's own native storage type.
+
+ See the note for update_row() on auto_increments and timestamps. This
+ case also applies to write_row().
+
+ Called from item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
+ sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc, and sql_update.cc.
+
+ @see
+ item_sum.cc, item_sum.cc, sql_acl.cc, sql_insert.cc,
+ sql_insert.cc, sql_select.cc, sql_table.cc, sql_udf.cc and sql_update.cc
+*/
+
+int ha_example::write_row(const uchar *buf)
+{
+ DBUG_ENTER("ha_example::write_row");
+ /*
+ Example of a successful write_row. We don't store the data
+ anywhere; they are thrown away. A real implementation will
+ probably need to do something with 'buf'. We report a success
+ here, to pretend that the insert was successful.
+ */
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Yes, update_row() does what you expect, it updates a row. old_data will have
+ the previous row record in it, while new_data will have the newest data in it.
+ Keep in mind that the server can do updates based on ordering if an ORDER BY
+ clause was used. Consecutive ordering is not guaranteed.
+
+ @details
+ Currently new_data will not have an updated auto_increament record, or
+ and updated timestamp field. You can do these for example by doing:
+ @code
+ if (table->next_number_field && record == table->record[0])
+ update_auto_increment();
+ @endcode
+
+ Called from sql_select.cc, sql_acl.cc, sql_update.cc, and sql_insert.cc.
+
+ @see
+ sql_select.cc, sql_acl.cc, sql_update.cc and sql_insert.cc
+*/
+int ha_example::update_row(const uchar *old_data, const uchar *new_data)
+{
+
+ DBUG_ENTER("ha_example::update_row");
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+}
+
+
+/**
+ @brief
+ This will delete a row. buf will contain a copy of the row to be deleted.
+ The server will call this right after the current row has been called (from
+ either a previous rnd_nexT() or index call).
+
+ @details
+ If you keep a pointer to the last row or can access a primary key it will
+ make doing the deletion quite a bit easier. Keep in mind that the server does
+ not guarantee consecutive deletions. ORDER BY clauses can be used.
+
+ Called in sql_acl.cc and sql_udf.cc to manage internal table
+ information. Called in sql_delete.cc, sql_insert.cc, and
+ sql_select.cc. In sql_select it is used for removing duplicates
+ while in insert it is used for REPLACE calls.
+
+ @see
+ sql_acl.cc, sql_udf.cc, sql_delete.cc, sql_insert.cc and sql_select.cc
+*/
+
+int ha_example::delete_row(const uchar *buf)
+{
+ DBUG_ENTER("ha_example::delete_row");
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+}
+
+
+/**
+ @brief
+ Positions an index cursor to the index specified in the handle. Fetches the
+ row if available. If the key value is null, begin at the first key of the
+ index.
+*/
+
+int ha_example::index_read_map(uchar *buf, const uchar *key,
+ key_part_map keypart_map __attribute__((unused)),
+ enum ha_rkey_function find_flag
+ __attribute__((unused)))
+{
+ int rc;
+ DBUG_ENTER("ha_example::index_read");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Used to read forward through the index.
+*/
+
+int ha_example::index_next(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_example::index_next");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ Used to read backwards through the index.
+*/
+
+int ha_example::index_prev(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_example::index_prev");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ index_first() asks for the first key in the index.
+
+ @details
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.
+
+ @see
+ opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc
+*/
+int ha_example::index_first(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_example::index_first");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ index_last() asks for the last key in the index.
+
+ @details
+ Called from opt_range.cc, opt_sum.cc, sql_handler.cc, and sql_select.cc.
+
+ @see
+ opt_range.cc, opt_sum.cc, sql_handler.cc and sql_select.cc
+*/
+int ha_example::index_last(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_example::index_last");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ rnd_init() is called when the system wants the storage engine to do a table
+ scan. See the example in the introduction at the top of this file to see when
+ rnd_init() is called.
+
+ @details
+ Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
+ and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc
+*/
+int ha_example::rnd_init(bool scan)
+{
+ DBUG_ENTER("ha_example::rnd_init");
+ DBUG_RETURN(0);
+}
+
+int ha_example::rnd_end()
+{
+ DBUG_ENTER("ha_example::rnd_end");
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ This is called for each row of the table scan. When you run out of records
+ you should return HA_ERR_END_OF_FILE. Fill buff up with the row information.
+ The Field structure for the table is the key to getting data into buf
+ in a manner that will allow the server to understand it.
+
+ @details
+ Called from filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc,
+ and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_handler.cc, sql_select.cc, sql_table.cc and sql_update.cc
+*/
+int ha_example::rnd_next(uchar *buf)
+{
+ int rc;
+ DBUG_ENTER("ha_example::rnd_next");
+ rc= HA_ERR_END_OF_FILE;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ position() is called after each call to rnd_next() if the data needs
+ to be ordered. You can do something like the following to store
+ the position:
+ @code
+ my_store_ptr(ref, ref_length, current_position);
+ @endcode
+
+ @details
+ The server uses ref to store data. ref_length in the above case is
+ the size needed to store current_position. ref is just a byte array
+ that the server will maintain. If you are using offsets to mark rows, then
+ current_position should be the offset. If it is a primary key like in
+ BDB, then it needs to be a primary key.
+
+ Called from filesort.cc, sql_select.cc, sql_delete.cc, and sql_update.cc.
+
+ @see
+ filesort.cc, sql_select.cc, sql_delete.cc and sql_update.cc
+*/
+void ha_example::position(const uchar *record)
+{
+ DBUG_ENTER("ha_example::position");
+ DBUG_VOID_RETURN;
+}
+
+
+/**
+ @brief
+ This is like rnd_next, but you are given a position to use
+ to determine the row. The position will be of the type that you stored in
+ ref. You can use ha_get_ptr(pos,ref_length) to retrieve whatever key
+ or position you saved when position() was called.
+
+ @details
+ Called from filesort.cc, records.cc, sql_insert.cc, sql_select.cc, and sql_update.cc.
+
+ @see
+ filesort.cc, records.cc, sql_insert.cc, sql_select.cc and sql_update.cc
+*/
+int ha_example::rnd_pos(uchar *buf, uchar *pos)
+{
+ int rc;
+ DBUG_ENTER("ha_example::rnd_pos");
+ rc= HA_ERR_WRONG_COMMAND;
+ DBUG_RETURN(rc);
+}
+
+
+/**
+ @brief
+ ::info() is used to return information to the optimizer. See my_base.h for
+ the complete description.
+
+ @details
+ Currently this table handler doesn't implement most of the fields really needed.
+ SHOW also makes use of this data.
+
+ You will probably want to have the following in your code:
+ @code
+ if (records < 2)
+ records = 2;
+ @endcode
+ The reason is that the server will optimize for cases of only a single
+ record. If, in a table scan, you don't know the number of records, it
+ will probably be better to set records to two so you can return as many
+ records as you need. Along with records, a few more variables you may wish
+ to set are:
+ records
+ deleted
+ data_file_length
+ index_file_length
+ delete_length
+ check_time
+ Take a look at the public variables in handler.h for more information.
+
+ Called in filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc,
+ sql_delete.cc, sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc,
+ sql_select.cc, sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc,
+ sql_table.cc, sql_union.cc, and sql_update.cc.
+
+ @see
+ filesort.cc, ha_heap.cc, item_sum.cc, opt_sum.cc, sql_delete.cc, sql_delete.cc,
+ sql_derived.cc, sql_select.cc, sql_select.cc, sql_select.cc, sql_select.cc,
+ sql_select.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_show.cc, sql_table.cc,
+ sql_union.cc and sql_update.cc
+*/
+int ha_example::info(uint flag)
+{
+ DBUG_ENTER("ha_example::info");
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ extra() is called whenever the server wishes to send a hint to
+ the storage engine. The myisam engine implements the most hints.
+ ha_innodb.cc has the most exhaustive list of these hints.
+
+ @see
+ ha_innodb.cc
+*/
+int ha_example::extra(enum ha_extra_function operation)
+{
+ DBUG_ENTER("ha_example::extra");
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Used to delete all rows in a table, including cases of truncate and cases where
+ the optimizer realizes that all rows will be removed as a result of an SQL statement.
+
+ @details
+ Called from item_sum.cc by Item_func_group_concat::clear(),
+ Item_sum_count_distinct::clear(), and Item_func_group_concat::clear().
+ Called from sql_delete.cc by mysql_delete().
+ Called from sql_select.cc by JOIN::reinit().
+ Called from sql_union.cc by st_select_lex_unit::exec().
+
+ @see
+ Item_func_group_concat::clear(), Item_sum_count_distinct::clear() and
+ Item_func_group_concat::clear() in item_sum.cc;
+ mysql_delete() in sql_delete.cc;
+ JOIN::reinit() in sql_select.cc and
+ st_select_lex_unit::exec() in sql_union.cc.
+*/
+int ha_example::delete_all_rows()
+{
+ DBUG_ENTER("ha_example::delete_all_rows");
+ DBUG_RETURN(HA_ERR_WRONG_COMMAND);
+}
+
+
+/**
+ @brief
+ This create a lock on the table. If you are implementing a storage engine
+ that can handle transacations look at ha_berkely.cc to see how you will
+ want to go about doing this. Otherwise you should consider calling flock()
+ here. Hint: Read the section "locking functions for mysql" in lock.cc to understand
+ this.
+
+ @details
+ Called from lock.cc by lock_external() and unlock_external(). Also called
+ from sql_table.cc by copy_data_between_tables().
+
+ @see
+ lock.cc by lock_external() and unlock_external() in lock.cc;
+ the section "locking functions for mysql" in lock.cc;
+ copy_data_between_tables() in sql_table.cc.
+*/
+int ha_example::external_lock(THD *thd, int lock_type)
+{
+ DBUG_ENTER("ha_example::external_lock");
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ The idea with handler::store_lock() is: The statement decides which locks
+ should be needed for the table. For updates/deletes/inserts we get WRITE
+ locks, for SELECT... we get read locks.
+
+ @details
+ Before adding the lock into the table lock handler (see thr_lock.c),
+ mysqld calls store lock with the requested locks. Store lock can now
+ modify a write lock to a read lock (or some other lock), ignore the
+ lock (if we don't want to use MySQL table locks at all), or add locks
+ for many tables (like we do when we are using a MERGE handler).
+
+ Berkeley DB, for example, changes all WRITE locks to TL_WRITE_ALLOW_WRITE
+ (which signals that we are doing WRITES, but are still allowing other
+ readers and writers).
+
+ When releasing locks, store_lock() is also called. In this case one
+ usually doesn't have to do anything.
+
+ In some exceptional cases MySQL may send a request for a TL_IGNORE;
+ This means that we are requesting the same lock as last time and this
+ should also be ignored. (This may happen when someone does a flush
+ table when we have opened a part of the tables, in which case mysqld
+ closes and reopens the tables and tries to get the same locks at last
+ time). In the future we will probably try to remove this.
+
+ Called from lock.cc by get_lock_data().
+
+ @note
+ In this method one should NEVER rely on table->in_use, it may, in fact,
+ refer to a different thread! (this happens if get_lock_data() is called
+ from mysql_lock_abort_for_thread() function)
+
+ @see
+ get_lock_data() in lock.cc
+*/
+THR_LOCK_DATA **ha_example::store_lock(THD *thd,
+ THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type)
+{
+ if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
+ lock.type=lock_type;
+ *to++= &lock;
+ return to;
+}
+
+
+/**
+ @brief
+ Used to delete a table. By the time delete_table() has been called all
+ opened references to this table will have been closed (and your globally
+ shared references released). The variable name will just be the name of
+ the table. You will need to remove any files you have created at this point.
+
+ @details
+ If you do not implement this, the default delete_table() is called from
+ handler.cc and it will delete all files with the file extensions returned
+ by bas_ext().
+
+ Called from handler.cc by delete_table and ha_create_table(). Only used
+ during create if the table_flag HA_DROP_BEFORE_CREATE was specified for
+ the storage engine.
+
+ @see
+ delete_table and ha_create_table() in handler.cc
+*/
+int ha_example::delete_table(const char *name)
+{
+ DBUG_ENTER("ha_example::delete_table");
+ /* This is not implemented but we want someone to be able that it works. */
+ DBUG_RETURN(0);
+}
+
+
+/**
+ @brief
+ Given a starting key and an ending key, estimate the number of rows that
+ will exist between the two keys.
+ The handler can also optionally update the 'pages' parameter with the page
+ number that contains the min and max keys. This will help the optimizer
+ to know if two ranges are partly on the same pages and if the min and
+ max key are on the same page.
+
+ @details
+ end_key may be empty, in which case determine if start_key matches any rows.
+
+ Called from opt_range.cc by check_quick_keys().
+
+ @see
+ check_quick_keys() in opt_range.cc
+*/
+ha_rows ha_example::records_in_range(uint inx,
+ const key_range *min_key,
+ const key_range *max_key,
+ page_range *pages)
+{
+ DBUG_ENTER("ha_example::records_in_range");
+ DBUG_RETURN(10); // low number to force index usage
+}
+
+
+/**
+ @brief
+ create() is called to create a database. The variable name will have the name
+ of the table.
+
+ @details
+ When create() is called you do not need to worry about
+ opening the table. Also, the .frm file will have already been
+ created so adjusting create_info is not necessary. You can overwrite
+ the .frm file at this point if you wish to change the table
+ definition, but there are no methods currently provided for doing
+ so.
+
+ Called from handle.cc by ha_create_table().
+
+ @see
+ ha_create_table() in handle.cc
+*/
+
+int ha_example::create(const char *name, TABLE *table_arg,
+ HA_CREATE_INFO *create_info)
+{
+#ifndef DBUG_OFF
+ ha_table_option_struct *options= table_arg->s->option_struct;
+ DBUG_ENTER("ha_example::create");
+ /*
+ This example shows how to support custom engine specific table and field
+ options.
+ */
+ DBUG_ASSERT(options);
+ DBUG_PRINT("info", ("strparam: '%-.64s' ullparam: %llu enumparam: %u "\
+ "boolparam: %u",
+ (options->strparam ? options->strparam : "<NULL>"),
+ options->ullparam, options->enumparam, options->boolparam));
+ for (Field **field= table_arg->s->field; *field; field++)
+ {
+ ha_field_option_struct *field_options= (*field)->option_struct;
+ DBUG_ASSERT(field_options);
+ DBUG_PRINT("info", ("field: %s complex: '%-.64s'",
+ (*field)->field_name.str,
+ (field_options->complex_param_to_parse_it_in_engine ?
+ field_options->complex_param_to_parse_it_in_engine :
+ "<NULL>")));
+ }
+#endif
+ DBUG_RETURN(0);
+}
+
+
+/**
+ check_if_supported_inplace_alter() is used to ask the engine whether
+ it can execute this ALTER TABLE statement in place or the server needs to
+ create a new table and copy th data over.
+
+ The engine may answer that the inplace alter is not supported or,
+ if supported, whether the server should protect the table from concurrent
+ accesses. Return values are
+
+ HA_ALTER_INPLACE_NOT_SUPPORTED
+ HA_ALTER_INPLACE_EXCLUSIVE_LOCK
+ HA_ALTER_INPLACE_SHARED_LOCK
+ etc
+*/
+
+enum_alter_inplace_result
+ha_example::check_if_supported_inplace_alter(TABLE* altered_table,
+ Alter_inplace_info* ha_alter_info)
+{
+ HA_CREATE_INFO *info= ha_alter_info->create_info;
+ DBUG_ENTER("ha_example::check_if_supported_inplace_alter");
+
+ if (ha_alter_info->handler_flags & ALTER_CHANGE_CREATE_OPTION)
+ {
+ /*
+ This example shows how custom engine specific table and field
+ options can be accessed from this function to be compared.
+ */
+ ha_table_option_struct *param_new= info->option_struct;
+ ha_table_option_struct *param_old= table->s->option_struct;
+
+ /*
+ check important parameters:
+ for this example engine, we'll assume that changing ullparam or
+ boolparam requires a table to be rebuilt, while changing strparam
+ or enumparam - does not.
+
+ For debugging purposes we'll announce this to the user
+ (don't do it in production!)
+
+ */
+ if (param_new->ullparam != param_old->ullparam)
+ {
+ push_warning_printf(ha_thd(), Sql_condition::WARN_LEVEL_NOTE,
+ ER_UNKNOWN_ERROR, "EXAMPLE DEBUG: ULL %llu -> %llu",
+ param_old->ullparam, param_new->ullparam);
+ DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
+ }
+
+ if (param_new->boolparam != param_old->boolparam)
+ {
+ push_warning_printf(ha_thd(), Sql_condition::WARN_LEVEL_NOTE,
+ ER_UNKNOWN_ERROR, "EXAMPLE DEBUG: YESNO %u -> %u",
+ param_old->boolparam, param_new->boolparam);
+ DBUG_RETURN(HA_ALTER_INPLACE_NOT_SUPPORTED);
+ }
+ }
+
+ if (ha_alter_info->handler_flags & ALTER_COLUMN_OPTION)
+ {
+ for (uint i= 0; i < table->s->fields; i++)
+ {
+ ha_field_option_struct *f_old= table->s->field[i]->option_struct;
+ ha_field_option_struct *f_new= info->fields_option_struct[i];
+ DBUG_ASSERT(f_old);
+ if (f_new)
+ {
+ push_warning_printf(ha_thd(), Sql_condition::WARN_LEVEL_NOTE,
+ ER_UNKNOWN_ERROR, "EXAMPLE DEBUG: Field %`s COMPLEX '%s' -> '%s'",
+ table->s->field[i]->field_name.str,
+ f_old->complex_param_to_parse_it_in_engine,
+ f_new->complex_param_to_parse_it_in_engine);
+ }
+ else
+ DBUG_PRINT("info", ("old field %i did not changed", i));
+ }
+ }
+
+ DBUG_RETURN(HA_ALTER_INPLACE_EXCLUSIVE_LOCK);
+}
+
+
+struct st_mysql_storage_engine example_storage_engine=
+{ MYSQL_HANDLERTON_INTERFACE_VERSION };
+
+static ulong srv_enum_var= 0;
+static ulong srv_ulong_var= 0;
+static double srv_double_var= 0;
+
+const char *enum_var_names[]=
+{
+ "e1", "e2", NullS
+};
+
+TYPELIB enum_var_typelib=
+{
+ array_elements(enum_var_names) - 1, "enum_var_typelib",
+ enum_var_names, NULL
+};
+
+static MYSQL_SYSVAR_ENUM(
+ enum_var, // name
+ srv_enum_var, // varname
+ PLUGIN_VAR_RQCMDARG, // opt
+ "Sample ENUM system variable.", // comment
+ NULL, // check
+ NULL, // update
+ 0, // def
+ &enum_var_typelib); // typelib
+
+static MYSQL_THDVAR_INT(int_var, PLUGIN_VAR_RQCMDARG, "-1..1",
+ NULL, NULL, 0, -1, 1, 0);
+
+static MYSQL_SYSVAR_ULONG(
+ ulong_var,
+ srv_ulong_var,
+ PLUGIN_VAR_RQCMDARG,
+ "0..1000",
+ NULL,
+ NULL,
+ 8,
+ 0,
+ 1000,
+ 0);
+
+static MYSQL_SYSVAR_DOUBLE(
+ double_var,
+ srv_double_var,
+ PLUGIN_VAR_RQCMDARG,
+ "0.500000..1000.500000",
+ NULL,
+ NULL,
+ 8.5,
+ 0.5,
+ 1000.5,
+ 0); // reserved always 0
+
+static MYSQL_THDVAR_DOUBLE(
+ double_thdvar,
+ PLUGIN_VAR_RQCMDARG,
+ "0.500000..1000.500000",
+ NULL,
+ NULL,
+ 8.5,
+ 0.5,
+ 1000.5,
+ 0);
+
+static MYSQL_THDVAR_INT(
+ deprecated_var, PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_DEPRECATED, "-1..1",
+ NULL, NULL, 0, -1, 1, 0);
+
+static struct st_mysql_sys_var* example_system_variables[]= {
+ MYSQL_SYSVAR(enum_var),
+ MYSQL_SYSVAR(ulong_var),
+ MYSQL_SYSVAR(int_var),
+ MYSQL_SYSVAR(double_var),
+ MYSQL_SYSVAR(double_thdvar),
+ MYSQL_SYSVAR(deprecated_var),
+ MYSQL_SYSVAR(varopt_default),
+ NULL
+};
+
+// this is an example of SHOW_SIMPLE_FUNC and of my_snprintf() service
+// If this function would return an array, one should use SHOW_FUNC
+static int show_func_example(MYSQL_THD thd, struct st_mysql_show_var *var,
+ char *buf)
+{
+ var->type= SHOW_CHAR;
+ var->value= buf; // it's of SHOW_VAR_FUNC_BUFF_SIZE bytes
+ my_snprintf(buf, SHOW_VAR_FUNC_BUFF_SIZE,
+ "enum_var is %lu, ulong_var is %lu, int_var is %d, "
+ "double_var is %f, %.6b", // %b is a MySQL extension
+ srv_enum_var, srv_ulong_var, THDVAR(thd, int_var),
+ srv_double_var, "really");
+ return 0;
+}
+
+static struct st_mysql_show_var func_status[]=
+{
+ {"func_example", (char *)show_func_example, SHOW_SIMPLE_FUNC},
+ {0,0,SHOW_UNDEF}
+};
+
+struct st_mysql_daemon unusable_example=
+{ MYSQL_DAEMON_INTERFACE_VERSION };
+
+maria_declare_plugin(example)
+{
+ MYSQL_STORAGE_ENGINE_PLUGIN,
+ &example_storage_engine,
+ "EXAMPLE",
+ "Brian Aker, MySQL AB",
+ "Example storage engine",
+ PLUGIN_LICENSE_GPL,
+ example_init_func, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x0001, /* version number (0.1) */
+ func_status, /* status variables */
+ example_system_variables, /* system variables */
+ "0.1", /* string version */
+ MariaDB_PLUGIN_MATURITY_EXPERIMENTAL /* maturity */
+},
+{
+ MYSQL_DAEMON_PLUGIN,
+ &unusable_example,
+ "UNUSABLE",
+ "Sergei Golubchik",
+ "Unusable Daemon",
+ PLUGIN_LICENSE_GPL,
+ NULL, /* Plugin Init */
+ NULL, /* Plugin Deinit */
+ 0x030E, /* version number (3.14) */
+ NULL, /* status variables */
+ NULL, /* system variables */
+ "3.14.15.926" , /* version, as a string */
+ MariaDB_PLUGIN_MATURITY_EXPERIMENTAL /* maturity */
+}
+maria_declare_plugin_end;
diff --git a/storage/example/ha_example.h b/storage/example/ha_example.h
new file mode 100644
index 00000000..5d067f7c
--- /dev/null
+++ b/storage/example/ha_example.h
@@ -0,0 +1,254 @@
+/*
+ Copyright (c) 2004, 2010, Oracle and/or its affiliates
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
+
+/** @file ha_example.h
+
+ @brief
+ The ha_example engine is a stubbed storage engine for example purposes only;
+ it does nothing at this point. Its purpose is to provide a source
+ code illustration of how to begin writing new storage engines; see also
+ /storage/example/ha_example.cc.
+
+ @note
+ Please read ha_example.cc before reading this file.
+ Reminder: The example storage engine implements all methods that are *required*
+ to be implemented. For a full list of all methods that you can implement, see
+ handler.h.
+
+ @see
+ /sql/handler.h and /storage/example/ha_example.cc
+*/
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+#include "my_global.h" /* ulonglong */
+#include "thr_lock.h" /* THR_LOCK, THR_LOCK_DATA */
+#include "handler.h" /* handler */
+#include "my_base.h" /* ha_rows */
+
+/** @brief
+ Example_share is a class that will be shared among all open handlers.
+ This example implements the minimum of what you will probably need.
+*/
+class Example_share : public Handler_share {
+public:
+ mysql_mutex_t mutex;
+ THR_LOCK lock;
+ Example_share();
+ ~Example_share()
+ {
+ thr_lock_delete(&lock);
+ mysql_mutex_destroy(&mutex);
+ }
+};
+
+/** @brief
+ Class definition for the storage engine
+*/
+class ha_example: public handler
+{
+ THR_LOCK_DATA lock; ///< MySQL lock
+ Example_share *share; ///< Shared lock info
+ Example_share *get_share(); ///< Get the share
+
+public:
+ ha_example(handlerton *hton, TABLE_SHARE *table_arg);
+ ~ha_example() = default;
+
+ /** @brief
+ The name of the index type that will be used for display.
+ Don't implement this method unless you really have indexes.
+ */
+ const char *index_type(uint inx) { return "HASH"; }
+
+ /** @brief
+ This is a list of flags that indicate what functionality the storage engine
+ implements. The current table flags are documented in handler.h
+ */
+ ulonglong table_flags() const
+ {
+ /*
+ We are saying that this engine is just statement capable to have
+ an engine that can only handle statement-based logging. This is
+ used in testing.
+ */
+ return HA_BINLOG_STMT_CAPABLE;
+ }
+
+ /** @brief
+ This is a bitmap of flags that indicates how the storage engine
+ implements indexes. The current index flags are documented in
+ handler.h. If you do not implement indexes, just return zero here.
+
+ @details
+ part is the key part to check. First key part is 0.
+ If all_parts is set, MySQL wants to know the flags for the combined
+ index, up to and including 'part'.
+ */
+ ulong index_flags(uint inx, uint part, bool all_parts) const
+ {
+ return 0;
+ }
+
+ /** @brief
+ unireg.cc will call max_supported_record_length(), max_supported_keys(),
+ max_supported_key_parts(), uint max_supported_key_length()
+ to make sure that the storage engine can handle the data it is about to
+ send. Return *real* limits of your storage engine here; MySQL will do
+ min(your_limits, MySQL_limits) automatically.
+ */
+ uint max_supported_record_length() const { return HA_MAX_REC_LENGTH; }
+
+ /** @brief
+ unireg.cc will call this to make sure that the storage engine can handle
+ the data it is about to send. Return *real* limits of your storage engine
+ here; MySQL will do min(your_limits, MySQL_limits) automatically.
+
+ @details
+ There is no need to implement ..._key_... methods if your engine doesn't
+ support indexes.
+ */
+ uint max_supported_keys() const { return 0; }
+
+ /** @brief
+ unireg.cc will call this to make sure that the storage engine can handle
+ the data it is about to send. Return *real* limits of your storage engine
+ here; MySQL will do min(your_limits, MySQL_limits) automatically.
+
+ @details
+ There is no need to implement ..._key_... methods if your engine doesn't
+ support indexes.
+ */
+ uint max_supported_key_parts() const { return 0; }
+
+ /** @brief
+ unireg.cc will call this to make sure that the storage engine can handle
+ the data it is about to send. Return *real* limits of your storage engine
+ here; MySQL will do min(your_limits, MySQL_limits) automatically.
+
+ @details
+ There is no need to implement ..._key_... methods if your engine doesn't
+ support indexes.
+ */
+ uint max_supported_key_length() const { return 0; }
+
+ /** @brief
+ Called in test_quick_select to determine if indexes should be used.
+ */
+ virtual double scan_time() { return (double) (stats.records+stats.deleted) / 20.0+10; }
+
+ /** @brief
+ This method will never be called if you do not implement indexes.
+ */
+ virtual double read_time(uint, uint, ha_rows rows)
+ { return (double) rows / 20.0+1; }
+
+ /*
+ Everything below are methods that we implement in ha_example.cc.
+
+ Most of these methods are not obligatory, skip them and
+ MySQL will treat them as not implemented
+ */
+ /** @brief
+ We implement this in ha_example.cc; it's a required method.
+ */
+ int open(const char *name, int mode, uint test_if_locked); // required
+
+ /** @brief
+ We implement this in ha_example.cc; it's a required method.
+ */
+ int close(void); // required
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int write_row(const uchar *buf);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int update_row(const uchar *old_data, const uchar *new_data);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int delete_row(const uchar *buf);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int index_read_map(uchar *buf, const uchar *key,
+ key_part_map keypart_map, enum ha_rkey_function find_flag);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int index_next(uchar *buf);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int index_prev(uchar *buf);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int index_first(uchar *buf);
+
+ /** @brief
+ We implement this in ha_example.cc. It's not an obligatory method;
+ skip it and and MySQL will treat it as not implemented.
+ */
+ int index_last(uchar *buf);
+
+ /** @brief
+ Unlike index_init(), rnd_init() can be called two consecutive times
+ without rnd_end() in between (it only makes sense if scan=1). In this
+ case, the second call should prepare for the new table scan (e.g if
+ rnd_init() allocates the cursor, the second call should position the
+ cursor to the start of the table; no need to deallocate and allocate
+ it again. This is a required method.
+ */
+ int rnd_init(bool scan); //required
+ int rnd_end();
+ int rnd_next(uchar *buf); ///< required
+ int rnd_pos(uchar *buf, uchar *pos); ///< required
+ void position(const uchar *record); ///< required
+ int info(uint); ///< required
+ int extra(enum ha_extra_function operation);
+ int external_lock(THD *thd, int lock_type); ///< required
+ int delete_all_rows(void);
+ ha_rows records_in_range(uint inx, const key_range *min_key,
+ const key_range *max_key, page_range *pages);
+ int delete_table(const char *from);
+ int create(const char *name, TABLE *form,
+ HA_CREATE_INFO *create_info); ///< required
+ enum_alter_inplace_result
+ check_if_supported_inplace_alter(TABLE* altered_table,
+ Alter_inplace_info* ha_alter_info);
+
+ THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
+ enum thr_lock_type lock_type); ///< required
+};
diff --git a/storage/example/mysql-test/README b/storage/example/mysql-test/README
new file mode 100644
index 00000000..0af43c76
--- /dev/null
+++ b/storage/example/mysql-test/README
@@ -0,0 +1,2 @@
+These tests don't have anything to do with the EXAMPLE engine itself,
+but they show how mysql-test handles overlays
diff --git a/storage/example/mysql-test/mtr/suite.pm b/storage/example/mysql-test/mtr/suite.pm
new file mode 100644
index 00000000..f7ff4224
--- /dev/null
+++ b/storage/example/mysql-test/mtr/suite.pm
@@ -0,0 +1,8 @@
+package My::Suite::MTR::Example;
+
+@ISA = qw(My::Suite);
+
+sub skip_combinations {(
+ 't/combs.combinations' => [ 'c1' ],
+)}
+bless { };
diff --git a/storage/example/mysql-test/mtr/t/combs.combinations b/storage/example/mysql-test/mtr/t/combs.combinations
new file mode 100644
index 00000000..518a0262
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/combs.combinations
@@ -0,0 +1,2 @@
+[c3o]
+table-cache=32
diff --git a/storage/example/mysql-test/mtr/t/inc.inc b/storage/example/mysql-test/mtr/t/inc.inc
new file mode 100644
index 00000000..8bca2f83
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/inc.inc
@@ -0,0 +1 @@
+let $a=2;
diff --git a/storage/example/mysql-test/mtr/t/newcomb.combinations b/storage/example/mysql-test/mtr/t/newcomb.combinations
new file mode 100644
index 00000000..baeaaf83
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/newcomb.combinations
@@ -0,0 +1,2 @@
+[new]
+--ansi
diff --git a/storage/example/mysql-test/mtr/t/over.result b/storage/example/mysql-test/mtr/t/over.result
new file mode 100644
index 00000000..20f11088
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/over.result
@@ -0,0 +1,4 @@
+select @@local_infile;
+select 1;
+1
+1
diff --git a/storage/example/mysql-test/mtr/t/over.test b/storage/example/mysql-test/mtr/t/over.test
new file mode 100644
index 00000000..15c57ec4
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/over.test
@@ -0,0 +1,8 @@
+#
+# This test exists only in the overlay. It will run only for the overlay
+# and not for the parent suite.
+#
+--disable_result_log
+source suite/mtr/t/combs.inc;
+--enable_result_log
+select 1;
diff --git a/storage/example/mysql-test/mtr/t/self.result b/storage/example/mysql-test/mtr/t/self.result
new file mode 100644
index 00000000..b907150d
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/self.result
@@ -0,0 +1,6 @@
+select "<1>";
+<1>
+<1>
+select "<2>";
+<2>
+<2>
diff --git a/storage/example/mysql-test/mtr/t/self.test b/storage/example/mysql-test/mtr/t/self.test
new file mode 100644
index 00000000..6afa109d
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/self.test
@@ -0,0 +1,8 @@
+#
+# A test that includes itself. But really it includes the
+# self.test from the parent suite, not itself.
+#
+let $a=1;
+source self.test;
+let $a=2;
+source self.test;
diff --git a/storage/example/mysql-test/mtr/t/source.result b/storage/example/mysql-test/mtr/t/source.result
new file mode 100644
index 00000000..07fac9ed
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/source.result
@@ -0,0 +1,3 @@
+select 2;
+2
+2
diff --git a/storage/example/mysql-test/mtr/t/test2,c2.result b/storage/example/mysql-test/mtr/t/test2,c2.result
new file mode 100644
index 00000000..ff5fb337
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/test2,c2.result
@@ -0,0 +1,4 @@
+select @@local_infile;
+select @@max_error_count;
+@@max_error_count
+32
diff --git a/storage/example/mysql-test/mtr/t/test2.opt b/storage/example/mysql-test/mtr/t/test2.opt
new file mode 100644
index 00000000..e0a2b842
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/test2.opt
@@ -0,0 +1 @@
+--max-error-count=32
diff --git a/storage/example/mysql-test/mtr/t/test2.rdiff b/storage/example/mysql-test/mtr/t/test2.rdiff
new file mode 100644
index 00000000..b0bf2fdf
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/test2.rdiff
@@ -0,0 +1,8 @@
+--- /usr/home/serg/Abk/mysql/5.1/mysql-test/suite/mtr/t/test2.result 2012-02-04 21:15:14.000000000 +0100
++++ /usr/home/serg/Abk/mysql/5.1/mysql-test/suite/mtr/t/test2.reject 2012-02-04 21:31:45.000000000 +0100
+@@ -1,4 +1,4 @@
+ select @@local_infile;
+ select @@max_error_count;
+ @@max_error_count
+-64
++32
diff --git a/storage/example/mysql-test/mtr/t/testsh-master.sh b/storage/example/mysql-test/mtr/t/testsh-master.sh
new file mode 100644
index 00000000..27ba77dd
--- /dev/null
+++ b/storage/example/mysql-test/mtr/t/testsh-master.sh
@@ -0,0 +1 @@
+true