1/*
2 Copyright (c) 2017, Aliyun and/or its affiliates.
3 Copyright (c) 2017, MariaDB corporation
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; version 2 of 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#include "mariadb.h"
20#include "sql_list.h"
21#include "table.h"
22#include "sql_sequence.h"
23#include "ha_sequence.h"
24#include "sql_plugin.h"
25#include "mysql/plugin.h"
26#include "sql_priv.h"
27#include "sql_parse.h"
28#include "sql_table.h"
29#include "sql_update.h"
30#include "sql_base.h"
31#include "log_event.h"
32
33/*
34 Table flags we should inherit and disable from the original engine.
35 We add HA_STATS_RECORDS_IS_EXACT as ha_sequence::info() will ensure
36 that records is always 1
37*/
38
39#define SEQUENCE_ENABLED_TABLE_FLAGS (HA_STATS_RECORDS_IS_EXACT | \
40 HA_PERSISTENT_TABLE)
41#define SEQUENCE_DISABLED_TABLE_FLAGS (HA_CAN_SQL_HANDLER | \
42 HA_CAN_INSERT_DELAYED | \
43 HA_BINLOG_STMT_CAPABLE)
44handlerton *sql_sequence_hton;
45
46/*
47 Create a sequence handler
48*/
49
50ha_sequence::ha_sequence(handlerton *hton, TABLE_SHARE *share)
51 :handler(hton, share), write_locked(0)
52{
53 sequence= share->sequence;
54 DBUG_ASSERT(share->sequence);
55}
56
57/**
58 Destructor method must remove the underlying handler
59*/
60ha_sequence::~ha_sequence()
61{
62 delete file;
63}
64
65/**
66 Sequence table open method
67
68 @param name Path to file (dbname and tablename)
69 @param mode mode
70 @param flags Flags how to open file
71
72 RETURN VALUES
73 @retval 0 Success
74 @retval != 0 Failure
75*/
76
77int ha_sequence::open(const char *name, int mode, uint flags)
78{
79 int error;
80 DBUG_ENTER("ha_sequence::open");
81 DBUG_ASSERT(table->s == table_share && file);
82
83 file->table= table;
84 if (likely(!(error= file->open(name, mode, flags))))
85 {
86 /*
87 Allocate ref in table's mem_root. We can't use table's ref
88 as it's allocated by ha_ caller that allocates this.
89 */
90 ref_length= file->ref_length;
91 if (!(ref= (uchar*) alloc_root(&table->mem_root,ALIGN_SIZE(ref_length)*2)))
92 {
93 file->ha_close();
94 error=HA_ERR_OUT_OF_MEM;
95 DBUG_RETURN(error);
96 }
97 file->ref= ref;
98 file->dup_ref= dup_ref= ref+ALIGN_SIZE(file->ref_length);
99
100 /*
101 ha_open() sets the following for us. We have to set this for the
102 underlying handler
103 */
104 file->cached_table_flags= file->table_flags();
105
106 file->reset_statistics();
107 internal_tmp_table= file->internal_tmp_table=
108 MY_TEST(flags & HA_OPEN_INTERNAL_TABLE);
109 reset_statistics();
110
111 /* Don't try to read the inital row the call is part of create code */
112 if (!(flags & (HA_OPEN_FOR_CREATE | HA_OPEN_FOR_REPAIR)))
113 {
114 if (unlikely((error= table->s->sequence->read_initial_values(table))))
115 file->ha_close();
116 }
117 else
118 table->m_needs_reopen= true;
119
120 /*
121 The following is needed to fix comparison of rows in
122 ha_update_first_row() for InnoDB
123 */
124 memcpy(table->record[1], table->s->default_values, table->s->reclength);
125 }
126 DBUG_RETURN(error);
127}
128
129/*
130 Clone the sequence. Needed if table is used by range optimization
131 (Very, very unlikely)
132*/
133
134handler *ha_sequence::clone(const char *name, MEM_ROOT *mem_root)
135{
136 ha_sequence *new_handler;
137 DBUG_ENTER("ha_sequence::clone");
138 if (!(new_handler= new (mem_root) ha_sequence(ht, table_share)))
139 DBUG_RETURN(NULL);
140
141 /*
142 Allocate new_handler->ref here because otherwise ha_open will allocate it
143 on this->table->mem_root and we will not be able to reclaim that memory
144 when the clone handler object is destroyed.
145 */
146
147 if (!(new_handler->ref= (uchar*) alloc_root(mem_root,
148 ALIGN_SIZE(ref_length)*2)))
149 goto err;
150
151 if (new_handler->ha_open(table, name,
152 table->db_stat,
153 HA_OPEN_IGNORE_IF_LOCKED | HA_OPEN_NO_PSI_CALL))
154 goto err;
155
156 /* Reuse original storage engine data for duplicate key reference */
157 new_handler->ref= file->ref;
158 new_handler->ref_length= file->ref_length;
159 new_handler->dup_ref= file->dup_ref;
160
161 DBUG_RETURN((handler*) new_handler);
162
163err:
164 delete new_handler;
165 DBUG_RETURN(NULL);
166}
167
168
169/*
170 Map the create table to the original storage engine
171*/
172
173int ha_sequence::create(const char *name, TABLE *form,
174 HA_CREATE_INFO *create_info)
175{
176 DBUG_ASSERT(create_info->sequence);
177 /* Sequence tables has one and only one row */
178 create_info->max_rows= create_info->min_rows= 1;
179 return (file->create(name, form, create_info));
180}
181
182/**
183 Sequence write row method.
184
185 A sequence table has only one row. Any inserts in the table
186 will update this row.
187
188 @retval 0 Success
189 @retval != 0 Failure
190
191 NOTES:
192 write_locked is set if we are called from SEQUENCE::next_value
193 In this case the mutex is already locked and we should not update
194 the sequence with 'buf' as the sequence object is already up to date.
195*/
196
197int ha_sequence::write_row(uchar *buf)
198{
199 int error;
200 sequence_definition tmp_seq;
201 bool sequence_locked;
202 DBUG_ENTER("ha_sequence::write_row");
203 DBUG_ASSERT(table->record[0] == buf);
204
205 row_already_logged= 0;
206 if (unlikely(sequence->initialized == SEQUENCE::SEQ_IN_PREPARE))
207 {
208 /* This calls is from ha_open() as part of create table */
209 DBUG_RETURN(file->write_row(buf));
210 }
211 if (unlikely(sequence->initialized == SEQUENCE::SEQ_IN_ALTER))
212 {
213 int error= 0;
214 /* This is called from alter table */
215 tmp_seq.read_fields(table);
216 if (tmp_seq.check_and_adjust(0))
217 DBUG_RETURN(HA_ERR_SEQUENCE_INVALID_DATA);
218 sequence->copy(&tmp_seq);
219 if (likely(!(error= file->write_row(buf))))
220 sequence->initialized= SEQUENCE::SEQ_READY_TO_USE;
221 DBUG_RETURN(error);
222 }
223 if (unlikely(sequence->initialized != SEQUENCE::SEQ_READY_TO_USE))
224 DBUG_RETURN(HA_ERR_WRONG_COMMAND);
225
226 sequence_locked= write_locked;
227 if (!write_locked) // If not from next_value()
228 {
229 /*
230 User tries to write a full row directly to the sequence table with
231 INSERT or LOAD DATA.
232
233 - Get an exclusive lock for the table. This is needed to ensure that
234 we excute all full inserts (same as ALTER SEQUENCE) in same order
235 on master and slaves
236 - Check that the new row is an accurate SEQUENCE object
237 */
238
239 THD *thd= table->in_use;
240 if (table->s->tmp_table == NO_TMP_TABLE &&
241 thd->mdl_context.upgrade_shared_lock(table->mdl_ticket,
242 MDL_EXCLUSIVE,
243 thd->variables.
244 lock_wait_timeout))
245 DBUG_RETURN(ER_LOCK_WAIT_TIMEOUT);
246
247 tmp_seq.read_fields(table);
248 if (tmp_seq.check_and_adjust(0))
249 DBUG_RETURN(HA_ERR_SEQUENCE_INVALID_DATA);
250
251 /*
252 Lock sequence to ensure that no one can come in between
253 while sequence, table and binary log are updated.
254 */
255 sequence->write_lock(table);
256 }
257
258 if (likely(!(error= file->update_first_row(buf))))
259 {
260 Log_func *log_func= Write_rows_log_event::binlog_row_logging_function;
261 if (!sequence_locked)
262 sequence->copy(&tmp_seq);
263 rows_changed++;
264 /* We have to do the logging while we hold the sequence mutex */
265 error= binlog_log_row(table, 0, buf, log_func);
266 row_already_logged= 1;
267 }
268
269 sequence->all_values_used= 0;
270 if (!sequence_locked)
271 sequence->write_unlock(table);
272 DBUG_RETURN(error);
273}
274
275
276/*
277 Inherit the sequence base table flags.
278*/
279
280handler::Table_flags ha_sequence::table_flags() const
281{
282 DBUG_ENTER("ha_sequence::table_flags");
283 DBUG_RETURN((file->table_flags() & ~SEQUENCE_DISABLED_TABLE_FLAGS) |
284 SEQUENCE_ENABLED_TABLE_FLAGS);
285}
286
287
288int ha_sequence::info(uint flag)
289{
290 DBUG_ENTER("ha_sequence::info");
291 file->info(flag);
292 /* Inform optimizer that we have always only one record */
293 stats= file->stats;
294 stats.records= 1;
295 DBUG_RETURN(false);
296}
297
298
299int ha_sequence::extra(enum ha_extra_function operation)
300{
301 if (operation == HA_EXTRA_PREPARE_FOR_ALTER_TABLE)
302 {
303 /* In case of ALTER TABLE allow ::write_row() to copy rows */
304 sequence->initialized= SEQUENCE::SEQ_IN_ALTER;
305 }
306 return file->extra(operation);
307}
308
309bool ha_sequence::check_if_incompatible_data(HA_CREATE_INFO *create_info,
310 uint table_changes)
311{
312 /* Table definition is locked for SEQUENCE tables */
313 return(COMPATIBLE_DATA_YES);
314}
315
316
317int ha_sequence::external_lock(THD *thd, int lock_type)
318{
319 int error= file->external_lock(thd, lock_type);
320
321 /*
322 Copy lock flag to satisfy DBUG_ASSERT checks in ha_* functions in
323 handler.cc when we later call it with file->ha_..()
324 */
325 if (!error)
326 file->m_lock_type= lock_type;
327 return error;
328}
329
330/*
331 Squence engine error deal method
332*/
333
334void ha_sequence::print_error(int error, myf errflag)
335{
336 const char *sequence_db= table_share->db.str;
337 const char *sequence_name= table_share->table_name.str;
338 DBUG_ENTER("ha_sequence::print_error");
339
340 switch (error) {
341 case HA_ERR_SEQUENCE_INVALID_DATA:
342 {
343 my_error(ER_SEQUENCE_INVALID_DATA, MYF(errflag), sequence_db,
344 sequence_name);
345 DBUG_VOID_RETURN;
346 }
347 case HA_ERR_SEQUENCE_RUN_OUT:
348 {
349 my_error(ER_SEQUENCE_RUN_OUT, MYF(errflag), sequence_db, sequence_name);
350 DBUG_VOID_RETURN;
351 }
352 case HA_ERR_WRONG_COMMAND:
353 my_error(ER_ILLEGAL_HA, MYF(0), "SEQUENCE", sequence_db, sequence_name);
354 DBUG_VOID_RETURN;
355 case ER_WRONG_INSERT_INTO_SEQUENCE:
356 my_error(error, MYF(0));
357 DBUG_VOID_RETURN;
358 }
359 file->print_error(error, errflag);
360 DBUG_VOID_RETURN;
361}
362
363/*****************************************************************************
364 Sequence plugin interface
365*****************************************************************************/
366
367/*
368 Create an new handler
369*/
370
371static handler *sequence_create_handler(handlerton *hton,
372 TABLE_SHARE *share,
373 MEM_ROOT *mem_root)
374{
375 DBUG_ENTER("sequence_create_handler");
376 DBUG_RETURN(new (mem_root) ha_sequence(hton, share));
377}
378
379
380/*
381 Sequence engine end.
382
383 SYNOPSIS
384 sequence_end()
385 p handlerton.
386 type panic type.
387 RETURN VALUES
388 0 Success
389 !=0 Failure
390*/
391static int sequence_end(handlerton* hton,
392 ha_panic_function type __attribute__((unused)))
393{
394 DBUG_ENTER("sequence_end");
395 DBUG_RETURN(0);
396}
397
398
399/*
400 Sequence engine init.
401
402 SYNOPSIS
403 sequence_initialize()
404
405 @param p handlerton.
406
407 retval 0 Success
408 retval !=0 Failure
409*/
410
411static int sequence_initialize(void *p)
412{
413 handlerton *local_sequence_hton= (handlerton *)p;
414 DBUG_ENTER("sequence_initialize");
415
416 local_sequence_hton->state= SHOW_OPTION_YES;
417 local_sequence_hton->db_type= DB_TYPE_SEQUENCE;
418 local_sequence_hton->create= sequence_create_handler;
419 local_sequence_hton->panic= sequence_end;
420 local_sequence_hton->flags= (HTON_NOT_USER_SELECTABLE |
421 HTON_HIDDEN |
422 HTON_TEMPORARY_NOT_SUPPORTED |
423 HTON_ALTER_NOT_SUPPORTED |
424 HTON_NO_PARTITION);
425 DBUG_RETURN(0);
426}
427
428
429static struct st_mysql_storage_engine sequence_storage_engine=
430{ MYSQL_HANDLERTON_INTERFACE_VERSION };
431
432maria_declare_plugin(sql_sequence)
433{
434 MYSQL_STORAGE_ENGINE_PLUGIN,
435 &sequence_storage_engine,
436 "SQL_SEQUENCE",
437 "jianwei.zhao @ Aliyun & Monty @ MariaDB corp",
438 "Sequence Storage Engine for CREATE SEQUENCE",
439 PLUGIN_LICENSE_GPL,
440 sequence_initialize, /* Plugin Init */
441 NULL, /* Plugin Deinit */
442 0x0100, /* 1.0 */
443 NULL, /* status variables */
444 NULL, /* system variables */
445 "1.0", /* string version */
446 MariaDB_PLUGIN_MATURITY_ALPHA /* maturity */
447}
448maria_declare_plugin_end;
449