polardbxengine/sql/sql_sequence.cc

463 lines
14 KiB
C++
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL/PolarDB-X Engine hereby grant you an
additional permission to link the program and your derivative works with the
separately licensed software that they have included with
MySQL/PolarDB-X Engine.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
/**
@file
Implementation of SEQUENCE object
CREATE SEQUENCE syntax:
  CREATE SEQUENCE [IF NOT EXISTS] schema.seqName
   [START WITH <constant>]
   [MINVALUE <constant>]
   [MAXVALUE <constant>]
   [INCREMENT BY <constant>]
   [CACHE <constant> | NOCACHE]
   [CYCLE | NOCYCLE]
  ;
Or:
CREATE TABLE `s` (
`currval` bigint(21) NOT NULL COMMENT 'current value',
`nextval` bigint(21) NOT NULL COMMENT 'next value',
`minvalue` bigint(21) NOT NULL COMMENT 'min value',
`maxvalue` bigint(21) NOT NULL COMMENT 'max value',
`start` bigint(21) NOT NULL COMMENT 'start value',
`increment` bigint(21) NOT NULL COMMENT 'increment value',
`cache` bigint(21) NOT NULL COMMENT 'cache size',
`cycle` bigint(21) NOT NULL COMMENT 'cycle state',
`round` bigint(21) NOT NULL COMMENT 'already how many round'
) ENGINE=Sequence DEFAULT CHARSET=latin1
insert into s values(0,0,1,10,1,1,2,0,0);
commit;
SHOW syntax:
  SHOW CREATE TABLE schema.seqName;
QUERY syntax:   
1.   
  SELECT [nextval | currval | *] FROM schema.seqName;
2. TODO: 
  SELECT seqName.nextval from dual;
  SELECT seqName.currval from dual;
3.
SELECT NEXTVAL(seq);
SELECT CURRVAL(seq);
Usage:
  use test; create sequence s;
  create table t(id int);
  insert into t values (nextval(s))
*/
/**
@addtogroup Sequence Engine
Sequence Engine syntax statement implementation.
@{
*/
#include <limits>
#include "sql/dd/types/abstract_table.h"
#include "sql/derror.h" // ER_THD
#include "sql/field.h"
#include "sql/parse_tree_column_attrs.h"
#include "sql/sql_base.h"
#include "sql/sql_class.h"
#include "sql/sql_table.h"
#include "sql/transaction.h"
#include "sql/sequence_common.h"
#include "sql/sql_sequence.h"
/**
Prepare sequence base engine.
@param[in] thd Connection context
@param[in] table TABLE_LIST object
@retval false success
@retval true failure
*/
bool PT_create_sequence_stmt::prepare_sequence_engine(const THD *thd,
const TABLE_LIST *table) {
DBUG_ENTER("PT_create_sequence_stmt::prepare_sequence_engine");
/* Step 1: prepare the db and table_name */
m_sequence_info.db = table->db;
m_sequence_info.table_name = table->table_name;
/* Step 2: prepare the base engine */
plugin_ref sequence_plugin = ha_resolve_sequence(thd);
plugin_ref base_plugin = ha_resolve_sequence_base(thd);
DBUG_EXECUTE_IF("sequence_engine_error", { sequence_plugin = NULL; });
if (sequence_plugin == nullptr ||
plugin_data<handlerton *>(sequence_plugin) == nullptr) {
my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), SEQUENCE_ENGINE_NAME.str);
DBUG_RETURN(true);
}
/* We will change the create_info.db_type when create_table_impl() */
if (!((m_create_info.db_type = m_sequence_info.base_db_type =
(base_plugin != nullptr ? plugin_data<handlerton *>(base_plugin)
: nullptr)))) {
my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), SEQUENCE_BASE_ENGINE_NAME.str);
DBUG_RETURN(true);
}
m_create_info.used_fields |= HA_CREATE_USED_ENGINE;
DBUG_RETURN(false);
}
/**
Prepare and check sequence table columns
@param[in] thd Connection context
@retval false success
@retval true failure
*/
bool PT_create_sequence_stmt::prepare_sequence_fields(const THD *thd) {
const st_sequence_field_info *field_def;
MEM_ROOT *mem_root;
LEX_STRING field_name;
size_t field_name_len;
PT_column_attr_base *not_null_attr;
PT_column_attr_base *comment_attr;
Mem_root_array<PT_column_attr_base *> *column_attrs;
PT_type *field_type;
PT_field_def_base *field_def_base;
PT_table_element *table_element;
Mem_root_array<PT_table_element *> *table_element_list;
DBUG_ENTER("PT_create_sequence_stmt::prepare_sequence_fields");
DBUG_ASSERT(opt_table_element_list == NULL);
/**
Columns definition structure:
1.-- PT_table_element list
2.-- PT_column_def
3.-- Field_name (LEX_STRING)
3.-- PT_field_def
4.-- PT_numeric_type
4.-- PT_column_attr_base list
5.-- PT_not_null_column_attr
5.-- PT_comment_column_attr
*/
field_def = seq_fields;
mem_root = thd->mem_root;
/* Native sequence create syntax */
table_element_list =
new (mem_root) Mem_root_array<PT_table_element *>(mem_root);
while (field_def->field_name) {
/* Column attrs include not_null and comment */
column_attrs =
new (mem_root) Mem_root_array<PT_column_attr_base *>(mem_root);
comment_attr = new (mem_root) PT_comment_column_attr(field_def->comment);
not_null_attr = new (mem_root) PT_not_null_column_attr;
column_attrs->push_back(not_null_attr);
column_attrs->push_back(comment_attr);
/* Column type is bigint(21) */
field_type = new (mem_root) PT_numeric_type(const_cast<THD *>(thd),
Int_type::BIGINT, field_def->field_length, 0);
field_def_base = new (mem_root) PT_field_def(field_type, column_attrs);
field_name_len = strlen(field_def->field_name);
field_name.str = thd->strmake(field_def->field_name, field_name_len);
field_name.length = field_name_len;
/* Column def and column name constitute column element */
table_element =
new (mem_root) PT_column_def(field_name, field_def_base, NULL);
table_element_list->push_back(table_element);
field_def++;
}
/* Assign the PT_create_table_stmt attribute */
opt_table_element_list = table_element_list;
DBUG_RETURN(false);
}
/**
Check the fields whether they are consistent with pre-defined.
@param[in] alter_info All the DDL information
@retval false success
@retval true failure
*/
bool PT_create_sequence_stmt::check_sequence_fields(
Alter_info *alter_info) const {
bool error = false;
DBUG_ENTER("PT_create_sequence_stmt::check_sequence_fields");
if ((error = check_sequence_fields_valid(alter_info)))
my_error(ER_SEQUENCE_INVALID, MYF(0), m_sequence_info.db,
m_sequence_info.table_name);
DBUG_RETURN(error);
}
/**
CREATE SEQUENCE statement command
@param[in] thd Connection context
@retval Sql_cmd SQL command
*/
Sql_cmd *PT_create_sequence_stmt::make_cmd(THD *thd) {
LEX *const lex = thd->lex;
lex->sql_command = SQLCOM_CREATE_TABLE;
Parse_context pc(thd, lex->current_select());
TABLE_LIST *table = pc.select->add_table_to_list(
thd, table_name, NULL, TL_OPTION_UPDATING, TL_WRITE, MDL_SHARED);
if (table == NULL) return NULL;
table->open_strategy = TABLE_LIST::OPEN_FOR_CREATE;
/* Step 1: prepare sequence engine. */
if (prepare_sequence_engine(thd, table)) return NULL;
/* Step 2: prepare sequence table columns */
if (prepare_sequence_fields(thd)) return NULL;
lex->create_info = &m_create_info;
lex->sequence_info = &m_sequence_info;
lex->create_info->sequence_info = &m_sequence_info;
Table_ddl_parse_context pc2(thd, pc.select, &m_alter_info);
pc2.create_info->options = 0;
if (only_if_not_exists)
pc2.create_info->options |= HA_LEX_CREATE_IF_NOT_EXISTS;
pc2.create_info->default_table_charset = NULL;
lex->name.str = 0;
lex->name.length = 0;
/* Step 3: Contextualize sequence row values */
if (opt_create_sequence_options) {
for (auto option : *opt_create_sequence_options)
if (option->contextualize(&pc2)) return NULL;
}
/* Step 4: check sequence row values */
if (pc2.sequence_info->check_valid()) return NULL;
/* Step 5: Contextualize sequence columns */
TABLE_LIST *qe_tables = nullptr;
if (opt_table_element_list) {
for (auto element : *opt_table_element_list) {
if (element->contextualize(&pc2)) return NULL;
}
}
if (check_sequence_fields(&m_alter_info)) return NULL;
switch (on_duplicate) {
case On_duplicate::IGNORE_DUP:
lex->set_ignore(true);
break;
case On_duplicate::REPLACE_DUP:
lex->duplicates = DUP_REPLACE;
break;
case On_duplicate::ERROR:
lex->duplicates = DUP_ERROR;
break;
}
DBUG_ASSERT(opt_query_expression == NULL);
lex->set_current_select(pc.select);
DBUG_ASSERT((pc2.create_info->used_fields & HA_CREATE_USED_ENGINE) &&
pc2.create_info->db_type);
create_table_set_open_action_and_adjust_tables(lex);
thd->lex->alter_info = &m_alter_info;
return new (thd->mem_root)
Sql_cmd_create_sequence(&m_alter_info, qe_tables, &m_sequence_info);
}
/**
Disable reprepare_observer temporarily.
*/
class Disable_reprepare_observer {
public:
Disable_reprepare_observer(THD *thd) : m_thd(thd) {
m_saved = m_thd->get_reprepare_observer();
if (m_saved) {
m_thd->pop_reprepare_observer();
/* One at most */
DBUG_ASSERT(m_thd->get_reprepare_observer() == nullptr);
}
}
~Disable_reprepare_observer() {
if (m_saved) {
m_thd->push_reprepare_observer(m_saved);
}
}
private:
THD *m_thd;
Reprepare_observer *m_saved;
};
/**
Create the sequence table and insert a row into table.
@param[in] thd User connection context
@retval false Success
@retval true Failure
*/
bool Sql_cmd_create_sequence::execute(THD *thd) {
DBUG_ENTER("Sql_cmd_create_sequence::execute");
DBUG_ASSERT(thd->lex->sequence_info);
/**
When after sequence being created, we insert a row into the table.
The insertion progress will open the sequence table, of cause the
version of the table share is always different from of the TABLE_LIST
during a table creation.
In non-prepare mode, mysql considers this as expected and ingores it.
But in prepared execution, a reprepare_observer installed and the observer
will report an error.
Actuallly, we can ignore the difference between the share and TABLE_LIST
during the sequence creation.
*/
Disable_reprepare_observer disable_ob(thd);
DBUG_RETURN(super::execute(thd));
}
bool Sql_cmd_create_sequence::prepare(THD *thd) { return super::prepare(thd); }
/**
Construtor of Open_sequence_table_ctx
*/
Open_sequence_table_ctx::Open_sequence_table_ctx(THD *thd,
TABLE_LIST *table_list)
: m_thd(thd), m_inherit_table(table_list), m_state(thd, table_list) {}
Open_sequence_table_ctx::Open_sequence_table_ctx(THD *thd, TABLE_SHARE *share)
: m_thd(thd), m_inherit_table(NULL), m_state(thd, share) {}
/**
Open and lock the sequence table.
@retval false success
@retval true failure
*/
bool Open_sequence_table_ctx::open_table() {
DBUG_ENTER("Open_sequence_table_ctx::open_table");
if (!m_inherit_table || !(m_inherit_table->table)) {
/**
Clone a TABLE_LIST from table, then open and lock the table for the
sequence dml.
*/
if (open_and_lock_tables(m_thd, m_state.cloned_table(),
MYSQL_LOCK_IGNORE_TIMEOUT))
DBUG_RETURN(true);
}
DBUG_ASSERT((m_thd->mdl_context.owns_equal_or_stronger_lock(
MDL_key::TABLE, m_state.cloned_table()->db,
m_state.cloned_table()->table_name, MDL_SHARED_WRITE)));
/**
Insert the initial row soon after CREATE SEQUENCE,
we will inherit transaction context.
*/
DBUG_RETURN(false);
}
/**
We will inherit the CREATE SEQUENCE transaction context,
so didn't need to close opened_table and release MDL explicitly.
Opened table and MDL wil be released when statement end that will incur
implicit commit.
*/
Open_sequence_table_ctx::~Open_sequence_table_ctx() {}
/**
Construtor of Insert_sequence_table_ctx, for saving current context.
*/
Insert_sequence_table_ctx::Insert_sequence_table_ctx(
THD *thd, TABLE_LIST *table_list, const Sequence_info *seq_info)
: otx(thd, table_list),
m_thd(thd),
m_seq_info(seq_info),
m_save_binlog_row_based(false) {
/* Sequence will be replicated by statement, so disable row binlog */
if ((m_save_binlog_row_based = m_thd->is_current_stmt_binlog_format_row()))
m_thd->clear_current_stmt_binlog_format_row();
}
Insert_sequence_table_ctx::~Insert_sequence_table_ctx() {
if (m_save_binlog_row_based) m_thd->set_current_stmt_binlog_format_row();
}
/**
Write the sequence initial row.
@retval false success
@retval true failure
*/
bool Insert_sequence_table_ctx::write_record() {
bool error = false;
TABLE *table;
st_sequence_field_info *field_info;
Sequence_field field_num;
ulonglong field_value;
DBUG_ENTER("Insert_sequence_table_ctx::write_record");
if ((error = otx.open_table())) DBUG_RETURN(error);
table = otx.get_table();
DBUG_ASSERT(table && table->in_use == m_thd);
table->use_all_columns();
field_info = seq_fields;
while (field_info->field_name) {
field_num = field_info->field_num;
field_value = m_seq_info->get_value(field_num);
table->field[field_num]->store((longlong)(field_value), true);
field_info++;
}
if ((error = table->file->ha_write_row(table->record[0]))) {
table->file->print_error(error, MYF(0));
}
DBUG_RETURN(error);
}
/// @} (end of group Sequence Engine)