| 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) | 
| 44 | handlerton *sql_sequence_hton; | 
| 45 |  | 
| 46 | /* | 
| 47 |   Create a sequence handler | 
| 48 | */ | 
| 49 |  | 
| 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 | */ | 
| 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 |  | 
| 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 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 |  | 
| 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 |  | 
| 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 |  | 
| 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 |  | 
| 280 | 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 |  | 
| 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 |  | 
| 299 | int ha_sequence::(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 |  | 
| 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 |  | 
| 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 |  | 
| 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 |  | 
| 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 | */ | 
| 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 |  | 
| 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 |  | 
| 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_ALPHA /* maturity                     */ | 
| 447 | } | 
| 448 | maria_declare_plugin_end; | 
| 449 |  |