449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
/* 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 */
 | 
						|
 | 
						|
 | 
						|
#include "lex_string.h"
 | 
						|
#include "m_string.h"
 | 
						|
#include "mysql/components/services/log_builtins.h"
 | 
						|
#include "mysql/components/services/log_shared.h"
 | 
						|
#include "sql/sql_base.h"
 | 
						|
#include "sql/sql_class.h"
 | 
						|
#include "sql/sql_lex.h"
 | 
						|
#include "sql/table.h"
 | 
						|
#include "sql/transaction.h"
 | 
						|
 | 
						|
#include "sql/ccl/ccl.h"
 | 
						|
#include "sql/ccl/ccl_cache.h"
 | 
						|
#include "sql/ccl/ccl_table.h"
 | 
						|
#include "sql/ccl/ccl_table_common.h"
 | 
						|
 | 
						|
#include "sql/common/table.h"
 | 
						|
#include "sql/common/table_common.h"
 | 
						|
 | 
						|
namespace im {
 | 
						|
 | 
						|
/* ccl table schema name */
 | 
						|
LEX_CSTRING CCL_SCHEMA_NAME = {C_STRING_WITH_LEN("mysql")};
 | 
						|
 | 
						|
/* ccl table name */
 | 
						|
LEX_CSTRING CCL_TABLE_NAME = {C_STRING_WITH_LEN("concurrency_control")};
 | 
						|
 | 
						|
 | 
						|
const char *CCL_TABLE_ALIAS = "concurrency_control";
 | 
						|
 | 
						|
/* Whether the table is concurrency_control */
 | 
						|
bool is_ccl_table(const char *table_name, size_t len) {
 | 
						|
  if (len == CCL_TABLE_NAME.length &&
 | 
						|
      my_strcasecmp(system_charset_info, CCL_TABLE_NAME.str, table_name))
 | 
						|
    return true;
 | 
						|
  return false;
 | 
						|
}
 | 
						|
/* Table "mysql.concurrency_control" definition */
 | 
						|
static const TABLE_FIELD_TYPE mysql_ccl_table_fields[MYSQL_CCL_FIELD_COUNT] = {
 | 
						|
    {{C_STRING_WITH_LEN("Id")}, {C_STRING_WITH_LEN("bigint")}, {NULL, 0}},
 | 
						|
    {{C_STRING_WITH_LEN("Type")},
 | 
						|
     {C_STRING_WITH_LEN("enum('SELECT','UPDATE','INSERT','DELETE')")},
 | 
						|
     {C_STRING_WITH_LEN("utf8")}},
 | 
						|
    {{C_STRING_WITH_LEN("Schema_name")},
 | 
						|
     {C_STRING_WITH_LEN("varchar(64)")},
 | 
						|
     {NULL, 0}},
 | 
						|
    {{C_STRING_WITH_LEN("Table_name")},
 | 
						|
     {C_STRING_WITH_LEN("varchar(64)")},
 | 
						|
     {NULL, 0}},
 | 
						|
    {{C_STRING_WITH_LEN("Concurrency_count")},
 | 
						|
     {C_STRING_WITH_LEN("bigint")},
 | 
						|
     {NULL, 0}},
 | 
						|
    {{C_STRING_WITH_LEN("Keywords")}, {C_STRING_WITH_LEN("text")}, {NULL, 0}},
 | 
						|
    {{C_STRING_WITH_LEN("State")},
 | 
						|
     {C_STRING_WITH_LEN("enum('N','Y')")},
 | 
						|
     {C_STRING_WITH_LEN("utf8")}},
 | 
						|
    {{C_STRING_WITH_LEN("Ordered")},
 | 
						|
     {C_STRING_WITH_LEN("enum('N','Y')")},
 | 
						|
     {C_STRING_WITH_LEN("utf8")}}};
 | 
						|
 | 
						|
static const TABLE_FIELD_DEF ccl_table_def = {MYSQL_CCL_FIELD_COUNT,
 | 
						|
                                              mysql_ccl_table_fields};
 | 
						|
 | 
						|
const char *ccl_rule_type_str[] = {"SELECT", "UPDATE", "INSERT", "DELETE", "QUEUE"};
 | 
						|
const char ccl_rule_state_str[] = {'N', 'Y'};
 | 
						|
const char ccl_rule_ordered_str[] = {'N', 'Y'};
 | 
						|
 | 
						|
/**
 | 
						|
  Open the ccl table, report error if failed.*
 | 
						|
 | 
						|
  Attention:
 | 
						|
  It didn't open attached transaction, so it must commit
 | 
						|
  current transaction context when close ccl table.
 | 
						|
 | 
						|
  Make sure it launched within main thread booting
 | 
						|
  or statement that cause implicit commit.
 | 
						|
 | 
						|
  Report client error if failed.
 | 
						|
 | 
						|
  @param[in]      thd           Thread context
 | 
						|
  @param[in]      table_list    CCL table
 | 
						|
  @param[in]      write         read or write
 | 
						|
 | 
						|
  @retval         Conf_error
 | 
						|
*/
 | 
						|
Conf_error open_ccl_table(THD *thd, TABLE_LIST_PTR &table_list, bool write) {
 | 
						|
  DBUG_ENTER("open_ccl_table");
 | 
						|
  if (open_conf_table(thd, table_list, CCL_SCHEMA_NAME, CCL_TABLE_NAME,
 | 
						|
                      CCL_TABLE_ALIAS, &ccl_table_def, write))
 | 
						|
 | 
						|
    DBUG_RETURN(Conf_error::CONF_ER_TABLE_OP_ERROR);
 | 
						|
 | 
						|
  DBUG_RETURN(Conf_error::CONF_OK);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Commit the ccl transaction.
 | 
						|
  Call reload_ccl_rules() if commit failed.
 | 
						|
 | 
						|
  @param[in]      thd           Thread context
 | 
						|
  @param[in]      rollback      Rollback request
 | 
						|
*/
 | 
						|
bool ccl_end_trans(THD *thd, bool rollback) {
 | 
						|
  bool result;
 | 
						|
  if ((result = conf_end_trans(thd, rollback)))
 | 
						|
    reload_ccl_rules(thd);
 | 
						|
  return result;
 | 
						|
}
 | 
						|
/**
 | 
						|
  Push invalid ccl record warning
 | 
						|
 | 
						|
  @param[in]      record        Ccl record
 | 
						|
  @param[in]      when          Operation
 | 
						|
*/
 | 
						|
void Ccl_reader::row_warning(Conf_record *record, const char *when,
 | 
						|
                             const char *) {
 | 
						|
  push_warning_printf(m_thd, Sql_condition::SL_WARNING, ER_CCL_INVALID_RULE,
 | 
						|
                      ER_THD(m_thd, ER_CCL_INVALID_RULE), record->get_id(),
 | 
						|
                      when);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Reconstruct and Report error by adding handler error.
 | 
						|
 | 
						|
  @param[in]      errcode     handler error.
 | 
						|
*/
 | 
						|
void Ccl_reader::print_ha_error(int errcode) {
 | 
						|
  ha_error(ER_CCL_TABLE_OP_FAILED, errcode);
 | 
						|
}
 | 
						|
/**
 | 
						|
  Log the conf table error.
 | 
						|
 | 
						|
  @param[in]    err     Conf error type
 | 
						|
*/
 | 
						|
void Ccl_reader::log_error(Conf_error err) { log_conf_error(ER_CCL, err); }
 | 
						|
 | 
						|
/* Create new ccl record */
 | 
						|
Conf_record *Ccl_reader::new_record() { return new (m_mem_root) Ccl_record(); }
 | 
						|
 | 
						|
/**
 | 
						|
  Save the row value into ccl_record structure.
 | 
						|
*/
 | 
						|
void Ccl_reader::read_attributes(Conf_record *r) {
 | 
						|
  DBUG_ENTER("Ccl_reader::read_attributes");
 | 
						|
  Ccl_record *record = dynamic_cast<Ccl_record *>(r);
 | 
						|
  DBUG_ASSERT(record);
 | 
						|
  record->reset();
 | 
						|
  record->id = m_table->field[MYSQL_CCL_FIELD_ID]->val_int();
 | 
						|
  record->type = to_ccl_type(m_table->field[MYSQL_CCL_FIELD_TYPE]->val_int());
 | 
						|
  record->schema_name =
 | 
						|
      get_field(m_mem_root, m_table->field[MYSQL_CCL_FIELD_SCHEMA_NAME]);
 | 
						|
  record->table_name =
 | 
						|
      get_field(m_mem_root, m_table->field[MYSQL_CCL_FIELD_TABLE_NAME]);
 | 
						|
  record->concurrency_count =
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_CONCURRENCY_COUNT]->val_int();
 | 
						|
  record->keywords =
 | 
						|
      get_field(m_mem_root, m_table->field[MYSQL_CCL_FIELD_KEYWORDS]);
 | 
						|
  record->state =
 | 
						|
      to_ccl_state(m_table->field[MYSQL_CCL_FIELD_STATE]->val_int());
 | 
						|
  record->ordered =
 | 
						|
      to_ccl_ordered(m_table->field[MYSQL_CCL_FIELD_ORDERED]->val_int());
 | 
						|
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
/**
 | 
						|
  Reconstruct and Report error by adding handler error.
 | 
						|
 | 
						|
  @param[in]      errcode     handler error.
 | 
						|
*/
 | 
						|
void Ccl_writer::print_ha_error(int errcode) {
 | 
						|
  ha_error(ER_CCL_TABLE_OP_FAILED, errcode);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Store the rule attributes into table->field
 | 
						|
 | 
						|
  @param[in]      record        the table row object
 | 
						|
*/
 | 
						|
void Ccl_writer::store_attributes(const Conf_record *r) {
 | 
						|
  const Ccl_record *record = dynamic_cast<const Ccl_record *>(r);
 | 
						|
 | 
						|
  if (m_op_type == Conf_table_op::OP_INSERT) {
 | 
						|
    restore_record(m_table, s->default_values);
 | 
						|
 | 
						|
    /* rule type */
 | 
						|
    longlong rule_type_pos = static_cast<longlong>(record->type);
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_TYPE]->store(
 | 
						|
        ccl_rule_type_str[rule_type_pos],
 | 
						|
        strlen(ccl_rule_type_str[rule_type_pos]), system_charset_info);
 | 
						|
    /* rule schema_name */
 | 
						|
    if (!blank(record->schema_name)) {
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_SCHEMA_NAME]->store(
 | 
						|
          record->schema_name, strlen(record->schema_name),
 | 
						|
          system_charset_info);
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_SCHEMA_NAME]->set_notnull();
 | 
						|
    } else
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_SCHEMA_NAME]->set_null();
 | 
						|
 | 
						|
    /* rule table_name*/
 | 
						|
    if (!blank(record->table_name)) {
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_TABLE_NAME]->store(
 | 
						|
          record->table_name, strlen(record->table_name), system_charset_info);
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_TABLE_NAME]->set_notnull();
 | 
						|
    } else
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_TABLE_NAME]->set_null();
 | 
						|
 | 
						|
    /* rule concurrency count */
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_CONCURRENCY_COUNT]->store(
 | 
						|
        (longlong)record->concurrency_count, true);
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_CONCURRENCY_COUNT]->set_notnull();
 | 
						|
 | 
						|
    /* rule keywords */
 | 
						|
    if (!blank(record->keywords)) {
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_KEYWORDS]->store(
 | 
						|
          record->keywords, strlen(record->keywords), system_charset_info);
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_KEYWORDS]->set_notnull();
 | 
						|
    } else
 | 
						|
      m_table->field[MYSQL_CCL_FIELD_KEYWORDS]->set_null();
 | 
						|
 | 
						|
    longlong rule_state_pos = static_cast<longlong>(record->state);
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_STATE]->store(
 | 
						|
        &ccl_rule_state_str[rule_state_pos], 1, system_charset_info);
 | 
						|
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_STATE]->set_notnull();
 | 
						|
 | 
						|
    longlong rule_ordered_pos = static_cast<longlong>(record->ordered);
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_ORDERED]->store(
 | 
						|
        &ccl_rule_ordered_str[rule_ordered_pos], 1, system_charset_info);
 | 
						|
    m_table->field[MYSQL_CCL_FIELD_ORDERED]->set_notnull();
 | 
						|
  }
 | 
						|
}
 | 
						|
