1 /*
2    Copyright (c) 2017, Aliyun and/or its affiliates.
3    Copyright (c) 2017, 2020, 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)
44 handlerton *sql_sequence_hton;
45 
46 /*
47   Create a sequence handler
48 */
49 
ha_sequence(handlerton * hton,TABLE_SHARE * share)50 ha_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 */
~ha_sequence()60 ha_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 
open(const char * name,int mode,uint flags)77 int 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 initial 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 if (!table->s->tmp_table)
118       table->internal_set_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 
clone(const char * name,MEM_ROOT * mem_root)134 handler *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 
163 err:
164   delete new_handler;
165   DBUG_RETURN(NULL);
166 }
167 
168 
169 /*
170   Map the create table to the original storage engine
171 */
172 
create(const char * name,TABLE * form,HA_CREATE_INFO * create_info)173 int 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 
write_row(uchar * buf)197 int 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 
table_flags() const280 handler::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 
info(uint flag)288 int 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 
extra(enum ha_extra_function operation)299 int 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 
check_if_incompatible_data(HA_CREATE_INFO * create_info,uint table_changes)309 bool 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 
external_lock(THD * thd,int lock_type)317 int 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 
print_error(int error,myf errflag)334 void 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 
sequence_create_handler(handlerton * hton,TABLE_SHARE * share,MEM_ROOT * mem_root)371 static 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 */
sequence_end(handlerton * hton,ha_panic_function type)391 static 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 
sequence_initialize(void * p)411 static 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 
429 static struct st_mysql_storage_engine sequence_storage_engine=
430 { MYSQL_HANDLERTON_INTERFACE_VERSION };
431 
maria_declare_plugin(sql_sequence)432 maria_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_STABLE /* maturity                     */
447 }
448 maria_declare_plugin_end;
449