summaryrefslogtreecommitdiffstats
path: root/storage/sequence/sequence.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/sequence/sequence.cc')
-rw-r--r--storage/sequence/sequence.cc543
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;