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