diff options
Diffstat (limited to 'storage/example')
-rw-r--r-- | storage/example/CMakeLists.txt | 17 | ||||
-rw-r--r-- | storage/example/ha_example.cc | 1133 | ||||
-rw-r--r-- | storage/example/ha_example.h | 254 | ||||
-rw-r--r-- | storage/example/mysql-test/README | 2 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/suite.pm | 8 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/combs.combinations | 2 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/inc.inc | 1 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/newcomb.combinations | 2 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/over.result | 4 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/over.test | 8 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/self.result | 6 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/self.test | 8 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/source.result | 3 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/test2,c2.result | 4 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/test2.opt | 1 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/test2.rdiff | 8 | ||||
-rw-r--r-- | storage/example/mysql-test/mtr/t/testsh-master.sh | 1 |
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 |