diff --git a/src/fuzzyduck.cpp b/src/fuzzyduck.cpp index 7d4f0b93..b7ff6501 100644 --- a/src/fuzzyduck.cpp +++ b/src/fuzzyduck.cpp @@ -18,6 +18,7 @@ void FuzzyDuck::BeginFuzzing() { if (seed == 0) { seed = random_engine.NextRandomInteger(); } + random_engine.SetSeed(seed); if (max_queries == 0) { throw BinderException("Provide a max_queries argument greater than 0"); } @@ -63,17 +64,24 @@ void FuzzyDuck::FuzzAllFunctions() { } string FuzzyDuck::GenerateQuery() { - LogTask("Generating query with seed " + to_string(seed)); - auto &engine = RandomEngine::Get(context); - // set the seed - engine.SetSeed(seed); - // get the next seed - seed = engine.NextRandomInteger(); - // generate the statement StatementGenerator generator(context); - auto statement = generator.GenerateStatement(); - return statement->ToString(); + // accumulate statement(s) + auto statement = string(""); + if (generator.RandomPercentage(10)) { + // multi statement + idx_t number_of_statements = generator.RandomValue(1000); + LogTask("Generating Multi-Statement query of " + to_string(number_of_statements) + " statements with seed " + + to_string(seed)); + for (idx_t i = 0; i < number_of_statements; i++) { + statement += generator.GenerateStatement()->ToString() + "; "; + } + } else { + // normal statement + LogTask("Generating Single-Statement query with seed " + to_string(seed)); + statement = generator.GenerateStatement()->ToString(); + } + return statement; } void sleep_thread(Connection *con, atomic *is_active, atomic *timed_out, idx_t timeout_duration) { diff --git a/src/include/statement_generator.hpp b/src/include/statement_generator.hpp index bc96b609..d699a4d0 100644 --- a/src/include/statement_generator.hpp +++ b/src/include/statement_generator.hpp @@ -9,14 +9,18 @@ #pragma once #include "duckdb.hpp" +#include "duckdb/parser/parsed_data/detach_info.hpp" #include "duckdb/parser/query_node.hpp" +#define TESTING_DIRECTORY_NAME "duckdb_unittest_tempdir" + namespace duckdb { class SQLStatement; class SelectStatement; class InsertStatement; class UpdateStatement; class DeleteStatement; +class SetStatement; class TableRef; class SelectNode; class SetOperationNode; @@ -47,14 +51,24 @@ class StatementGenerator { vector GenerateAllFunctionCalls(); -private: - unique_ptr GenerateStatement(StatementType type); + //! Returns true with a percentage change (0-100) + bool RandomPercentage(idx_t percentage); + idx_t RandomValue(idx_t max); + string GetRandomAttachedDataBase(); + unique_ptr GenerateStatement(StatementType type); // came from private +private: + unique_ptr GenerateAttachUse(); + unique_ptr GenerateSet(); + unique_ptr GenerateAttach(); + unique_ptr GenerateDetach(); unique_ptr GenerateSelect(); unique_ptr GenerateCreate(); unique_ptr GenerateQueryNode(); unique_ptr GenerateCreateInfo(); + unique_ptr GenerateAttachInfo(); + unique_ptr GenerateDetachInfo(); void GenerateCTEs(QueryNode &node); unique_ptr GenerateTableRef(); @@ -93,14 +107,12 @@ class StatementGenerator { string GenerateCast(const LogicalType &target, const string &source_name, bool add_varchar); bool FunctionArgumentsAlwaysNull(const string &name); - idx_t RandomValue(idx_t max); bool RandomBoolean(); - //! Returns true with a percentage change (0-100) - bool RandomPercentage(idx_t percentage); string RandomString(idx_t length); unique_ptr RandomExpression(idx_t percentage); //! Generate identifier for a column or parent using "t" or "c" prefixes. ie. t0, or c0 + string GenerateDataBaseName(); string GenerateIdentifier(); string GenerateTableIdentifier(); string GenerateSchemaIdentifier(); diff --git a/src/statement_generator.cpp b/src/statement_generator.cpp index e5053b3a..3d0b975e 100644 --- a/src/statement_generator.cpp +++ b/src/statement_generator.cpp @@ -4,19 +4,25 @@ #include "duckdb/common/random_engine.hpp" #include "duckdb/common/types/uuid.hpp" #include "duckdb/function/table/system_functions.hpp" +#include "duckdb/main/attached_database.hpp" +#include "duckdb/main/database_manager.hpp" #include "duckdb/parser/expression/list.hpp" +#include "duckdb/parser/parsed_data/create_function_info.hpp" #include "duckdb/parser/parsed_data/create_schema_info.hpp" #include "duckdb/parser/parsed_data/create_table_info.hpp" -#include "duckdb/parser/parsed_data/create_view_info.hpp" -#include "duckdb/parser/parsed_data/create_function_info.hpp" #include "duckdb/parser/parsed_data/create_type_info.hpp" +#include "duckdb/parser/parsed_data/create_view_info.hpp" #include "duckdb/parser/parsed_expression_iterator.hpp" #include "duckdb/parser/query_node/select_node.hpp" #include "duckdb/parser/query_node/set_operation_node.hpp" +#include "duckdb/parser/statement/attach_statement.hpp" #include "duckdb/parser/statement/create_statement.hpp" #include "duckdb/parser/statement/delete_statement.hpp" +#include "duckdb/parser/statement/detach_statement.hpp" #include "duckdb/parser/statement/insert_statement.hpp" +#include "duckdb/parser/statement/multi_statement.hpp" #include "duckdb/parser/statement/select_statement.hpp" +#include "duckdb/parser/statement/set_statement.hpp" #include "duckdb/parser/statement/update_statement.hpp" #include "duckdb/parser/tableref/list.hpp" @@ -28,6 +34,7 @@ struct GeneratorContext { vector> table_functions; vector> pragma_functions; vector> tables_and_views; + vector> attached_databases; }; StatementGenerator::StatementGenerator(ClientContext &context) : context(context), parent(nullptr), depth(0) { @@ -46,8 +53,14 @@ StatementGenerator::~StatementGenerator() { } std::shared_ptr StatementGenerator::GetDatabaseState(ClientContext &context) { + // start a transaction so that catalog scans can take place. + if (!context.transaction.HasActiveTransaction()) { + context.transaction.BeginTransaction(); + } auto result = std::make_shared(); result->test_types = TestAllTypesFun::GetTestTypes(); + auto &db_manager = DatabaseManager::Get(context); + result->attached_databases = db_manager.GetDatabases(context); auto schemas = Catalog::GetAllSchemas(context); // extract the functions @@ -73,6 +86,9 @@ std::shared_ptr StatementGenerator::GetDatabaseState(ClientCon result->tables_and_views.push_back(entry); }); } + if (context.transaction.HasActiveTransaction()) { + context.transaction.Commit(); + } return result; } @@ -80,6 +96,19 @@ unique_ptr StatementGenerator::GenerateStatement() { if (RandomPercentage(80)) { return GenerateStatement(StatementType::SELECT_STATEMENT); } + if (RandomPercentage(40)) { + if (RandomPercentage(50)) { + // We call this directly so we have a higher chance to fuzz persistent databases + return GenerateAttachUse(); + } + return GenerateStatement(StatementType::ATTACH_STATEMENT); + } + if (RandomPercentage(60)) { + return GenerateStatement(StatementType::DETACH_STATEMENT); + } + if (RandomPercentage(30)) { + return GenerateStatement(StatementType::SET_STATEMENT); + } return GenerateStatement(StatementType::CREATE_STATEMENT); } @@ -89,6 +118,13 @@ unique_ptr StatementGenerator::GenerateStatement(StatementType typ return GenerateSelect(); case StatementType::CREATE_STATEMENT: return GenerateCreate(); + case StatementType::ATTACH_STATEMENT: + return GenerateAttach(); + case StatementType::DETACH_STATEMENT: + return GenerateDetach(); + // generate USE statement + case StatementType::SET_STATEMENT: + return GenerateSet(); default: throw InternalException("Unsupported type"); } @@ -109,6 +145,76 @@ unique_ptr StatementGenerator::GenerateCreate() { return create; } +unique_ptr StatementGenerator::GenerateAttach() { + auto attach = make_uniq(); + attach->info = GenerateAttachInfo(); + return attach; +} + +unique_ptr StatementGenerator::GenerateDetach() { + auto detach = make_uniq(); + detach->info = GenerateDetachInfo(); + return detach; +} + +// generate USE statement +unique_ptr StatementGenerator::GenerateSet() { + auto name_expr = make_uniq(GenerateDataBaseName()); + if (RandomPercentage(90)) { + auto name = GetRandomAttachedDataBase(); + name_expr = make_uniq(Value(name)); + } + auto set = make_uniq("schema", std::move(name_expr), SetScope::AUTOMATIC); + return set; +} + +unique_ptr StatementGenerator::GenerateAttachUse() { + auto multi_statement = make_uniq(); + multi_statement->statements.push_back(std::move(GenerateAttach())); + multi_statement->statements.push_back(std::move(GenerateSet())); + return multi_statement; +} + +//===--------------------------------------------------------------------===// +// Generate Detach Info +//===--------------------------------------------------------------------===// + +unique_ptr StatementGenerator::GenerateDetachInfo() { + auto info = make_uniq(); + if (RandomPercentage(20)) { + info->name = "RANDOM_NAME_" + RandomString(15); + } else { + info->name = GetRandomAttachedDataBase(); + } + return info; +} + +std::string StatementGenerator::GetRandomAttachedDataBase() { + auto state = GetDatabaseState(context); + auto st_name = state->attached_databases[RandomValue(state->attached_databases.size())]; + auto name = st_name.get().name; + return name; +} + +//===--------------------------------------------------------------------===// +// Generate Attach Info +//===--------------------------------------------------------------------===// + +unique_ptr StatementGenerator::GenerateAttachInfo() { + auto info = make_uniq(); + auto fs = FileSystem::CreateLocal(); + // check if the directory exists + if (!fs->DirectoryExists(TESTING_DIRECTORY_NAME)) { + fs->CreateDirectory(TESTING_DIRECTORY_NAME); + } + info->name = RandomString(10); + info->path = TESTING_DIRECTORY_NAME + string("/fuzz_gen_db_") + info->name + string(".db"); + if (RandomPercentage(30)) { + info->options["READ_ONLY"] = Value(true); + } + return info; +} + //===--------------------------------------------------------------------===// // Create Info Node //===--------------------------------------------------------------------===// @@ -937,6 +1043,12 @@ unique_ptr StatementGenerator::GenerateLambda() { return make_uniq(std::move(lhs), std::move(rhs)); } +string StatementGenerator::GenerateDataBaseName() { + auto identifier = "DB" + to_string(GetIndex()); + current_relation_names.push_back(identifier); + return identifier; +} + string StatementGenerator::GenerateTableIdentifier() { auto identifier = "t" + to_string(GetIndex()); current_relation_names.push_back(identifier);