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