From fe48d0ff880350078097bd48c7c168f071cd051b Mon Sep 17 00:00:00 2001 From: Varun Nagaraju Date: Wed, 16 Jul 2025 14:37:33 +0530 Subject: [PATCH] Adds support for Foreign keys table in Pstress https://perconadev.atlassian.net/browse/PSTRESS-152 Introduced support for Foreign key tables in Pstress. * The tables with foreign key references are suffixed with `_fk` while creating the tables. * These FK tables include `ifk_col` column referencing parent table's `ipkey`. * Example : When --fk-prob is 50 --tables 100 options are passed, there is a 50% chance there will be a table tt_N_fk whose parent would be tt_N. * To fully test FK behaviour, use `--fk-prob=100 --pk-prob=100` * The FK tables can be disabled with `--no-fk` option. Fixed transaction behaviour: * Ensured `START TRANSACTION` doesn't block other sessions by running transactions independently. Adjusted the default probabilities of some features: * A new option called partition-prob is added which controls the number of partition tables created and is set to 10% by default. * The exisiting option ratio-normal-temp is replaced with a new option, temporary-prob to control the number of temporary tables and is set to 10% by default. * The exisiting option commit-rollback-ratio is replaced with a new option, commit-prob, which controls the probability of executing commit and is set to 95 by default. * Decreased the probability of using SAVEPOINT in a transaction from 50% to 10%. * The option `primary-key-probability` is renamed to `--pk-prob` fpr clarity. Thanks Rahul Malik for the contribution. (github.com/Percona-QA/pstress/pull/92) --- src/common.hpp | 11 +- src/help.cpp | 55 +++++--- src/random_test.cpp | 335 +++++++++++++++++++++++++++++++++----------- src/random_test.hpp | 106 +++++++++++++- 4 files changed, 392 insertions(+), 115 deletions(-) diff --git a/src/common.hpp b/src/common.hpp index c0cb97a6..02b18488 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -59,11 +59,8 @@ struct Option { INDEX_COLUMNS, NO_AUTO_INC, NO_DESC_INDEX, - NO_TEMPORARY, - NO_PARTITION, ONLY_TEMPORARY, ONLY_PARTITION, - TEMPORARY_TO_NORMAL_RATIO, INITIAL_RECORDS_IN_TABLE, NUMBER_OF_SECONDS_WORKLOAD, ALTER_TABLE_ENCRYPTION, @@ -121,7 +118,7 @@ struct Option { MYSQLD_SERVER_OPTION = 'z', TRANSATION_PRB_K, TRANSACTIONS_SIZE, - COMMMIT_TO_ROLLBACK_RATIO, + COMMIT_PROB, SAVEPOINT_PRB_K, CHECK_TABLE, CHECK_TABLE_PRELOAD, @@ -144,6 +141,12 @@ struct Option { DROP_CREATE, EXACT_INITIAL_RECORDS, PREPARE, + NO_TEMPORARY, + NO_PARTITION, + NO_FK, + FK_PROB, + PARTITION_PROB, + TEMPORARY_PROB, MAX } option; Option(Type t, Opt o, std::string n) diff --git a/src/help.cpp b/src/help.cpp index 3b97ab5d..3ce4f11a 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -229,6 +229,12 @@ void add_options() { opt->setArgs(no_argument); opt->setBool(false); + /* No FK tables */ + opt = newOption(Option::BOOL, Option::NO_FK, "no-fk-tables"); + opt->help = "do not work on foriegn tables"; + opt->setArgs(no_argument); + opt->setBool(false); + /* No Partition tables */ opt = newOption(Option::BOOL, Option::NO_PARTITION, "no-partition-tables"); opt->help = "do not work on partition tables"; @@ -241,12 +247,21 @@ void add_options() { opt->setArgs(no_argument); opt->setBool(false); + opt = newOption(Option::INT, Option::FK_PROB, "fk-prob"); + opt->help = R"( + Probability of each normal table having the FK. Currently, FKs are only linked + to the primary key of the parent table. So, even with 100% probability, a table + will have an FK only if its parent has a primary key. + )"; + opt->setInt(50); + + opt = newOption(Option::INT, Option::PARTITION_PROB, "partition-prob"); + opt->help = "Probability of parititon tables"; + opt->setInt(10); + /* Ratio of temporary table to normal table */ - opt = newOption(Option::INT, Option::TEMPORARY_TO_NORMAL_RATIO, - "ratio-normal-temp"); - opt->help = "ratio of normal to temporary tables. for e.g. if ratio to " - "normal table to temporary is 10 . --tables 40. them only 4 " - "temorary table will be created per session"; + opt = newOption(Option::INT, Option::TEMPORARY_PROB, "temporary-prob"); + opt->help = "Probability of temporary tables"; opt->setInt(10); /* Initial Records in table */ @@ -270,7 +285,7 @@ void add_options() { opt->setInt(1000); /* primary key probability */ - opt = newOption(Option::INT, Option::PRIMARY_KEY, "primary-key-probability"); + opt = newOption(Option::INT, Option::PRIMARY_KEY, "pk-prob"); opt->help = "Probability of adding primary key in a table"; opt->setInt(50); @@ -684,7 +699,7 @@ void add_options() { opt = newOption(Option::BOOL, Option::LOG_QUERY_DURATION, "log-query-duration"); opt->help = "Log query duration in milliseconds"; - opt->setBool(true); + opt->setBool(false); opt->setArgs(no_argument); /* log failed queries */ @@ -714,28 +729,26 @@ void add_options() { opt->setArgs(no_argument); /* transaction probability */ - opt = newOption(Option::INT, Option::TRANSATION_PRB_K, "trx-prb-k"); + opt = newOption(Option::INT, Option::TRANSATION_PRB_K, "trx-prob-k"); opt->help = "probability(out of 1000) of combining sql as single trx"; - opt->setInt(10); + opt->setInt(1); /* tranasaction size */ opt = newOption(Option::INT, Option::TRANSACTIONS_SIZE, "trx-size"); opt->help = "average size of each trx"; - opt->setInt(100); - - /* commit to rollback ratio */ - opt = newOption(Option::INT, Option::COMMMIT_TO_ROLLBACK_RATIO, - "commit-rollback-ratio"); - opt->help = "ratio of commit to rollback. e.g.\nif 5, then 5 " - "transactions will be committed and 1 will be rollback.\n" - "if 0 then all transactions will be rollback"; - opt->setInt(5); + opt->setInt(10); + + /* probability of executing commit */ + opt = newOption(Option::INT, Option::COMMIT_PROB, "commit-prob"); + opt->help = "probability of executing commit after a transaction. Else it " + "would be rollback "; + opt->setInt(95); /* number of savepoints in trxs */ - opt = newOption(Option::INT, Option::SAVEPOINT_PRB_K, "savepoint-prb-k"); - opt->help = "probability of using savepoint in a transaction.\n Also 25% " + opt = newOption(Option::INT, Option::SAVEPOINT_PRB_K, "savepoint-prob-k"); + opt->help = "probability of using savepoint in a transaction.\n Also 10% " "such transaction will be rollback to some savepoint"; - opt->setInt(50); + opt->setInt(10); /* steps */ opt = newOption(Option::INT, Option::STEP, "step"); diff --git a/src/random_test.cpp b/src/random_test.cpp index 34b38612..c931ce3f 100644 --- a/src/random_test.cpp +++ b/src/random_test.cpp @@ -17,7 +17,10 @@ using namespace rapidjson; std::mt19937 rng; -const std::string partition_string = "_p"; +const std::string TABLE_PREFIX = "tt_"; +const std::string PARTITION_SUFFIX = "_p"; +const std::string FK_SUFFIX = "_fk"; +const std::string TEMP_SUFFIX = "_t"; const int version = 2; /* range for random number int, integers, floats and double. more the value, less randomness. @@ -45,7 +48,7 @@ std::mutex ddl_logs_write; static std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); -std::atomic table_started(0); +std::atomic table_started(0); std::atomic check_failures(0); std::atomic table_completed(0); std::atomic_flag lock_stream = ATOMIC_FLAG_INIT; @@ -62,6 +65,23 @@ static MYSQL_ROW mysql_fetch_row_safe(Thd1 *thd) { return mysql_fetch_row(thd->result.get()); } +/* return table pointer of matching table. This is only done during the + * first step or during the prepare, so you would have only tables that are not + * renamed */ +static Table *pick_table(Table::TABLE_TYPES type, int id) { + std::string name = TABLE_PREFIX + std::to_string(id); + if (type == Table::FK) { + name += FK_SUFFIX; + } else if (type == Table::PARTITION) { + name += PARTITION_SUFFIX; + } + for (auto const &table : *all_tables) { + if (table->name_ == name) + return table; + } + return nullptr; +} + static bool mysql_num_fields_safe(Thd1 *thd, unsigned int req) { if (!thd->result) { thd->thread_log << "mysql_num_fields called with nullptr arg!"; @@ -76,6 +96,24 @@ static bool mysql_num_fields_safe(Thd1 *thd, unsigned int req) { return ret; } +/* generate random numbers to populate in primary and fk +@param[in] number_of_records +@param[out] vector containing unique elements */ +static std::vector generateUniqueRandomNumbers(int number_of_records) { + + std::unordered_set unique_keys_set(number_of_records); + + int max_size = + g_integer_range * options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt(); + + while (unique_keys_set.size() < static_cast(number_of_records)) { + unique_keys_set.insert(rand_int(max_size, 1)); + } + + std::vector unique_keys(unique_keys_set.begin(), unique_keys_set.end()); + return unique_keys; +} + /* run check table */ static bool get_check_result(const std::string &sql, Thd1 *thd) { @@ -263,8 +301,15 @@ int sum_of_all_options(Thd1 *thd) { options->at(Option::NO_PARTITION)->getBool()) throw std::runtime_error("choose either only partition or no partition"); - if (options->at(Option::ONLY_PARTITION)->getBool()) + if (options->at(Option::ONLY_PARTITION)->getBool()) { options->at(Option::NO_TEMPORARY)->setBool("true"); + options->at(Option::PARTITION_PROB)->setInt(100); + } + + if (options->at(Option::ONLY_TEMPORARY)->getBool()) { + options->at(Option::NO_PARTITION)->setBool("true"); + options->at(Option::TEMPORARY_PROB)->setInt(100); + } /* if select is set as zero, disable all type of selects */ if (options->at(Option::NO_SELECT)->getBool()) { @@ -638,7 +683,7 @@ static std::string rand_value_universal(Column::COLUMN_TYPES type_, return ""; } -/* return random value of any string */ +/* return random value of a column*/ std::string Column::rand_value() { return rand_value_universal(type_, length); } /* return random value of sub string */ @@ -973,6 +1018,17 @@ template void Table::Serialize(Writer &writer) const { }; writer.EndArray(); } + } else if (type == FK) { + auto fk_table = static_cast(this); + std::string parent = fk_table->parent->name_; + std::string on_update = fk_table->enumToString(fk_table->on_update); + std::string on_delete = fk_table->enumToString(fk_table->on_delete); + writer.String("parent"); + writer.String(parent.c_str(), static_cast(parent.length())); + writer.String("on_update"); + writer.String(on_update.c_str(), static_cast(on_update.length())); + writer.String("on_delete"); + writer.String(on_delete.c_str(), static_cast(on_delete.length())); } writer.String("engine"); @@ -1070,13 +1126,9 @@ bool Table::load(Thd1 *thd) { /* load default data in table */ if (!options->at(Option::JUST_LOAD_DDL)->getBool()) { - thd->ddl_query = false; - size_t number_of_records = - options->at(Option::EXACT_INITIAL_RECORDS)->getBool() - ? options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt() - : rand_int(options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt()); - if (!InsertBulkRecord(thd, number_of_records)) + thd->ddl_query = false; + if (!InsertBulkRecord(thd)) return false; } @@ -1085,6 +1137,12 @@ bool Table::load(Thd1 *thd) { return false; } + if (this->type == Table::TABLE_TYPES::FK) { + if (!static_cast(this)->load_fk_constraint(thd)) { + return false; + } + } + if (run_query_failed) { thd->thread_log << "some other thread failed, Exiting. Please check logs " << std::endl; @@ -1119,6 +1177,33 @@ bool Table::load_secondary_indexes(Thd1 *thd) { return true; } +bool FK_table::load_fk_constraint(Thd1 *thd) { + + std::string constraint = name_ + "_" + parent->name_; + std::string pk; + for (const auto &col : *parent->columns_) { + if (col->primary_key == true) { + pk = col->name_; + break; + } + } + assert(pk.size() > 0); + + std::string sql = "ALTER TABLE " + name_ + " ADD CONSTRAINT " + constraint + + " FOREIGN KEY (ifk_col) REFERENCES " + parent->name_ + + " (" + pk + ")"; + sql += " ON UPDATE " + enumToString(on_update); + sql += " ON DELETE " + enumToString(on_delete); + + if (!execute_sql(sql, thd)) { + thd->thread_log << "Failed to add fk constraint " + << " on " << name_ << std::endl; + run_query_failed = true; + return false; + } + return true; +} + /* Constructor used by load_metadata */ Partition::Partition(std::string n, std::string part_type_, int number_of_part_) : Table(n), number_of_part(number_of_part_) { @@ -1460,6 +1545,12 @@ void Table::CreateDefaultColumn() { auto no_auto_inc = opt_bool(NO_AUTO_INC); bool has_auto_increment = false; + if (type == FK) { + std::string name = "fk_col"; + Column::COLUMN_TYPES type = Column::INTEGER; + AddInternalColumn(new Column{name, this, type}); + } + /* if table is partition add new column */ if (type == PARTITION) { std::string name = "p_col"; @@ -1489,6 +1580,7 @@ void Table::CreateDefaultColumn() { name = "pkey"; col = new Column{name, this, type}; col->primary_key = true; + if (!no_auto_inc && rand_int(3) < 3) { /* 75% of primary key tables are autoinc */ if (this->type == PARTITION && rand_int(3) == 1) @@ -1633,24 +1725,30 @@ void Table::CreateDefaultIndex() { /* Create new table and pick some attributes */ Table *Table::table_id(TABLE_TYPES type, int id) { Table *table; - std::string name = "tt_" + std::to_string(id); + std::string name = TABLE_PREFIX + std::to_string(id); switch (type) { case PARTITION: - table = new Partition(name + partition_string); + table = new Partition(name + PARTITION_SUFFIX); break; case NORMAL: table = new Table(name); break; case TEMPORARY: - table = new Temporary_table(name + "_t"); + table = new Temporary_table(name + TEMP_SUFFIX); break; default: throw std::runtime_error("Unhandle Table type"); + case FK: + table = new FK_table(name + FK_SUFFIX); break; } table->type = type; + table->number_of_initial_records = + options->at(Option::EXACT_INITIAL_RECORDS)->getBool() + ? options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt() + : rand_int(options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt()); static auto no_encryption = opt_bool(NO_ENCRYPTION); /* temporary table on 8.0 can't have key block size */ @@ -1720,10 +1818,22 @@ Table *Table::table_id(TABLE_TYPES type, int id) { table->CreateDefaultColumn(); table->CreateDefaultIndex(); + if (type == FK) { + static_cast(table)->pickRefrence(table); + } return table; } +/* check if table has a primary key */ +bool Table::has_pk() const { + for (const auto &col : *columns_) { + if (col->primary_key) + return true; + } + return false; +} + /* prepare table definition */ std::string Table::definition(bool with_index) { std::string def = "CREATE"; @@ -1759,7 +1869,16 @@ std::string Table::definition(bool with_index) { for (auto id : *indexes_) { def += id->definition() + ", "; } + if (type == FK) { + auto fk = static_cast(this); + def += " FOREIGN KEY (ifk_col) REFERENCES " + fk->parent->name_ + + " (ipkey) "; + def += " ON UPDATE " + fk->enumToString(fk->on_update) + " ON DELETE " + + fk->enumToString(fk->on_delete); + def += ", "; + } } + } else { /* only load autoinc */ if (indexes_->size() > 0) { @@ -1865,10 +1984,28 @@ void generate_metadata_for_tables() { if (!only_temporary_tables) { for (int i = 1; i <= tables; i++) { - if (!options->at(Option::ONLY_PARTITION)->getBool()) - all_tables->push_back(Table::table_id(Table::NORMAL, i)); - if (!options->at(Option::NO_PARTITION)->getBool()) + if (!options->at(Option::ONLY_PARTITION)->getBool()) { + auto parent_table = Table::table_id(Table::NORMAL, i); + all_tables->push_back(parent_table); + + /* Create FK table */ + if (!options->at(Option::NO_FK)->getBool() && + options->at(Option::FK_PROB)->getInt() > rand_int(100) && + parent_table->has_pk()) { + auto child_table = Table::table_id(Table::FK, i); + all_tables->push_back(child_table); + static_cast(child_table)->parent = parent_table; + } + } + + if (!options->at(Option::NO_PARTITION)->getBool() && + options->at(Option::PARTITION_PROB)->getInt() > rand_int(100)) all_tables->push_back(Table::table_id(Table::PARTITION, i)); + /* + if (!options->at(Option::NO_FK)->getBool() && + options->at(Option::FK_PROB)->getInt() > rand_int(100)) + all_tables->push_back(Table::table_id(Table::FK, i)); + */ } } } @@ -2695,15 +2832,30 @@ void Table::UpdateRandomROW(Thd1 *thd) { execute_sql(sql, thd); } -bool Table::InsertBulkRecord(Thd1 *thd, size_t number_of_records) { - std::vector unique_keys; +bool Table::InsertBulkRecord(Thd1 *thd) { bool is_list_partition = false; - if (number_of_records == 0) + // if parent has no records, child can't have records + if (type == FK) { + if (static_cast(this)->parent->number_of_initial_records == 0) + number_of_initial_records = 0; + } + + if (number_of_initial_records == 0) return true; std::string prepare_sql = "INSERT "; + std::vector fk_unique_keys; + + /* If a table has FK move its parent keys in fk_unique_keys */ + if (type == TABLE_TYPES::FK) { + fk_unique_keys = std::move(thd->unique_keys); + } + if (has_pk()) { + thd->unique_keys = generateUniqueRandomNumbers(number_of_initial_records); + } + /* ignore error in the case parition list */ if (type == PARTITION && static_cast(this)->part_type == Partition::LIST) { @@ -2715,63 +2867,50 @@ bool Table::InsertBulkRecord(Thd1 *thd, size_t number_of_records) { prepare_sql += "INTO " + name_ + " ("; - assert(number_of_records <= - static_cast( - g_integer_range * - options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt())); + assert(number_of_initial_records <= + (g_integer_range * + options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt())); for (const auto &column : *columns_) { prepare_sql += column->name_ + ", "; - - /* if a table has primary key generated random keys */ - if (column->primary_key) { - std::unordered_set unique_keys_sets(number_of_records); - size_t record_generated = 0; - while (record_generated < number_of_records) { - /* 0 in primary key generates */ - auto tmp_key = rand_int( - g_integer_range * - options->at(Option::INITIAL_RECORDS_IN_TABLE)->getInt(), - 1); - - if (unique_keys_sets.count(tmp_key) == 0) { - unique_keys_sets.insert(tmp_key); - record_generated++; - } - } - unique_keys.assign(unique_keys_sets.begin(), unique_keys_sets.end()); - } } prepare_sql.erase(prepare_sql.length() - 2); prepare_sql += ")"; std::string values = " VALUES"; + int records = 0; - while (number_of_records-- > 0) { + while (records < number_of_initial_records) { std::string value = "("; for (const auto &column : *columns_) { - if (column->type_ == Column::COLUMN_TYPES::GENERATED) + /* For FK we get the unique value from the parent table unique vector */ + if (column->name_.find("fk_col") != std::string::npos) { + value += + std::to_string(fk_unique_keys[rand_int(fk_unique_keys.size() - 1)]); + } else if (column->type_ == Column::COLUMN_TYPES::GENERATED) { value += "DEFAULT"; - else if (column->primary_key) - value += std::to_string(unique_keys.at(number_of_records)); - else if (column->auto_increment == true) + } else if (column->primary_key) { + value += std::to_string(thd->unique_keys.at(records)); + } else if (column->auto_increment == true) { value += "NULL"; - else if (is_list_partition && column->name_.compare("ip_col") == 0) { + } else if (is_list_partition && column->name_.compare("ip_col") == 0) { /* for list partition we insert only maximum possible value * todo modify rand_value to return list parititon range */ value += std::to_string( rand_int(maximum_records_in_each_parititon_list * options->at(Option::MAX_PARTITIONS)->getInt())); - } else + } else { value += column->rand_value(); + } value += ", "; } value.erase(value.size() - 2); value += ")"; values += value; - if (values.size() > 1024 * 1024 || number_of_records == 0) { + records++; + if (values.size() > 1024 * 1024 || number_of_initial_records == records) { if (!execute_sql(prepare_sql + values, thd)) { ddl_logs_write.lock(); thd->ddl_logs << "Bulk insert failed for table " << name_ << std::endl; @@ -3221,6 +3360,18 @@ static std::string load_metadata_from_file() { } } else if (table_type.compare("NORMAL") == 0) { table = new Table(name); + } else if (table_type == "FK") { + std::string on_update = tab["on_update"].GetString(); + std::string on_delete = tab["on_delete"].GetString(); + std::string parent_name = tab["parent"].GetString(); + + table = new FK_table(name, on_update, on_delete); + for (auto &tbl : *all_tables) { + if (tbl->name_ == parent_name) { + static_cast(table)->parent = tbl; + break; + } + } } else throw std::runtime_error("Unhandle Table type " + table_type); @@ -3391,6 +3542,9 @@ static bool check_tables_partitions_preload(Table *table, Thd1 *thd) { } else { get_check_result("CHECK TABLE " + table->name_, thd) || failures++; } + if (failures != 0) { + check_failures++; + } return failures == 0 ? true : false; } @@ -3428,6 +3582,8 @@ bool Thd1::load_metadata() { /* return true if successful or error out in case of fail */ bool Thd1::run_some_query() { + std::vector tableTypes = {Table::NORMAL, Table::FK, + Table::PARTITION}; execute_sql("USE " + options->at(Option::DATABASE)->getString(), this); /* first create temporary tables metadata if requried */ @@ -3438,7 +3594,7 @@ bool Thd1::run_some_query() { temp_tables = 0; else temp_tables = options->at(Option::TABLES)->getInt() / - options->at(Option::TEMPORARY_TO_NORMAL_RATIO)->getInt(); + options->at(Option::TEMPORARY_PROB)->getInt(); /* create temporary table */ std::vector *all_session_tables = new std::vector
; @@ -3455,11 +3611,20 @@ bool Thd1::run_some_query() { options->at(Option::STEP)->getInt() == 1) { auto current = table_started++; - while (current < all_tables->size()) { - auto table = all_tables->at(current); - if (!table->load(this)) - return false; + while (current <= options->at(Option::TABLES)->getInt()) { + /* first load normal table , then FK and then partition + FK table uses thd->unique_key vector to pick random FK + thd->unique_key is populated from primary key */ + + for (const auto &tableType : tableTypes) { + auto table = pick_table(tableType, current + 1); + if (table == nullptr) + continue; + if (!table->load(this)) { + return false; + } table_completed++; + } current = table_started++; } @@ -3475,15 +3640,20 @@ bool Thd1::run_some_query() { } std::this_thread::sleep_for(dura); } + /* table initial data is created delete , empty the unique_keys */ + this->unique_keys.resize(0); + } else if (options->at(Option::CHECK_TABLE_PRELOAD)->getBool()) { + int number_of_tables = all_tables->size(); auto current = table_started++; - while (current < all_tables->size()) { + while (current < number_of_tables) { auto table = all_tables->at(current); - check_tables_partitions_preload(table, this) || check_failures++; + check_tables_partitions_preload(table, this); table_completed++; current = table_started++; } + // wait for all tables to finish check table while (table_completed < all_tables->size()) { thread_log << "Waiting for all threds to finish check tables " @@ -3527,50 +3697,45 @@ bool Thd1::run_some_query() { /* freqency of all options per thread */ int opt_feq[Option::MAX][2] = {{0, 0}}; - /* variables related to trxs */ - static auto trx_prob = options->at(Option::TRANSATION_PRB_K)->getInt(); - static auto trx_max_size = options->at(Option::TRANSACTIONS_SIZE)->getInt(); - static auto commit_to_rollback = - options->at(Option::COMMMIT_TO_ROLLBACK_RATIO)->getInt(); static auto savepoint_prob = options->at(Option::SAVEPOINT_PRB_K)->getInt(); - int trx_left = -1; // -1 for single trx + int trx_left = 0; int current_save_point = 0; while (std::chrono::system_clock::now() < end) { - /* check if we need to make sql as part of existing or new trx */ - if (trx_prob > 0) { - if (trx_left == 0) - execute_sql((rand_int(commit_to_rollback) == 0 ? "ROLLBACK" : "COMMIT"), - this); - /* check if sql are chossen as part of statement or single trx */ - if (trx_left <= 0 && trx_max_size > 0 && rand_int(1000) < trx_prob) { + /* check if we need to make sql as part of existing or new trx */ + if (trx_left > 0) { + trx_left--; + if (trx_left == 0) { + if (rand_int(100, 1) > options->at(Option::COMMIT_PROB)->getInt()) { + execute_sql("ROLLBACK", this); + } else { + execute_sql("COMMIT", this); + } current_save_point = 0; - if (rand_int(1000) == 1) - trx_left = trx_max_size * 100; - else - trx_left = rand_int(trx_max_size); - execute_sql("START TRANSACTION", this); - } - - /* use savepoint or rollback to savepoint */ - if (trx_left > 0 && savepoint_prob > 0) { - if (rand_int(1000) < savepoint_prob) - execute_sql("SAVEPOINT SAVE" + std::to_string(++current_save_point), + } else { + if (rand_int(1000) < savepoint_prob) { + current_save_point++; + execute_sql("SAVEPOINT SAVE" + std::to_string(current_save_point), this); + } - /* 1/4 chances of rollbacking to savepoint */ - if (current_save_point > 0 && rand_int(1000 * 4) < savepoint_prob) { + /* 10% chances of rollbacking to savepoint */ + if (current_save_point > 0 && rand_int(10) == 1) { auto sv = rand_int(current_save_point, 1); execute_sql("ROLLBACK TO SAVEPOINT SAVE" + std::to_string(sv), this); current_save_point = sv - 1; } - - trx_left--; } } + if (trx_left == 0 && + rand_int(1000) < options->at(Option::TRANSATION_PRB_K)->getInt()) { + execute_sql("START TRANSACTION", this); + trx_left = rand_int(options->at(Option::TRANSACTIONS_SIZE)->getInt(), 1); + } + auto table = all_session_tables->at(rand_int(all_session_tables->size() - 1)); auto option = pick_some_option(); diff --git a/src/random_test.hpp b/src/random_test.hpp index 17b43e44..624b6bf6 100644 --- a/src/random_test.hpp +++ b/src/random_test.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #define INNODB_16K_PAGE_SIZE 16 #define INNODB_8K_PAGE_SIZE 8 #define INNODB_32K_PAGE_SIZE 32 @@ -94,6 +95,7 @@ class Column { bool primary_key = false; bool auto_increment = false; bool compressed = false; // percona type compressed + std::vector unique_values; Table *table_; }; @@ -167,12 +169,16 @@ struct Thd1 { bool ddl_query = false; // is the query ddl bool success = false; // if the sql is successfully executed int max_con_fail_count = 0; // consecutive failed queries + + /* for loading Bulkdata, Primary key of current table is stored in this vector + * which is used for the FK tables */ + std::vector unique_keys; int query_number = 0; }; /* Table basic properties */ struct Table { - enum TABLE_TYPES { PARTITION, NORMAL, TEMPORARY } type; + enum TABLE_TYPES { PARTITION, NORMAL, TEMPORARY, FK } type; Table(std::string n); static Table *table_id(TABLE_TYPES choice, int id); @@ -200,7 +206,7 @@ struct Table { void SetAlterEngine(Thd1 *thd); void ModifyColumn(Thd1 *thd); void InsertRandomRow(Thd1 *thd); - bool InsertBulkRecord(Thd1 *thd, size_t number_of_records); + bool InsertBulkRecord(Thd1 *thd); void DropColumn(Thd1 *thd); void AddColumn(Thd1 *thd); void DropIndex(Thd1 *thd); @@ -223,6 +229,7 @@ struct Table { std::string compression; std::string encryption = "N"; int key_block_size = 0; + int number_of_initial_records; size_t auto_inc_index; // std::string data_directory; todo add corressponding code std::vector *columns_; @@ -238,9 +245,12 @@ struct Table { return "PARTITION"; case TEMPORARY: return "TEMPORARY"; + case FK: + return "FK"; } return "FAIL"; }; + bool has_pk () const ; void set_type(std::string s) { if (s.compare("PARTITION") == 0) @@ -249,9 +259,97 @@ struct Table { type = NORMAL; else if (s.compare("TEMPORARY") == 0) type = TEMPORARY; + else if (s.compare("FK") == 0) + type = FK; }; }; + +/* Fk table */ +struct FK_table : Table { + FK_table(std::string n) : Table(n) { + }; + + /* conustructor used for load_metadata */ + FK_table(std::string n, std::string on_update, std::string on_delete) + : Table(n) { + set_refrence(on_update, on_delete); + } + + /* current only used for step 1. So we do not store in metadata. + Used to get distince keys of pkey table */ + Table* parent; + bool load_fk_constraint(Thd1 *thd); + + void pickRefrence(Table *table) { + on_delete = getRandomForeignKeyAction(table); + on_update = getRandomForeignKeyAction(table); + } + + enum class ForeignKeyAction { + RESTRICT, + CASCADE, + SET_NULL, + NO_ACTION + }; + std::string enumToString(ForeignKeyAction value) const { + switch (value) { + case ForeignKeyAction::RESTRICT: + return "RESTRICT"; + case ForeignKeyAction::CASCADE: + return "CASCADE"; + case ForeignKeyAction::SET_NULL: + return "SET NULL"; + case ForeignKeyAction::NO_ACTION: + return "NO ACTION"; + default: + return "Unknown"; + } + } + // Function to convert string to enum + ForeignKeyAction stringToEnum(const std::string &str) { + static const std::unordered_map enumMap = { + {"RESTRICT", ForeignKeyAction::RESTRICT}, + {"CASCADE", ForeignKeyAction::CASCADE}, + {"SET NULL", ForeignKeyAction::SET_NULL}, + {"NO ACTION", ForeignKeyAction::NO_ACTION}}; + + auto it = enumMap.find(str); + if (it != enumMap.end()) { + return it->second; + } + + // Throw an exception if the string does not correspond to any enum value + throw std::invalid_argument("Invalid enum value: " + str); + } + ForeignKeyAction on_update; + ForeignKeyAction on_delete; + + ForeignKeyAction getRandomForeignKeyAction(Table *table) { + /* if a table has a virtual generated column and if any of the base column + * column use RESTRICT */ + + for (const auto &col : *table->columns_) { + if (col->type_ == Column::COLUMN_TYPES::GENERATED) { + const std::string &clause = + static_cast(col)->clause(); + if (clause.find("fk_col") != std::string::npos && + clause.find("STORED") != std::string::npos) { + return ForeignKeyAction::RESTRICT; + } + } + } + + int randomValue = rand_int(static_cast(ForeignKeyAction::NO_ACTION)); + return static_cast(randomValue); + } + + void set_refrence(std::string on_update_str, std::string on_delete_str) { + on_update = stringToEnum(on_update_str); + on_delete = stringToEnum(on_delete_str); + } +}; + /* Partition table */ struct Partition : public Table { public: @@ -314,8 +412,7 @@ struct Partition : public Table { }; /* Temporary table */ -struct Temporary_table : public Table { -public: +struct Temporary_table : Table { Temporary_table(std::string n) : Table(n){}; Temporary_table(const Temporary_table &table) : Table(table.name_){}; }; @@ -326,7 +423,6 @@ int sum_of_all_server_options(); Option::Opt pick_some_option(); std::vector *random_strs_generator(unsigned long int seed); bool load_metadata(Thd1 *thd); -int save_dictionary(std::vector
*all_tables); /* Execute SQL and update thd variables param[in] sql query that we want to execute