/**
 | 
						|
  Store the record rule id into table->field[ID].
 | 
						|
 | 
						|
  @param[in]      record        the table row
 | 
						|
*/
 | 
						|
void Ccl_writer::store_id(const Conf_record *r) {
 | 
						|
  const Ccl_record *record = dynamic_cast<const Ccl_record *>(r);
 | 
						|
  m_table->field[MYSQL_CCL_FIELD_ID]->store(record->id, true);
 | 
						|
}
 | 
						|
/**
 | 
						|
  Retrieve ccl type from table->fields into record.
 | 
						|
 | 
						|
  @param[out]     record        the table row object
 | 
						|
*/
 | 
						|
void Ccl_writer::retrieve_attr(Conf_record *r) {
 | 
						|
  Ccl_record *record = dynamic_cast<Ccl_record *>(r);
 | 
						|
  record->type = to_ccl_type(m_table->field[MYSQL_CCL_FIELD_TYPE]->val_int());
 | 
						|
}
 | 
						|
/**
 | 
						|
  Push row not found warning to client.
 | 
						|
 | 
						|
  @param[in]      record        table table row object
 | 
						|
*/
 | 
						|
void Ccl_writer::row_not_found_warning(Conf_record *r) {
 | 
						|
  Ccl_record *record = dynamic_cast<Ccl_record *>(r);
 | 
						|
  /* Not found rule, push warning here */
 | 
						|
  push_warning_printf(m_thd, Sql_condition::SL_WARNING, ER_CCL_RULE_NOT_FOUND,
 | 
						|
                      ER_THD(m_thd, ER_CCL_RULE_NOT_FOUND), record->id,
 | 
						|
                      "table");
 | 
						|
}
 | 
						|
