diff options
Diffstat (limited to 'storage/sequence/sequence.cc')
-rw-r--r-- | storage/sequence/sequence.cc | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/storage/sequence/sequence.cc b/storage/sequence/sequence.cc new file mode 100644 index 00000000..f5a18094 --- /dev/null +++ b/storage/sequence/sequence.cc @@ -0,0 +1,543 @@ +/* + Copyright (c) 2013 Monty Program 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 +*/ + +/* + a engine that auto-creates tables with rows filled with sequential values +*/ + +#include <my_config.h> +#include <ctype.h> +#include <mysql_version.h> +#include <item.h> +#include <item_sum.h> +#include <handler.h> +#include <table.h> +#include <field.h> +#include <sql_limit.h> + +static handlerton *sequence_hton; + +class Sequence_share : public Handler_share { +public: + const char *name; + THR_LOCK lock; + + ulonglong from, to, step; + bool reverse; + + Sequence_share(const char *name_arg, ulonglong from_arg, ulonglong to_arg, + ulonglong step_arg, bool reverse_arg): + name(name_arg), from(from_arg), to(to_arg), step(step_arg), + reverse(reverse_arg) + { + thr_lock_init(&lock); + } + ~Sequence_share() + { + thr_lock_delete(&lock); + } +}; + +class ha_seq final : public handler +{ +private: + THR_LOCK_DATA lock; + Sequence_share *get_share(); + ulonglong cur; + +public: + Sequence_share *seqs; + ha_seq(handlerton *hton, TABLE_SHARE *table_arg) + : handler(hton, table_arg), seqs(0) { } + ulonglong table_flags() const + { return HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE; } + + /* open/close/locking */ + int create(const char *name, TABLE *table_arg, + HA_CREATE_INFO *create_info) + { return HA_ERR_WRONG_COMMAND; } + + int open(const char *name, int mode, uint test_if_locked); + int close(void); + int delete_table(const char *name) + { + return 0; + } + THR_LOCK_DATA **store_lock(THD *, THR_LOCK_DATA **, enum thr_lock_type); + + /* table scan */ + int rnd_init(bool scan); + int rnd_next(unsigned char *buf); + void position(const uchar *record); + int rnd_pos(uchar *buf, uchar *pos); + int info(uint flag); + + /* indexes */ + ulong index_flags(uint inx, uint part, bool all_parts) const + { return HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER | + HA_READ_RANGE | HA_KEYREAD_ONLY; } + uint max_supported_keys() const { return 1; } + int index_read_map(uchar *buf, const uchar *key, key_part_map keypart_map, + enum ha_rkey_function find_flag); + int index_next(uchar *buf); + int index_prev(uchar *buf); + int index_first(uchar *buf); + int index_last(uchar *buf); + ha_rows records_in_range(uint inx, const key_range *start_key, + const key_range *end_key, page_range *pages); + double scan_time() { return (double)nvalues(); } + double read_time(uint index, uint ranges, ha_rows rows) { return (double)rows; } + double keyread_time(uint index, uint ranges, ha_rows rows) { return (double)rows; } + +private: + void set(uchar *buf); + ulonglong nvalues() { return (seqs->to - seqs->from)/seqs->step; } +}; + +THR_LOCK_DATA **ha_seq::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= TL_WRITE_ALLOW_WRITE; + *to ++= &lock; + return to; +} + +void ha_seq::set(unsigned char *buf) +{ + MY_BITMAP *old_map = dbug_tmp_use_all_columns(table, &table->write_set); + my_ptrdiff_t offset = (my_ptrdiff_t) (buf - table->record[0]); + Field *field = table->field[0]; + field->move_field_offset(offset); + field->store(cur, true); + field->move_field_offset(-offset); + dbug_tmp_restore_column_map(&table->write_set, old_map); +} + +int ha_seq::rnd_init(bool scan) +{ + cur= seqs->reverse ? seqs->to : seqs->from; + return 0; +} + +int ha_seq::rnd_next(unsigned char *buf) +{ + if (seqs->reverse) + return index_prev(buf); + else + return index_next(buf); +} + +void ha_seq::position(const uchar *record) +{ + *(ulonglong*)ref= cur; +} + +int ha_seq::rnd_pos(uchar *buf, uchar *pos) +{ + cur= *(ulonglong*)pos; + return rnd_next(buf); +} + +int ha_seq::info(uint flag) +{ + if (flag & HA_STATUS_VARIABLE) + stats.records = nvalues(); + return 0; +} + +int ha_seq::index_read_map(uchar *buf, const uchar *key_arg, + key_part_map keypart_map, + enum ha_rkey_function find_flag) +{ + ulonglong key= uint8korr(key_arg); + switch (find_flag) { + case HA_READ_AFTER_KEY: + key++; + // fall through + case HA_READ_KEY_OR_NEXT: + if (key <= seqs->from) + cur= seqs->from; + else + { + cur= (key - seqs->from + seqs->step - 1) / seqs->step * seqs->step + seqs->from; + if (cur >= seqs->to) + return HA_ERR_KEY_NOT_FOUND; + } + return index_next(buf); + + case HA_READ_KEY_EXACT: + if ((key - seqs->from) % seqs->step != 0 || key < seqs->from || key >= seqs->to) + return HA_ERR_KEY_NOT_FOUND; + cur= key; + return index_next(buf); + + case HA_READ_BEFORE_KEY: + key--; + // fall through + case HA_READ_PREFIX_LAST_OR_PREV: + if (key >= seqs->to) + cur= seqs->to; + else + { + if (key < seqs->from) + return HA_ERR_KEY_NOT_FOUND; + cur= (key - seqs->from) / seqs->step * seqs->step + seqs->from; + } + return index_prev(buf); + default: return HA_ERR_WRONG_COMMAND; + } +} + + +int ha_seq::index_next(uchar *buf) +{ + if (cur == seqs->to) + return HA_ERR_END_OF_FILE; + set(buf); + cur+= seqs->step; + return 0; +} + + +int ha_seq::index_prev(uchar *buf) +{ + if (cur == seqs->from) + return HA_ERR_END_OF_FILE; + cur-= seqs->step; + set(buf); + return 0; +} + + +int ha_seq::index_first(uchar *buf) +{ + cur= seqs->from; + return index_next(buf); +} + + +int ha_seq::index_last(uchar *buf) +{ + cur= seqs->to; + return index_prev(buf); +} + +ha_rows ha_seq::records_in_range(uint inx, const key_range *min_key, + const key_range *max_key, + page_range *pages) +{ + ulonglong kmin= min_key ? uint8korr(min_key->key) : seqs->from; + ulonglong kmax= max_key ? uint8korr(max_key->key) : seqs->to - 1; + if (kmin >= seqs->to || kmax < seqs->from || kmin > kmax) + return 0; + return (kmax - seqs->from) / seqs->step - + (kmin - seqs->from + seqs->step - 1) / seqs->step + 1; +} + + +int ha_seq::open(const char *name, int mode, uint test_if_locked) +{ + if (!(seqs= get_share())) + return HA_ERR_OUT_OF_MEM; + DBUG_ASSERT(my_strcasecmp(table_alias_charset, name, seqs->name) == 0); + + ref_length= sizeof(cur); + thr_lock_data_init(&seqs->lock,&lock,NULL); + return 0; +} + +int ha_seq::close(void) +{ + return 0; +} + +static handler *create_handler(handlerton *hton, TABLE_SHARE *table, + MEM_ROOT *mem_root) +{ + return new (mem_root) ha_seq(hton, table); +} + + +static bool parse_table_name(const char *name, size_t name_length, + ulonglong *from, ulonglong *to, ulonglong *step) +{ + uint n0=0, n1= 0, n2= 0; + *step= 1; + + // the table is discovered if its name matches the pattern of seq_1_to_10 or + // seq_1_to_10_step_3 + sscanf(name, "seq_%llu_to_%n%llu%n_step_%llu%n", + from, &n0, to, &n1, step, &n2); + // I consider this a bug in sscanf() - when an unsigned number + // is requested, -5 should *not* be accepted. But is is :( + // hence the additional check below: + return + n0 == 0 || !isdigit(name[4]) || !isdigit(name[n0]) || // reject negative numbers + (n1 != name_length && n2 != name_length); +} + + +Sequence_share *ha_seq::get_share() +{ + Sequence_share *tmp_share; + lock_shared_ha_data(); + if (!(tmp_share= static_cast<Sequence_share*>(get_ha_share_ptr()))) + { + bool reverse; + ulonglong from, to, step; + + parse_table_name(table_share->table_name.str, + table_share->table_name.length, &from, &to, &step); + + if ((reverse = from > to)) + { + if (step > from - to) + to = from; + else + swap_variables(ulonglong, from, to); + /* + when keyread is allowed, optimizer will always prefer an index to a + table scan for our tables, and we'll never see the range reversed. + */ + table_share->keys_for_keyread.clear_all(); + } + + to= (to - from) / step * step + step + from; + + tmp_share= new Sequence_share(table_share->normalized_path.str, from, to, step, reverse); + + if (!tmp_share) + goto err; + set_ha_share_ptr(static_cast<Handler_share*>(tmp_share)); + } +err: + unlock_shared_ha_data(); + return tmp_share; +} + + +static int discover_table(handlerton *hton, THD *thd, TABLE_SHARE *share) +{ + ulonglong from, to, step; + if (parse_table_name(share->table_name.str, share->table_name.length, + &from, &to, &step)) + return HA_ERR_NO_SUCH_TABLE; + + if (step == 0) + return HA_WRONG_CREATE_OPTION; + + const char *sql="create table seq (seq bigint unsigned primary key)"; + return share->init_from_sql_statement_string(thd, 0, sql, strlen(sql)); +} + + +static int discover_table_existence(handlerton *hton, const char *db, + const char *table_name) +{ + ulonglong from, to, step; + return !parse_table_name(table_name, strlen(table_name), &from, &to, &step); +} + +static int dummy_commit_rollback(handlerton *, THD *, bool) { return 0; } + +static int dummy_savepoint(handlerton *, THD *, void *) { return 0; } + +/***************************************************************************** + Example of a simple group by handler for queries like: + SELECT SUM(seq) from sequence_table; + + This implementation supports SUM() and COUNT() on primary key. +*****************************************************************************/ + +class ha_seq_group_by_handler: public group_by_handler +{ + Select_limit_counters limit; + List<Item> *fields; + TABLE_LIST *table_list; + bool first_row; + +public: + ha_seq_group_by_handler(THD *thd_arg, List<Item> *fields_arg, + TABLE_LIST *table_list_arg, + Select_limit_counters *orig_lim) + : group_by_handler(thd_arg, sequence_hton), limit(orig_lim[0]), + fields(fields_arg), table_list(table_list_arg) + { + // Reset limit because we are handling it now + orig_lim->set_unlimited(); + } + ~ha_seq_group_by_handler() {} + int init_scan() { first_row= 1 ; return 0; } + int next_row(); + int end_scan() { return 0; } +}; + +static group_by_handler * +create_group_by_handler(THD *thd, Query *query) +{ + ha_seq_group_by_handler *handler; + Item *item; + List_iterator_fast<Item> it(*query->select); + + /* check that only one table is used in FROM clause and no sub queries */ + if (query->from->next_local != 0) + return 0; + /* check that there is no where clause and no group_by */ + if (query->where != 0 || query->group_by != 0) + return 0; + + /* + Check that all fields are sum(primary_key) or count(primary_key) + For more ways to work with the field list and sum functions, see + opt_sum.cc::opt_sum_query(). + */ + while ((item= it++)) + { + Item *arg0; + Field *field; + if (item->type() != Item::SUM_FUNC_ITEM || + (((Item_sum*) item)->sum_func() != Item_sum::SUM_FUNC && + ((Item_sum*) item)->sum_func() != Item_sum::COUNT_FUNC)) + + return 0; // Not a SUM() function + arg0= ((Item_sum*) item)->get_arg(0); + if (arg0->type() != Item::FIELD_ITEM) + { + if ((((Item_sum*) item)->sum_func() == Item_sum::COUNT_FUNC) && + arg0->basic_const_item()) + continue; // Allow count(1) + return 0; + } + field= ((Item_field*) arg0)->field; + /* + Check that we are using the sequence table (the only table in the FROM + clause) and not an outer table. + */ + if (field->table != query->from->table) + return 0; + /* Check that we are using a SUM() on the primary key */ + if (strcmp(field->field_name.str, "seq")) + return 0; + } + + /* Create handler and return it */ + handler= new ha_seq_group_by_handler(thd, query->select, query->from, + query->limit); + return handler; +} + +int ha_seq_group_by_handler::next_row() +{ + List_iterator_fast<Item> it(*fields); + Item_sum *item_sum; + Sequence_share *seqs= ((ha_seq*) table_list->table->file)->seqs; + DBUG_ENTER("ha_seq_group_by_handler::next_row"); + + /* + Check if this is the first call to the function. If not, we have already + returned all data. + */ + if (!first_row || + limit.get_offset_limit() > 0 || + limit.get_select_limit() == 0) + DBUG_RETURN(HA_ERR_END_OF_FILE); + first_row= 0; + + /* Pointer to first field in temporary table where we should store summary*/ + Field **field_ptr= table->field; + ulonglong elements= (seqs->to - seqs->from + seqs->step - 1) / seqs->step; + + while ((item_sum= (Item_sum*) it++)) + { + Field *field= *(field_ptr++); + switch (item_sum->sum_func()) { + case Item_sum::COUNT_FUNC: + { + Item *arg0= ((Item_sum*) item_sum)->get_arg(0); + if (arg0->basic_const_item() && arg0->is_null()) + field->store(0LL, 1); + else + field->store((longlong) elements, 1); + break; + } + case Item_sum::SUM_FUNC: + { + /* Calculate SUM(f, f+step, f+step*2 ... to) */ + ulonglong sum; + sum= seqs->from * elements + seqs->step * (elements*elements-elements)/2; + field->store((longlong) sum, 1); + break; + } + default: + DBUG_ASSERT(0); + } + field->set_notnull(); + } + DBUG_RETURN(0); +} + + +/***************************************************************************** + Initialize the interface between the sequence engine and MariaDB +*****************************************************************************/ + +static int drop_table(handlerton *hton, const char *path) +{ + const char *name= strrchr(path, FN_LIBCHAR)+1; + ulonglong from, to, step; + if (parse_table_name(name, strlen(name), &from, &to, &step)) + return ENOENT; + return 0; +} + +static int init(void *p) +{ + handlerton *hton= (handlerton *)p; + sequence_hton= hton; + hton->create= create_handler; + hton->drop_table= drop_table; + hton->discover_table= discover_table; + hton->discover_table_existence= discover_table_existence; + hton->commit= hton->rollback= dummy_commit_rollback; + hton->savepoint_set= hton->savepoint_rollback= hton->savepoint_release= + dummy_savepoint; + hton->create_group_by= create_group_by_handler; + return 0; +} + +static struct st_mysql_storage_engine descriptor = +{ MYSQL_HANDLERTON_INTERFACE_VERSION }; + +maria_declare_plugin(sequence) +{ + MYSQL_STORAGE_ENGINE_PLUGIN, + &descriptor, + "SEQUENCE", + "Sergei Golubchik", + "Generated tables filled with sequential values", + PLUGIN_LICENSE_GPL, + init, + NULL, + 0x0100, + NULL, + NULL, + "0.1", + MariaDB_PLUGIN_MATURITY_STABLE +} +maria_declare_plugin_end; |