1 /*
2    Copyright (c) 2013 Monty Program Ab
3 
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License
6    as published by the Free Software Foundation; version 2 of
7    the License.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA
17 */
18 
19 /*
20   a engine that auto-creates tables with rows filled with sequential values
21 */
22 
23 #include <my_config.h>
24 #include <ctype.h>
25 #include <mysql_version.h>
26 #include <item.h>
27 #include <item_sum.h>
28 #include <handler.h>
29 #include <table.h>
30 #include <field.h>
31 #include <sql_limit.h>
32 
33 static handlerton *sequence_hton;
34 
35 class Sequence_share : public Handler_share {
36 public:
37   const char *name;
38   THR_LOCK lock;
39 
40   ulonglong from, to, step;
41   bool reverse;
42 
Sequence_share(const char * name_arg,ulonglong from_arg,ulonglong to_arg,ulonglong step_arg,bool reverse_arg)43   Sequence_share(const char *name_arg, ulonglong from_arg, ulonglong to_arg,
44                  ulonglong step_arg, bool reverse_arg):
45     name(name_arg), from(from_arg), to(to_arg), step(step_arg),
46     reverse(reverse_arg)
47   {
48     thr_lock_init(&lock);
49   }
~Sequence_share()50   ~Sequence_share()
51   {
52     thr_lock_delete(&lock);
53   }
54 };
55 
56 class ha_seq final : public handler
57 {
58 private:
59   THR_LOCK_DATA lock;
60   Sequence_share *get_share();
61   ulonglong cur;
62 
63 public:
64   Sequence_share *seqs;
ha_seq(handlerton * hton,TABLE_SHARE * table_arg)65   ha_seq(handlerton *hton, TABLE_SHARE *table_arg)
66     : handler(hton, table_arg), seqs(0) { }
table_flags() const67   ulonglong table_flags() const
68   { return HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE; }
69 
70   /* open/close/locking */
create(const char * name,TABLE * table_arg,HA_CREATE_INFO * create_info)71   int create(const char *name, TABLE *table_arg,
72              HA_CREATE_INFO *create_info)
73   { return HA_ERR_WRONG_COMMAND; }
74 
75   int open(const char *name, int mode, uint test_if_locked);
76   int close(void);
delete_table(const char * name)77   int delete_table(const char *name)
78   {
79     return 0;
80   }
81   THR_LOCK_DATA **store_lock(THD *, THR_LOCK_DATA **, enum thr_lock_type);
82 
83   /* table scan */
84   int rnd_init(bool scan);
85   int rnd_next(unsigned char *buf);
86   void position(const uchar *record);
87   int rnd_pos(uchar *buf, uchar *pos);
88   int info(uint flag);
89 
90   /* indexes */
index_flags(uint inx,uint part,bool all_parts) const91   ulong index_flags(uint inx, uint part, bool all_parts) const
92   { return HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER |
93            HA_READ_RANGE | HA_KEYREAD_ONLY; }
max_supported_keys() const94   uint max_supported_keys() const { return 1; }
95   int index_read_map(uchar *buf, const uchar *key, key_part_map keypart_map,
96                      enum ha_rkey_function find_flag);
97   int index_next(uchar *buf);
98   int index_prev(uchar *buf);
99   int index_first(uchar *buf);
100   int index_last(uchar *buf);
101   ha_rows records_in_range(uint inx, const key_range *start_key,
102                            const key_range *end_key, page_range *pages);
scan_time()103   double scan_time() { return (double)nvalues(); }
read_time(uint index,uint ranges,ha_rows rows)104   double read_time(uint index, uint ranges, ha_rows rows) { return (double)rows; }
keyread_time(uint index,uint ranges,ha_rows rows)105   double keyread_time(uint index, uint ranges, ha_rows rows) { return (double)rows; }
106 
107 private:
108   void set(uchar *buf);
nvalues()109   ulonglong nvalues() { return (seqs->to - seqs->from)/seqs->step; }
110 };
111 
store_lock(THD * thd,THR_LOCK_DATA ** to,enum thr_lock_type lock_type)112 THR_LOCK_DATA **ha_seq::store_lock(THD *thd, THR_LOCK_DATA **to,
113                            enum thr_lock_type lock_type)
114 {
115   if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK)
116     lock.type= TL_WRITE_ALLOW_WRITE;
117   *to ++= &lock;
118   return to;
119 }
120 
set(unsigned char * buf)121 void ha_seq::set(unsigned char *buf)
122 {
123   MY_BITMAP *old_map = dbug_tmp_use_all_columns(table, &table->write_set);
124   my_ptrdiff_t offset = (my_ptrdiff_t) (buf - table->record[0]);
125   Field *field = table->field[0];
126   field->move_field_offset(offset);
127   field->store(cur, true);
128   field->move_field_offset(-offset);
129   dbug_tmp_restore_column_map(&table->write_set, old_map);
130 }
131 
rnd_init(bool scan)132 int ha_seq::rnd_init(bool scan)
133 {
134   cur= seqs->reverse ? seqs->to : seqs->from;
135   return 0;
136 }
137 
rnd_next(unsigned char * buf)138 int ha_seq::rnd_next(unsigned char *buf)
139 {
140   if (seqs->reverse)
141     return index_prev(buf);
142   else
143     return index_next(buf);
144 }
145 
position(const uchar * record)146 void ha_seq::position(const uchar *record)
147 {
148   *(ulonglong*)ref= cur;
149 }
150 
rnd_pos(uchar * buf,uchar * pos)151 int ha_seq::rnd_pos(uchar *buf, uchar *pos)
152 {
153   cur= *(ulonglong*)pos;
154   return rnd_next(buf);
155 }
156 
info(uint flag)157 int ha_seq::info(uint flag)
158 {
159   if (flag & HA_STATUS_VARIABLE)
160     stats.records = nvalues();
161   return 0;
162 }
163 
index_read_map(uchar * buf,const uchar * key_arg,key_part_map keypart_map,enum ha_rkey_function find_flag)164 int ha_seq::index_read_map(uchar *buf, const uchar *key_arg,
165                            key_part_map keypart_map,
166                            enum ha_rkey_function find_flag)
167 {
168   ulonglong key= uint8korr(key_arg);
169   switch (find_flag) {
170   case HA_READ_AFTER_KEY:
171     key++;
172     // fall through
173   case HA_READ_KEY_OR_NEXT:
174     if (key <= seqs->from)
175       cur= seqs->from;
176     else
177     {
178       cur= (key - seqs->from + seqs->step - 1) / seqs->step * seqs->step + seqs->from;
179       if (cur >= seqs->to)
180         return HA_ERR_KEY_NOT_FOUND;
181     }
182     return index_next(buf);
183 
184   case HA_READ_KEY_EXACT:
185     if ((key - seqs->from) % seqs->step != 0 || key < seqs->from || key >= seqs->to)
186       return HA_ERR_KEY_NOT_FOUND;
187     cur= key;
188     return index_next(buf);
189 
190   case HA_READ_BEFORE_KEY:
191     key--;
192     // fall through
193   case HA_READ_PREFIX_LAST_OR_PREV:
194     if (key >= seqs->to)
195       cur= seqs->to;
196     else
197     {
198       if (key < seqs->from)
199         return HA_ERR_KEY_NOT_FOUND;
200       cur= (key - seqs->from) / seqs->step * seqs->step + seqs->from;
201     }
202     return index_prev(buf);
203   default: return HA_ERR_WRONG_COMMAND;
204   }
205 }
206 
207 
index_next(uchar * buf)208 int ha_seq::index_next(uchar *buf)
209 {
210   if (cur == seqs->to)
211     return HA_ERR_END_OF_FILE;
212   set(buf);
213   cur+= seqs->step;
214   return 0;
215 }
216 
217 
index_prev(uchar * buf)218 int ha_seq::index_prev(uchar *buf)
219 {
220   if (cur == seqs->from)
221     return HA_ERR_END_OF_FILE;
222   cur-= seqs->step;
223   set(buf);
224   return 0;
225 }
226 
227 
index_first(uchar * buf)228 int ha_seq::index_first(uchar *buf)
229 {
230   cur= seqs->from;
231   return index_next(buf);
232 }
233 
234 
index_last(uchar * buf)235 int ha_seq::index_last(uchar *buf)
236 {
237   cur= seqs->to;
238   return index_prev(buf);
239 }
240 
records_in_range(uint inx,const key_range * min_key,const key_range * max_key,page_range * pages)241 ha_rows ha_seq::records_in_range(uint inx, const key_range *min_key,
242                                  const key_range *max_key,
243                                  page_range *pages)
244 {
245   ulonglong kmin= min_key ? uint8korr(min_key->key) : seqs->from;
246   ulonglong kmax= max_key ? uint8korr(max_key->key) : seqs->to - 1;
247   if (kmin >= seqs->to || kmax < seqs->from || kmin > kmax)
248     return 0;
249   return (kmax - seqs->from) / seqs->step -
250          (kmin - seqs->from + seqs->step - 1) / seqs->step + 1;
251 }
252 
253 
open(const char * name,int mode,uint test_if_locked)254 int ha_seq::open(const char *name, int mode, uint test_if_locked)
255 {
256   if (!(seqs= get_share()))
257     return HA_ERR_OUT_OF_MEM;
258   DBUG_ASSERT(my_strcasecmp(table_alias_charset, name, seqs->name) == 0);
259 
260   ref_length= sizeof(cur);
261   thr_lock_data_init(&seqs->lock,&lock,NULL);
262   return 0;
263 }
264 
close(void)265 int ha_seq::close(void)
266 {
267   return 0;
268 }
269 
create_handler(handlerton * hton,TABLE_SHARE * table,MEM_ROOT * mem_root)270 static handler *create_handler(handlerton *hton, TABLE_SHARE *table,
271                                MEM_ROOT *mem_root)
272 {
273   return new (mem_root) ha_seq(hton, table);
274 }
275 
276 
parse_table_name(const char * name,size_t name_length,ulonglong * from,ulonglong * to,ulonglong * step)277 static bool parse_table_name(const char *name, size_t name_length,
278                              ulonglong *from, ulonglong *to, ulonglong *step)
279 {
280   uint n0=0, n1= 0, n2= 0;
281   *step= 1;
282 
283   // the table is discovered if its name matches the pattern of seq_1_to_10 or
284   // seq_1_to_10_step_3
285   sscanf(name, "seq_%llu_to_%n%llu%n_step_%llu%n",
286          from, &n0, to, &n1, step, &n2);
287   // I consider this a bug in sscanf() - when an unsigned number
288   // is requested, -5 should *not* be accepted. But is is :(
289   // hence the additional check below:
290   return
291     n0 == 0 || !isdigit(name[4]) || !isdigit(name[n0]) || // reject negative numbers
292     (n1 != name_length && n2 != name_length);
293 }
294 
295 
get_share()296 Sequence_share *ha_seq::get_share()
297 {
298   Sequence_share *tmp_share;
299   lock_shared_ha_data();
300   if (!(tmp_share= static_cast<Sequence_share*>(get_ha_share_ptr())))
301   {
302     bool reverse;
303     ulonglong from, to, step;
304 
305     parse_table_name(table_share->table_name.str,
306                      table_share->table_name.length, &from, &to, &step);
307 
308     if ((reverse = from > to))
309     {
310       if (step > from - to)
311         to = from;
312       else
313         swap_variables(ulonglong, from, to);
314       /*
315         when keyread is allowed, optimizer will always prefer an index to a
316         table scan for our tables, and we'll never see the range reversed.
317       */
318       table_share->keys_for_keyread.clear_all();
319     }
320 
321     to= (to - from) / step * step + step + from;
322 
323     tmp_share= new Sequence_share(table_share->normalized_path.str, from, to, step, reverse);
324 
325     if (!tmp_share)
326       goto err;
327     set_ha_share_ptr(static_cast<Handler_share*>(tmp_share));
328   }
329 err:
330   unlock_shared_ha_data();
331   return tmp_share;
332 }
333 
334 
discover_table(handlerton * hton,THD * thd,TABLE_SHARE * share)335 static int discover_table(handlerton *hton, THD *thd, TABLE_SHARE *share)
336 {
337   ulonglong from, to, step;
338   if (parse_table_name(share->table_name.str, share->table_name.length,
339                        &from, &to, &step))
340     return HA_ERR_NO_SUCH_TABLE;
341 
342   if (step == 0)
343     return HA_WRONG_CREATE_OPTION;
344 
345   const char *sql="create table seq (seq bigint unsigned primary key)";
346   return share->init_from_sql_statement_string(thd, 0, sql, strlen(sql));
347 }
348 
349 
discover_table_existence(handlerton * hton,const char * db,const char * table_name)350 static int discover_table_existence(handlerton *hton, const char *db,
351                                     const char *table_name)
352 {
353   ulonglong from, to, step;
354   return !parse_table_name(table_name, strlen(table_name), &from, &to, &step);
355 }
356 
dummy_commit_rollback(handlerton *,THD *,bool)357 static int dummy_commit_rollback(handlerton *, THD *, bool) { return 0; }
358 
dummy_savepoint(handlerton *,THD *,void *)359 static int dummy_savepoint(handlerton *, THD *, void *) { return 0; }
360 
361 /*****************************************************************************
362   Example of a simple group by handler for queries like:
363   SELECT SUM(seq) from sequence_table;
364 
365   This implementation supports SUM() and COUNT() on primary key.
366 *****************************************************************************/
367 
368 class ha_seq_group_by_handler: public group_by_handler
369 {
370   Select_limit_counters limit;
371   List<Item> *fields;
372   TABLE_LIST *table_list;
373   bool first_row;
374 
375 public:
ha_seq_group_by_handler(THD * thd_arg,List<Item> * fields_arg,TABLE_LIST * table_list_arg,Select_limit_counters * orig_lim)376   ha_seq_group_by_handler(THD *thd_arg, List<Item> *fields_arg,
377                           TABLE_LIST *table_list_arg,
378                           Select_limit_counters *orig_lim)
379     : group_by_handler(thd_arg, sequence_hton),  limit(orig_lim[0]),
380       fields(fields_arg), table_list(table_list_arg)
381     {
382       // Reset limit because we are handling it now
383       orig_lim->set_unlimited();
384     }
~ha_seq_group_by_handler()385   ~ha_seq_group_by_handler() {}
init_scan()386   int init_scan() { first_row= 1 ; return 0; }
387   int next_row();
end_scan()388   int end_scan()  { return 0; }
389 };
390 
391 static group_by_handler *
create_group_by_handler(THD * thd,Query * query)392 create_group_by_handler(THD *thd, Query *query)
393 {
394   ha_seq_group_by_handler *handler;
395   Item *item;
396   List_iterator_fast<Item> it(*query->select);
397 
398   /* check that only one table is used in FROM clause and no sub queries */
399   if (query->from->next_local != 0)
400     return 0;
401   /* check that there is no where clause and no group_by */
402   if (query->where != 0 || query->group_by != 0)
403     return 0;
404 
405   /*
406     Check that all fields are sum(primary_key) or count(primary_key)
407     For more ways to work with the field list and sum functions, see
408     opt_sum.cc::opt_sum_query().
409   */
410   while ((item= it++))
411   {
412     Item *arg0;
413     Field *field;
414     if (item->type() != Item::SUM_FUNC_ITEM ||
415         (((Item_sum*) item)->sum_func() != Item_sum::SUM_FUNC &&
416          ((Item_sum*) item)->sum_func() != Item_sum::COUNT_FUNC))
417 
418       return 0;                                  // Not a SUM() function
419     arg0= ((Item_sum*) item)->get_arg(0);
420     if (arg0->type() != Item::FIELD_ITEM)
421     {
422       if ((((Item_sum*) item)->sum_func() == Item_sum::COUNT_FUNC) &&
423           arg0->basic_const_item())
424         continue;                               // Allow count(1)
425       return 0;
426     }
427     field= ((Item_field*) arg0)->field;
428     /*
429       Check that we are using the sequence table (the only table in the FROM
430       clause) and not an outer table.
431     */
432     if (field->table != query->from->table)
433       return 0;
434     /* Check that we are using a SUM() on the primary key */
435     if (strcmp(field->field_name.str, "seq"))
436       return 0;
437   }
438 
439   /* Create handler and return it */
440   handler= new ha_seq_group_by_handler(thd, query->select, query->from,
441                                        query->limit);
442   return handler;
443 }
444 
next_row()445 int ha_seq_group_by_handler::next_row()
446 {
447   List_iterator_fast<Item> it(*fields);
448   Item_sum *item_sum;
449   Sequence_share *seqs= ((ha_seq*) table_list->table->file)->seqs;
450   DBUG_ENTER("ha_seq_group_by_handler::next_row");
451 
452   /*
453     Check if this is the first call to the function. If not, we have already
454     returned all data.
455   */
456   if (!first_row ||
457       limit.get_offset_limit() > 0 ||
458       limit.get_select_limit() == 0)
459     DBUG_RETURN(HA_ERR_END_OF_FILE);
460   first_row= 0;
461 
462   /* Pointer to first field in temporary table where we should store summary*/
463   Field **field_ptr= table->field;
464   ulonglong elements= (seqs->to - seqs->from + seqs->step - 1) / seqs->step;
465 
466   while ((item_sum= (Item_sum*) it++))
467   {
468     Field *field= *(field_ptr++);
469     switch (item_sum->sum_func()) {
470     case Item_sum::COUNT_FUNC:
471     {
472       Item *arg0= ((Item_sum*) item_sum)->get_arg(0);
473       if (arg0->basic_const_item() && arg0->is_null())
474         field->store(0LL, 1);
475       else
476         field->store((longlong) elements, 1);
477       break;
478     }
479     case Item_sum::SUM_FUNC:
480     {
481       /* Calculate SUM(f, f+step, f+step*2 ... to) */
482       ulonglong sum;
483       sum= seqs->from * elements + seqs->step * (elements*elements-elements)/2;
484       field->store((longlong) sum, 1);
485       break;
486     }
487     default:
488       DBUG_ASSERT(0);
489     }
490     field->set_notnull();
491   }
492   DBUG_RETURN(0);
493 }
494 
495 
496 /*****************************************************************************
497   Initialize the interface between the sequence engine and MariaDB
498 *****************************************************************************/
499 
drop_table(handlerton * hton,const char * path)500 static int drop_table(handlerton *hton, const char *path)
501 {
502   const char *name= strrchr(path, FN_LIBCHAR)+1;
503   ulonglong from, to, step;
504   if (parse_table_name(name, strlen(name), &from, &to, &step))
505     return ENOENT;
506   return 0;
507 }
508 
init(void * p)509 static int init(void *p)
510 {
511   handlerton *hton= (handlerton *)p;
512   sequence_hton= hton;
513   hton->create= create_handler;
514   hton->drop_table= drop_table;
515   hton->discover_table= discover_table;
516   hton->discover_table_existence= discover_table_existence;
517   hton->commit= hton->rollback= dummy_commit_rollback;
518   hton->savepoint_set= hton->savepoint_rollback= hton->savepoint_release=
519     dummy_savepoint;
520   hton->create_group_by= create_group_by_handler;
521   return 0;
522 }
523 
524 static struct st_mysql_storage_engine descriptor =
525 { MYSQL_HANDLERTON_INTERFACE_VERSION };
526 
maria_declare_plugin(sequence)527 maria_declare_plugin(sequence)
528 {
529   MYSQL_STORAGE_ENGINE_PLUGIN,
530   &descriptor,
531   "SEQUENCE",
532   "Sergei Golubchik",
533   "Generated tables filled with sequential values",
534   PLUGIN_LICENSE_GPL,
535   init,
536   NULL,
537   0x0100,
538   NULL,
539   NULL,
540   "0.1",
541   MariaDB_PLUGIN_MATURITY_STABLE
542 }
543 maria_declare_plugin_end;
544