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