/**
 | 
						|
  Init the rules when mysqld reboot
 | 
						|
 | 
						|
  It should log error message if failed, reported client error
 | 
						|
  will be ingored.
 | 
						|
 | 
						|
  @param[in]      bootstrap     Whether initialize or restart.
 | 
						|
*/
 | 
						|
void ccl_rules_init(bool bootstrap) {
 | 
						|
  Conf_error error;
 | 
						|
  DBUG_ENTER("Ccl_rules_init");
 | 
						|
 | 
						|
  if (bootstrap) DBUG_VOID_RETURN;
 | 
						|
 | 
						|
  /* When reboot, we need to create new THD to read rules */
 | 
						|
  THD *orig_thd = current_thd;
 | 
						|
 | 
						|
  THD *thd = new THD;
 | 
						|
  thd->thread_stack = pointer_cast<char *>(&thd);
 | 
						|
  thd->store_globals();
 | 
						|
  lex_start(thd);
 | 
						|
 | 
						|
  ccl_queue_buckets_init(ccl_queue_bucket_count, ccl_queue_bucket_size);
 | 
						|
 | 
						|
  error = reload_ccl_rules(thd);
 | 
						|
 | 
						|
  lex_end(thd->lex);
 | 
						|
  delete thd;
 | 
						|
  if (orig_thd) orig_thd->store_globals();
 | 
						|
  /* Log error message if any */
 | 
						|
  ccl_log_error(error);
 | 
						|
  DBUG_VOID_RETURN;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Flush all rules and load rules from table.
 | 
						|
 | 
						|
  Report client error if failed, whether log error
 | 
						|
  message decided by caller.
 | 
						|
 | 
						|
  Log warning message if the number of successful loading into cache
 | 
						|
  is not equal to records in table.
 | 
						|
 | 
						|
  @param[in]      thd           Thread context
 | 
						|
 | 
						|
  @retval         Ccl_error
 | 
						|
*/
 | 
						|
Conf_error reload_ccl_rules(THD *thd) {
 | 
						|
  Conf_error error;
 | 
						|
  size_t num;
 | 
						|
  TABLE_LIST_PTR table_list;
 | 
						|
  Conf_records *records;
 | 
						|
  DBUG_ENTER("reload_ccl_rules");
 | 
						|
 | 
						|
  if ((error = open_ccl_table(thd, table_list, false)) != Conf_error::CONF_OK)
 | 
						|
    DBUG_RETURN(error);
 | 
						|
 | 
						|
  DBUG_ASSERT(table_list->table);
 | 
						|
 | 
						|
  Ccl_reader reader(thd, table_list->table, thd->mem_root);
 | 
						|
  records = reader.read_all_rows(&error);
 | 
						|
 | 
						|
  if (error != Conf_error::CONF_OK) goto err_and_close;
 | 
						|
 | 
						|
  DBUG_ASSERT(records);
 | 
						|
 | 
						|
  /** Here only push warning in refresh_ccl_cache. */
 | 
						|
  num = refresh_ccl_cache(records, true);
 | 
						|
  if (num != records->size()) {
 | 
						|
    std::stringstream ss;
 | 
						|
    ss << "it should load " << records->size() << " rules, actually " << num
 | 
						|
       << " rules.";
 | 
						|
    LogErr(WARNING_LEVEL, ER_CCL, ss.str().c_str());
 | 
						|
  }
 | 
						|
err_and_close:
 | 
						|
  commit_and_close_conf_table(thd);
 | 
						|
  DBUG_RETURN(error);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Add new rule into concurrency_table and insert ccl cache.
 | 
						|
  Report client error if failed.
 | 
						|
 | 
						|
  @param[in]      thd         Thread context
 | 
						|
  @param[in]      record      Rule
 | 
						|
 | 
						|
  @retval         false       Success
 | 
						|
  @retval         true        Failure
 | 
						|
*/
 | 
						|
bool add_ccl_rule(THD *thd, Conf_record *r) {
 | 
						|
  Conf_error error;
 | 
						|
  size_t num;
 | 
						|
  TABLE_LIST_PTR table_list;
 | 
						|
  Conf_records records(key_memory_ccl);
 | 
						|
  Ccl_record_group group;
 | 
						|
  Ccl_record *record;
 | 
						|
 | 
						|
  DBUG_ENTER("add_ccl_rule");
 | 
						|
 | 
						|
  if ((error = open_ccl_table(thd, table_list, true)) != Conf_error::CONF_OK)
 | 
						|
    DBUG_RETURN(true);
 | 
						|
 | 
						|
  Ccl_writer writer(thd, table_list->table, thd->mem_root,
 | 
						|
                    Conf_table_op::OP_INSERT);
 | 
						|
 | 
						|
  if (writer.write_row(r)) goto err;
 | 
						|
 | 
						|
  /* Only write into table if not active rule */
 | 
						|
  if (!(r->check_active())) goto end;
 | 
						|
 | 
						|
  /* Personalized process ccl rule cache  */
 | 
						|
  record = dynamic_cast<Ccl_record *>(r);
 | 
						|
  records.push_back(record);
 | 
						|
  num = group.classify_rule(record->type, &records);
 | 
						|
 | 
						|
  if (num != 1) {
 | 
						|
    my_error(ER_CCL_INVALID_RULE, MYF(0), 0, "add rule");
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
  /* Here must report client error if some rule failed. */
 | 
						|
  if ((num = System_ccl::instance()->refresh_rules(
 | 
						|
           group, record->type, false, Ccl_error_level::CCL_CRITICAL)) !=
 | 
						|
      group.size())
 | 
						|
    goto err;
 | 
						|
 | 
						|
end:
 | 
						|
  ccl_end_trans(thd, false);
 | 
						|
  DBUG_RETURN(false);
 | 
						|
 | 
						|
err:
 | 
						|
  ccl_end_trans(thd, true);
 | 
						|
  DBUG_RETURN(true);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Delete the rule.
 | 
						|
  Delete the row or clear the rule cache or both.
 | 
						|
 | 
						|
  Only report warning message if row or cache not found
 | 
						|
*/
 | 
						|
bool del_ccl_rule(THD *thd, Conf_record *record) {
 | 
						|
  Conf_error error;
 | 
						|
  TABLE_LIST_PTR table_list;
 | 
						|
  size_t num;
 | 
						|
  DBUG_ENTER("del_ccl_rule");
 | 
						|
 | 
						|
  if ((error = open_ccl_table(thd, table_list, true)) != Conf_error::CONF_OK)
 | 
						|
    DBUG_RETURN(true);
 | 
						|
 | 
						|
  Ccl_writer writer(thd, table_list->table, thd->mem_root,
 | 
						|
                    Conf_table_op::OP_DELETE);
 | 
						|
 | 
						|
  if (writer.delete_row_by_id(record)) goto err;
 | 
						|
 | 
						|
  num = System_ccl::instance()->delete_rule(record->get_id());
 | 
						|
  if (num < 1) {
 | 
						|
    push_warning_printf(thd, Sql_condition::SL_WARNING, ER_CCL_RULE_NOT_FOUND,
 | 
						|
                        ER_THD(thd, ER_CCL_RULE_NOT_FOUND), record->get_id(),
 | 
						|
                        "cache");
 | 
						|
  }
 | 
						|
  ccl_end_trans(thd, false);
 | 
						|
  DBUG_RETURN(false);
 | 
						|
 | 
						|
err:
 | 
						|
  ccl_end_trans(thd, true);
 | 
						|
  DBUG_RETURN(true);
 | 
						|
}
 | 
						|
} /* namespace im */
 |