diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index de13cc2a0d37..bd7b386ec83b 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -45,100 +45,3 @@ jobs: name: artifacts path: ./out/artifacts - sqlsmith-fuzzing: - name: SQLSmith Fuzzing - if: github.repository == 'duckdb/duckdb' - runs-on: ubuntu-latest - env: - BUILD_SQLSMITH: 1 - BUILD_TPCH: 1 - BUILD_TPCDS: 1 - BUILD_PARQUET: 1 - BUILD_JEMALLOC: 1 - GEN: ninja - FUZZEROFDUCKSKEY: ${{ secrets.FUZZEROFDUCKSKEY }} - - steps: - - name: Dependencies - shell: bash - run: sudo apt-get update -y -qq && sudo apt-get install -y -qq ninja-build ccache - - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Build - shell: bash - run: | - make debug - - - name: Fuzz - shell: bash - run: | - python3 scripts/run_fuzzer.py --sqlsmith --alltypes --shell=build/debug/duckdb - - sqlancer-fuzzing: - name: SQLancer Fuzzing - if: github.repository == 'duckdb/duckdb' - runs-on: ubuntu-latest - env: - BUILD_SQLSMITH: 1 - BUILD_JDBC: 1 - BUILD_JEMALLOC: 1 - GEN: ninja - FUZZEROFDUCKSKEY: ${{ secrets.FUZZEROFDUCKSKEY }} - - steps: - - name: Dependencies - shell: bash - run: sudo apt-get update -y -qq && sudo apt-get install -y -qq ninja-build ccache - - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v4 - with: - python-version: '3.9' - - - name: Build SQLancer - shell: bash - run: | - git clone https://github.com/Mytherin/sqlancer - cd sqlancer - mvn package -q -DskipTests - cd .. - - - name: Setup Ccache - uses: hendrikmuhs/ccache-action@main - with: - key: ${{ github.job }} - save: ${{ github.ref == 'refs/heads/master' || github.repository != 'duckdb/duckdb' }} - - - name: Build JDBC Driver - shell: bash - run: | - mkdir -p build/jdbcdebug - cd build/jdbcdebug - cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug -DJDBC_DRIVER=1 -DBUILD_UNITTESTS=FALSE -DENABLE_SANITIZER=FALSE ../.. - cmake --build . - cd ../.. - cp build/jdbcdebug/tools/jdbc/duckdb_jdbc.jar ./sqlancer/target/lib/duckdb_jdbc-*.jar - - - name: Build Shell - shell: bash - run: | - make debug - - - name: Build Python Client - shell: bash - run: | - pip install requests numpy - cd tools/pythonpkg - python3 setup.py install --user - cd ../.. - - - name: Fuzz - shell: bash - run: | - python3 scripts/run_sqlancer.py --sqlancer=`pwd`/sqlancer --shell=build/debug/duckdb diff --git a/extension/sqlsmith/CMakeLists.txt b/extension/sqlsmith/CMakeLists.txt index 5da63cf10500..5867e5381c6a 100644 --- a/extension/sqlsmith/CMakeLists.txt +++ b/extension/sqlsmith/CMakeLists.txt @@ -6,8 +6,9 @@ include_directories(include) include_directories(third_party/sqlsmith/include) add_subdirectory(third_party) -set(SQLSMITH_SOURCES sqlsmith-extension.cpp statement_simplifier.cpp - ${SQLSMITH_OBJECT_FILES}) +set(SQLSMITH_SOURCES + sqlsmith-extension.cpp statement_generator.cpp statement_simplifier.cpp + fuzzyduck.cpp ${SQLSMITH_OBJECT_FILES}) add_library(sqlsmith_extension STATIC ${SQLSMITH_SOURCES}) set(PARAMETERS "-warnings") diff --git a/extension/sqlsmith/fuzzyduck.cpp b/extension/sqlsmith/fuzzyduck.cpp new file mode 100644 index 000000000000..ad9624c09147 --- /dev/null +++ b/extension/sqlsmith/fuzzyduck.cpp @@ -0,0 +1,112 @@ +#include "fuzzyduck.hpp" +#include "duckdb/common/random_engine.hpp" +#include "statement_generator.hpp" + +namespace duckdb { + +FuzzyDuck::FuzzyDuck(ClientContext &context) : context(context) { +} + +FuzzyDuck::~FuzzyDuck() { +} + +void FuzzyDuck::Fuzz() { + auto &random_engine = RandomEngine::Get(context); + if (seed == 0) { + seed = random_engine.NextRandomInteger(); + } + if (max_queries == 0) { + max_queries = NumericLimits::Maximum(); + } + if (!complete_log.empty()) { + auto &fs = FileSystem::GetFileSystem(context); + TryRemoveFile(complete_log); + complete_log_handle = + fs.OpenFile(complete_log, FileFlags::FILE_FLAGS_WRITE | FileFlags::FILE_FLAGS_FILE_CREATE_NEW); + } + for (idx_t i = 0; i < max_queries; i++) { + LogMessage("Query " + to_string(i) + "\n"); + auto query = GenerateQuery(); + RunQuery(std::move(query)); + } + if (complete_log_handle) { + complete_log_handle->Close(); + } +} + +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(); +} + +void FuzzyDuck::RunQuery(string query) { + LogQuery(query + ";"); + + Connection con(*context.db); + auto result = con.Query(query); + if (result->HasError()) { + LogMessage("EXECUTION ERROR: " + result->GetError()); + } else { + LogMessage("EXECUTION SUCCESS!"); + } +} + +void FuzzyDuck::TryRemoveFile(const string &path) { + auto &fs = FileSystem::GetFileSystem(context); + if (fs.FileExists(path)) { + fs.RemoveFile(path); + } +} + +void FuzzyDuck::LogMessage(const string &message) { + if (!verbose_output) { + return; + } + Printer::Print(message); +} + +void FuzzyDuck::LogTask(const string &message) { + if (verbose_output) { + LogMessage(message + "\n"); + } + LogToCurrent(message); +} + +void FuzzyDuck::LogQuery(const string &message) { + if (verbose_output) { + LogMessage(message + "\n"); + } + LogToCurrent(message); + LogToComplete(message); +} + +void FuzzyDuck::LogToCurrent(const string &message) { + if (log.empty()) { + return; + } + auto &fs = FileSystem::GetFileSystem(context); + TryRemoveFile(log); + auto file = fs.OpenFile(log, FileFlags::FILE_FLAGS_WRITE | FileFlags::FILE_FLAGS_FILE_CREATE_NEW); + file->Write((void *)message.c_str(), message.size()); + file->Sync(); + file->Close(); +} +void FuzzyDuck::LogToComplete(const string &message) { + if (!complete_log_handle) { + return; + } + complete_log_handle->Write((void *)message.c_str(), message.size()); + complete_log_handle->Write((void *)"\n", 1); + complete_log_handle->Sync(); +} + +} // namespace duckdb diff --git a/extension/sqlsmith/include/fuzzyduck.hpp b/extension/sqlsmith/include/fuzzyduck.hpp new file mode 100644 index 000000000000..c2583a03b3f5 --- /dev/null +++ b/extension/sqlsmith/include/fuzzyduck.hpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// fuzzyduck.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb.hpp" +#include "duckdb/parser/query_node.hpp" + +namespace duckdb { +struct FileHandle; + +class FuzzyDuck { +public: + FuzzyDuck(ClientContext &context); + ~FuzzyDuck(); + + ClientContext &context; + uint32_t seed = 0; + idx_t max_queries = 0; + string complete_log; + string log; + bool verbose_output = false; + +public: + void Fuzz(); + +private: + string GenerateQuery(); + void RunQuery(string query); + + void LogMessage(const string &message); + void LogTask(const string &message); + void LogQuery(const string &message); + + void LogToCurrent(const string &message); + void LogToComplete(const string &message); + + void TryRemoveFile(const string &path); + +private: + unique_ptr complete_log_handle; +}; + +} // namespace duckdb diff --git a/extension/sqlsmith/include/statement_generator.hpp b/extension/sqlsmith/include/statement_generator.hpp new file mode 100644 index 000000000000..b362eccdf315 --- /dev/null +++ b/extension/sqlsmith/include/statement_generator.hpp @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// statement_generator.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb.hpp" +#include "duckdb/parser/query_node.hpp" + +namespace duckdb { +class SQLStatement; +class SelectStatement; +class InsertStatement; +class UpdateStatement; +class DeleteStatement; +class TableRef; +class SelectNode; +class SetOperationNode; +class QueryNode; +class ParsedExpression; +class ResultModifier; +class OrderModifier; +class UpdateSetInfo; + +struct GeneratorContext; + +class StatementGenerator { +public: + constexpr static idx_t MAX_DEPTH = 10; + constexpr static idx_t MAX_EXPRESSION_DEPTH = 50; + + friend class ExpressionDepthChecker; + friend class AggregateChecker; + friend class WindowChecker; + +public: + StatementGenerator(ClientContext &context); + StatementGenerator(StatementGenerator &parent); + ~StatementGenerator(); + +public: + unique_ptr GenerateStatement(); + +private: + unique_ptr GenerateStatement(StatementType type); + + unique_ptr GenerateSelect(); + unique_ptr GenerateQueryNode(); + + void GenerateCTEs(QueryNode &node); + unique_ptr GenerateTableRef(); + unique_ptr GenerateExpression(); + + unique_ptr GenerateBaseTableRef(); + unique_ptr GenerateExpressionListRef(); + unique_ptr GenerateJoinRef(); + unique_ptr GenerateSubqueryRef(); + unique_ptr GenerateTableFunctionRef(); + unique_ptr GeneratePivotRef(); + + unique_ptr GenerateConstant(); + unique_ptr GenerateColumnRef(); + unique_ptr GenerateFunction(); + unique_ptr GenerateOperator(); + unique_ptr GenerateWindowFunction(optional_ptr function = nullptr); + unique_ptr GenerateConjunction(); + unique_ptr GenerateStar(); + unique_ptr GenerateLambda(); + unique_ptr GenerateSubquery(); + unique_ptr GenerateCast(); + unique_ptr GenerateBetween(); + unique_ptr GenerateComparison(); + unique_ptr GeneratePositionalReference(); + unique_ptr GenerateCase(); + + unique_ptr GenerateOrderBy(); + + LogicalType GenerateLogicalType(); + + idx_t RandomValue(idx_t max); + bool RandomBoolean(); + //! Returns true with a percentage change (0-100) + bool RandomPercentage(idx_t percentage); + unique_ptr RandomExpression(idx_t percentage); + + string GenerateIdentifier(); + string GenerateTableIdentifier(); + + string GenerateRelationName(); + string GenerateColumnName(); + idx_t GetIndex(); + + Value GenerateConstantValue(); + + ExpressionType GenerateComparisonType(); + +private: + ClientContext &context; + optional_ptr parent; + unique_ptr current_statement; + vector current_relation_names; + vector current_column_names; + + shared_ptr generator_context; + idx_t index = 0; + idx_t depth = 0; + idx_t expression_depth = 0; + + bool in_window = false; + bool in_aggregate = false; + + shared_ptr GetDatabaseState(ClientContext &context); + vector> GenerateChildren(idx_t min, idx_t max); + + template + const T &Choose(const vector &entries) { + if (entries.empty()) { + throw InternalException("Attempting to choose from an empty vector"); + } + return entries[RandomValue(entries.size())]; + } +}; + +} // namespace duckdb diff --git a/extension/sqlsmith/sqlsmith-extension.cpp b/extension/sqlsmith/sqlsmith-extension.cpp index 202f9f1da4b5..ee0a53c07b8a 100644 --- a/extension/sqlsmith/sqlsmith-extension.cpp +++ b/extension/sqlsmith/sqlsmith-extension.cpp @@ -3,6 +3,7 @@ #include "sqlsmith-extension.hpp" #include "sqlsmith.hh" #include "statement_simplifier.hpp" +#include "fuzzyduck.hpp" #ifndef DUCKDB_AMALGAMATION #include "duckdb/function/table_function.hpp" @@ -55,7 +56,7 @@ static duckdb::unique_ptr SQLSmithBind(ClientContext &context, Tab } static void SQLSmithFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { - auto &data = (SQLSmithFunctionData &)*data_p.bind_data; + auto &data = data_p.bind_data->CastNoConst(); if (data.finished) { return; } @@ -101,7 +102,7 @@ static duckdb::unique_ptr ReduceSQLBind(ClientContext &context, Ta } static void ReduceSQLFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { - auto &data = (ReduceSQLFunctionData &)*data_p.bind_data; + auto &data = data_p.bind_data->CastNoConst(); if (data.offset >= data.statements.size()) { // finished returning values return; @@ -117,6 +118,45 @@ static void ReduceSQLFunction(ClientContext &context, TableFunctionInput &data_p output.SetCardinality(count); } +struct FuzzyDuckFunctionData : public TableFunctionData { + FuzzyDuckFunctionData(ClientContext &context) : fuzzer(context) { + } + + FuzzyDuck fuzzer; + bool finished = false; +}; + +static duckdb::unique_ptr FuzzyDuckBind(ClientContext &context, TableFunctionBindInput &input, + vector &return_types, vector &names) { + auto result = make_uniq(context); + for (auto &kv : input.named_parameters) { + if (kv.first == "seed") { + result->fuzzer.seed = IntegerValue::Get(kv.second); + } else if (kv.first == "max_queries") { + result->fuzzer.max_queries = UBigIntValue::Get(kv.second); + } else if (kv.first == "complete_log") { + result->fuzzer.complete_log = StringValue::Get(kv.second); + } else if (kv.first == "log") { + result->fuzzer.log = StringValue::Get(kv.second); + } else if (kv.first == "verbose_output") { + result->fuzzer.verbose_output = BooleanValue::Get(kv.second); + } + } + return_types.emplace_back(LogicalType::BOOLEAN); + names.emplace_back("Success"); + return std::move(result); +} + +static void FuzzyDuckFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { + auto &data = data_p.bind_data->CastNoConst(); + if (data.finished) { + return; + } + + data.fuzzer.Fuzz(); + data.finished = true; +} + void SQLSmithExtension::Load(DuckDB &db) { auto &db_instance = *db.instance; @@ -131,6 +171,14 @@ void SQLSmithExtension::Load(DuckDB &db) { sqlsmith_func.named_parameters["log"] = LogicalType::VARCHAR; ExtensionUtil::RegisterFunction(db_instance, sqlsmith_func); + TableFunction fuzzy_duck_fun("fuzzyduck", {}, FuzzyDuckFunction, FuzzyDuckBind); + fuzzy_duck_fun.named_parameters["seed"] = LogicalType::INTEGER; + fuzzy_duck_fun.named_parameters["max_queries"] = LogicalType::UBIGINT; + fuzzy_duck_fun.named_parameters["log"] = LogicalType::VARCHAR; + fuzzy_duck_fun.named_parameters["complete_log"] = LogicalType::VARCHAR; + fuzzy_duck_fun.named_parameters["verbose_output"] = LogicalType::BOOLEAN; + ExtensionUtil::RegisterFunction(db_instance, fuzzy_duck_fun); + TableFunction reduce_sql_function("reduce_sql_statement", {LogicalType::VARCHAR}, ReduceSQLFunction, ReduceSQLBind); ExtensionUtil::RegisterFunction(db_instance, reduce_sql_function); } diff --git a/extension/sqlsmith/statement_generator.cpp b/extension/sqlsmith/statement_generator.cpp new file mode 100644 index 000000000000..007fc6d67a67 --- /dev/null +++ b/extension/sqlsmith/statement_generator.cpp @@ -0,0 +1,1013 @@ +#include "statement_generator.hpp" + +#include "duckdb/parser/query_node/select_node.hpp" +#include "duckdb/parser/query_node/set_operation_node.hpp" +#include "duckdb/parser/tableref/list.hpp" +#include "duckdb/parser/parsed_expression_iterator.hpp" +#include "duckdb/parser/expression/list.hpp" +#include "duckdb/parser/statement/delete_statement.hpp" +#include "duckdb/parser/statement/insert_statement.hpp" +#include "duckdb/parser/statement/update_statement.hpp" +#include "duckdb/parser/statement/select_statement.hpp" +#include "duckdb/function/table/system_functions.hpp" +#include "duckdb/catalog/catalog_entry/list.hpp" +#include "duckdb/parser/expression/list.hpp" +#include "duckdb/common/random_engine.hpp" +#include "duckdb/common/types/uuid.hpp" + +namespace duckdb { + +struct GeneratorContext { + vector test_types; + vector> scalar_functions; + vector> table_functions; + vector> pragma_functions; + vector> tables_and_views; +}; + +StatementGenerator::StatementGenerator(ClientContext &context) : context(context), parent(nullptr), depth(0) { + generator_context = GetDatabaseState(context); +} + +StatementGenerator::StatementGenerator(StatementGenerator &parent_p) + : context(parent_p.context), parent(&parent_p), generator_context(parent_p.generator_context), + depth(parent_p.depth + 1) { + if (depth > MAX_DEPTH) { + throw InternalException("depth too high"); + } +} + +StatementGenerator::~StatementGenerator() { +} + +shared_ptr StatementGenerator::GetDatabaseState(ClientContext &context) { + auto result = make_shared(); + result->test_types = TestAllTypesFun::GetTestTypes(); + + auto schemas = Catalog::GetAllSchemas(context); + // extract the functions + for (auto &schema_ref : schemas) { + auto &schema = schema_ref.get(); + schema.Scan(context, CatalogType::SCALAR_FUNCTION_ENTRY, + [&](CatalogEntry &entry) { result->scalar_functions.push_back(entry); }); + schema.Scan(context, CatalogType::TABLE_FUNCTION_ENTRY, + [&](CatalogEntry &entry) { result->table_functions.push_back(entry); }); + schema.Scan(context, CatalogType::PRAGMA_FUNCTION_ENTRY, + [&](CatalogEntry &entry) { result->pragma_functions.push_back(entry); }); + schema.Scan(context, CatalogType::TABLE_ENTRY, [&](CatalogEntry &entry) { + if (entry.internal) { + return; + } + result->tables_and_views.push_back(entry); + }); + } + return result; +} + +unique_ptr StatementGenerator::GenerateStatement() { + return GenerateStatement(StatementType::SELECT_STATEMENT); +} + +unique_ptr StatementGenerator::GenerateStatement(StatementType type) { + switch (type) { + case StatementType::SELECT_STATEMENT: + return GenerateSelect(); + default: + throw InternalException("Unsupported type"); + } +} + +//===--------------------------------------------------------------------===// +// Statements +//===--------------------------------------------------------------------===// +unique_ptr StatementGenerator::GenerateSelect() { + auto select = make_uniq(); + select->node = GenerateQueryNode(); + return select; +} + +//===--------------------------------------------------------------------===// +// Query Node +//===--------------------------------------------------------------------===// +void StatementGenerator::GenerateCTEs(QueryNode &node) { + if (depth > 0) { + return; + } + while (RandomPercentage(20)) { + auto cte = make_uniq(); + cte->query = unique_ptr_cast(GenerateSelect()); + for (idx_t i = 0; i < 1 + RandomValue(10); i++) { + cte->aliases.push_back(GenerateIdentifier()); + } + node.cte_map.map.insert(make_pair(GenerateTableIdentifier(), std::move(cte))); + } +} +unique_ptr StatementGenerator::GenerateQueryNode() { + unique_ptr result; + bool is_distinct = false; + if (RandomPercentage(70)) { + // select node + auto select_node = make_uniq(); + // generate CTEs + GenerateCTEs(*select_node); + + is_distinct = RandomPercentage(30); + if (RandomPercentage(95)) { + select_node->from_table = GenerateTableRef(); + } + select_node->select_list = GenerateChildren(1, 10); + select_node->where_clause = RandomExpression(60); + select_node->having = RandomExpression(25); + if (RandomPercentage(30)) { + select_node->groups.group_expressions = GenerateChildren(1, 5); + auto group_count = select_node->groups.group_expressions.size(); + if (RandomPercentage(70)) { + // single GROUP BY + GroupingSet set; + for (idx_t i = 0; i < group_count; i++) { + set.insert(i); + } + select_node->groups.grouping_sets.push_back(std::move(set)); + } else { + // multiple grouping sets + while (true) { + GroupingSet set; + while (true) { + set.insert(RandomValue(group_count)); + if (RandomPercentage(50)) { + break; + } + } + select_node->groups.grouping_sets.push_back(std::move(set)); + if (RandomPercentage(70)) { + break; + } + } + } + } + select_node->qualify = RandomExpression(10); + select_node->aggregate_handling = + RandomPercentage(10) ? AggregateHandling::FORCE_AGGREGATES : AggregateHandling::STANDARD_HANDLING; + if (RandomPercentage(10)) { + auto sample = make_uniq(); + sample->is_percentage = RandomPercentage(50); + if (sample->is_percentage) { + sample->sample_size = Value::BIGINT(RandomValue(100)); + } else { + sample->sample_size = Value::BIGINT(RandomValue(99999)); + } + sample->method = Choose( + {SampleMethod::BERNOULLI_SAMPLE, SampleMethod::RESERVOIR_SAMPLE, SampleMethod::SYSTEM_SAMPLE}); + select_node->sample = std::move(sample); + } + result = std::move(select_node); + } else { + auto setop = make_uniq(); + GenerateCTEs(*setop); + setop->setop_type = Choose({SetOperationType::EXCEPT, SetOperationType::INTERSECT, + SetOperationType::UNION, SetOperationType::UNION_BY_NAME}); + setop->left = GenerateQueryNode(); + setop->right = GenerateQueryNode(); + switch (setop->setop_type) { + case SetOperationType::EXCEPT: + case SetOperationType::INTERSECT: + is_distinct = true; + break; + case SetOperationType::UNION: + case SetOperationType::UNION_BY_NAME: + is_distinct = RandomBoolean(); + break; + default: + throw InternalException("Unsupported set operation type"); + } + result = std::move(setop); + } + + if (is_distinct) { + result->modifiers.push_back(make_uniq()); + } + if (RandomPercentage(20)) { + result->modifiers.push_back(GenerateOrderBy()); + } + if (RandomPercentage(20)) { + if (RandomPercentage(50)) { + auto limit_percent_modifier = make_uniq(); + if (RandomPercentage(30)) { + limit_percent_modifier->limit = GenerateExpression(); + } else if (RandomPercentage(30)) { + limit_percent_modifier->offset = GenerateExpression(); + } else { + limit_percent_modifier->limit = GenerateExpression(); + limit_percent_modifier->offset = GenerateExpression(); + } + result->modifiers.push_back(std::move(limit_percent_modifier)); + } else { + auto limit_modifier = make_uniq(); + if (RandomPercentage(30)) { + limit_modifier->limit = GenerateExpression(); + } else if (RandomPercentage(30)) { + limit_modifier->offset = GenerateExpression(); + } else { + limit_modifier->limit = GenerateExpression(); + limit_modifier->offset = GenerateExpression(); + } + result->modifiers.push_back(std::move(limit_modifier)); + } + } + return result; +} + +//===--------------------------------------------------------------------===// +// Table Ref +//===--------------------------------------------------------------------===// +unique_ptr StatementGenerator::GenerateTableRef() { + if (RandomPercentage(60)) { + return GenerateBaseTableRef(); + } + if (RandomPercentage(20)) { + return GenerateExpressionListRef(); + } + if (RandomPercentage(40)) { + return GenerateJoinRef(); + } + switch (RandomValue(3)) { + case 0: + return GenerateSubqueryRef(); + case 1: + return GenerateTableFunctionRef(); + case 2: + return GeneratePivotRef(); + default: + throw InternalException("StatementGenerator::GenerateTableRef"); + } +} + +unique_ptr StatementGenerator::GenerateBaseTableRef() { + if (generator_context->tables_and_views.empty()) { + return GenerateExpressionListRef(); + } + auto &entry_ref = Choose(generator_context->tables_and_views); + auto &entry = entry_ref.get(); + auto result = make_uniq(); + idx_t column_count; + switch (entry.type) { + case CatalogType::TABLE_ENTRY: { + auto &table = entry.Cast(); + column_count = table.GetColumns().LogicalColumnCount(); + break; + } + case CatalogType::VIEW_ENTRY: { + auto &view = entry.Cast(); + column_count = view.types.size(); + break; + } + default: + throw InternalException("StatementGenerator::GenerateBaseTableRef"); + } + for (idx_t i = 0; i < column_count; i++) { + result->column_name_alias.push_back(GenerateIdentifier()); + } + result->alias = GenerateTableIdentifier(); + result->table_name = entry.name; + return result; +} + +unique_ptr StatementGenerator::GenerateExpressionListRef() { + auto result = make_uniq(); + auto column_count = 1 + RandomValue(10); + for (idx_t r = 0; r < 1 + RandomValue(10); r++) { + vector> values; + for (idx_t c = 0; c < column_count; c++) { + values.push_back(GenerateConstant()); + } + result->values.push_back(std::move(values)); + } + return result; +} + +unique_ptr StatementGenerator::GenerateJoinRef() { + JoinRefType join_ref; + if (RandomPercentage(10)) { + join_ref = JoinRefType::CROSS; + } else if (RandomPercentage(10)) { + join_ref = JoinRefType::ASOF; + } else if (RandomPercentage(10)) { + join_ref = JoinRefType::NATURAL; + } else if (RandomPercentage(10)) { + join_ref = JoinRefType::POSITIONAL; + } else { + join_ref = JoinRefType::REGULAR; + } + auto join = make_uniq(join_ref); + join->left = GenerateTableRef(); + join->right = GenerateTableRef(); + if (join_ref != JoinRefType::CROSS && join_ref != JoinRefType::NATURAL) { + if (RandomPercentage(70)) { + join->condition = GenerateExpression(); + } else { + while (true) { + join->using_columns.push_back(GenerateColumnName()); + if (RandomPercentage(50)) { + break; + } + } + } + } + join->type = Choose( + {JoinType::LEFT, JoinType::RIGHT, JoinType::INNER, JoinType::OUTER, JoinType::SEMI, JoinType::ANTI}); + return join; +} + +unique_ptr StatementGenerator::GenerateSubqueryRef() { + if (depth >= MAX_DEPTH) { + return GenerateBaseTableRef(); + } + unique_ptr subquery; + { + StatementGenerator child_generator(*this); + subquery = unique_ptr_cast(child_generator.GenerateSelect()); + for (auto &col : child_generator.current_column_names) { + current_column_names.push_back(std::move(col)); + } + } + auto result = make_uniq(std::move(subquery), GenerateTableIdentifier()); + return result; +} + +unique_ptr StatementGenerator::GenerateTableFunctionRef() { + auto function = make_uniq(); + auto &table_function_ref = Choose(generator_context->table_functions); + auto &entry = table_function_ref.get().Cast(); + auto table_function = entry.functions.GetFunctionByOffset(RandomValue(entry.functions.Size())); + + auto result = make_uniq(); + vector> children; + for (idx_t i = 0; i < table_function.arguments.size(); i++) { + children.push_back(GenerateConstant()); + } + vector names; + for (auto &e : table_function.named_parameters) { + names.push_back(e.first); + } + while (!names.empty() && RandomPercentage(50)) { + auto name = Choose(names); + names.erase(std::find(names.begin(), names.end(), name)); + auto expr = GenerateConstant(); + expr->alias = name; + children.push_back(std::move(expr)); + } + result->function = make_uniq(entry.name, std::move(children)); + for (idx_t i = 0; i < 1 + RandomValue(9); i++) { + result->column_name_alias.push_back(GenerateIdentifier()); + } + result->alias = GenerateTableIdentifier(); + return result; +} + +unique_ptr StatementGenerator::GeneratePivotRef() { + auto pivot = make_uniq(); + pivot->source = GenerateTableRef(); + bool is_pivot = RandomPercentage(50); + if (is_pivot) { + // pivot + // aggregates + while (true) { + pivot->aggregates.push_back(GenerateFunction()); + if (RandomPercentage(50)) { + break; + } + } + while (RandomPercentage(50)) { + pivot->groups.push_back(GenerateColumnName()); + } + } else { + // unpivot + while (true) { + pivot->unpivot_names.push_back(GenerateColumnName()); + if (RandomPercentage(50)) { + break; + } + } + pivot->include_nulls = RandomBoolean(); + } + while (true) { + PivotColumn col; + idx_t number_of_columns = 1 + RandomValue(2); + for (idx_t i = 0; i < number_of_columns; i++) { + if (is_pivot) { + col.pivot_expressions.push_back(GenerateExpression()); + } else { + col.unpivot_names.push_back(GenerateColumnName()); + } + } + while (true) { + PivotColumnEntry entry; + for (idx_t i = 0; i < number_of_columns; i++) { + entry.values.push_back(GenerateConstantValue()); + } + col.entries.push_back(std::move(entry)); + if (RandomPercentage(50)) { + break; + } + } + pivot->pivots.push_back(std::move(col)); + if (RandomPercentage(70)) { + break; + } + } + return pivot; +} + +//===--------------------------------------------------------------------===// +// Expressions +//===--------------------------------------------------------------------===// +class ExpressionDepthChecker { +public: + explicit ExpressionDepthChecker(StatementGenerator &generator) : generator(generator) { + generator.expression_depth++; + } + ~ExpressionDepthChecker() { + generator.expression_depth--; + } + + StatementGenerator &generator; +}; + +unique_ptr StatementGenerator::GenerateExpression() { + ExpressionDepthChecker checker(*this); + if (RandomPercentage(50) || RandomPercentage(expression_depth + depth * 5)) { + return GenerateColumnRef(); + } + if (RandomPercentage(30)) { + return GenerateConstant(); + } + if (RandomPercentage(3)) { + return GenerateSubquery(); + } + switch (RandomValue(9)) { + case 0: + return GenerateOperator(); + case 1: + return GenerateFunction(); + case 2: + return GenerateWindowFunction(); + case 3: + return GenerateConjunction(); + case 4: + return GenerateStar(); + case 5: + return GenerateCast(); + case 6: + return GenerateBetween(); + case 7: + return GenerateComparison(); + case 8: + return GeneratePositionalReference(); + default: + throw InternalException("StatementGenerator::GenerateExpression"); + } +} + +Value StatementGenerator::GenerateConstantValue() { + if (RandomPercentage(50)) { + return Value::BIGINT(RandomValue(9999)); + } + if (RandomPercentage(30)) { + return Value(UUID::ToString(UUID::GenerateRandomUUID(RandomEngine::Get(context)))); + } + auto &val = Choose(generator_context->test_types); + Value constant_val; + switch (RandomValue(3)) { + case 0: + constant_val = val.min_value; + break; + case 1: + constant_val = val.max_value; + break; + case 2: + constant_val = Value(val.type); + break; + default: + throw InternalException("StatementGenerator::GenerateConstant"); + } + return constant_val; +} + +unique_ptr StatementGenerator::GenerateConstant() { + return make_uniq(GenerateConstantValue()); +} + +LogicalType StatementGenerator::GenerateLogicalType() { + return Choose(generator_context->test_types).type; +} + +unique_ptr StatementGenerator::GenerateColumnRef() { + auto column_name = GenerateColumnName(); + if (column_name.empty()) { + return GenerateConstant(); + } + return make_uniq(std::move(column_name)); +} + +class AggregateChecker { +public: + explicit AggregateChecker(StatementGenerator &generator) : generator(generator) { + generator.in_aggregate = true; + } + ~AggregateChecker() { + generator.in_aggregate = false; + } + + StatementGenerator &generator; +}; + +unique_ptr StatementGenerator::GenerateFunction() { + // get a random function + auto &function_ref = Choose(generator_context->scalar_functions); + auto &function = function_ref.get(); + string name; + idx_t min_parameters; + idx_t max_parameters; + unique_ptr filter; + unique_ptr order_bys; + bool distinct = false; + unique_ptr checker; + vector arguments; + switch (function.type) { + case CatalogType::SCALAR_FUNCTION_ENTRY: { + auto &scalar_entry = function.Cast(); + auto actual_function = scalar_entry.functions.GetFunctionByOffset(RandomValue(scalar_entry.functions.Size())); + + name = scalar_entry.name; + arguments = actual_function.arguments; + min_parameters = actual_function.arguments.size(); + ; + max_parameters = min_parameters; + if (actual_function.varargs.id() != LogicalTypeId::INVALID) { + max_parameters += 5; + } + break; + } + case CatalogType::AGGREGATE_FUNCTION_ENTRY: { + auto &aggregate_entry = function.Cast(); + auto actual_function = + aggregate_entry.functions.GetFunctionByOffset(RandomValue(aggregate_entry.functions.Size())); + + name = aggregate_entry.name; + min_parameters = actual_function.arguments.size(); + ; + max_parameters = min_parameters; + if (actual_function.varargs.id() != LogicalTypeId::INVALID) { + max_parameters += 5; + } + if (RandomPercentage(10) && !in_window) { + return GenerateWindowFunction(&actual_function); + } + if (in_aggregate) { + // we cannot nest aggregates + return GenerateColumnRef(); + } + checker = make_uniq(*this); + filter = RandomExpression(10); + if (RandomPercentage(10)) { + // generate order by + order_bys = GenerateOrderBy(); + } + if (RandomPercentage(10)) { + distinct = true; + } + break; + } + case CatalogType::MACRO_ENTRY: { + auto ¯o_entry = function.Cast(); + name = macro_entry.name; + min_parameters = macro_entry.function->parameters.size(); + max_parameters = min_parameters; + break; + } + default: + throw InternalException("StatementGenerator::GenerateFunction"); + } + auto children = GenerateChildren(min_parameters, max_parameters); + // push lambda expressions + for (idx_t i = 0; i < arguments.size(); i++) { + if (arguments[i].id() == LogicalTypeId::LAMBDA) { + children[i] = GenerateLambda(); + } + } + // FIXME: add export_state + return make_uniq(std::move(name), std::move(children), std::move(filter), std::move(order_bys), + distinct); +} + +unique_ptr StatementGenerator::GenerateOrderBy() { + auto result = make_uniq(); + while (true) { + auto order_type = Choose({OrderType::ASCENDING, OrderType::DESCENDING, OrderType::ORDER_DEFAULT}); + auto null_type = Choose( + {OrderByNullType::NULLS_FIRST, OrderByNullType::NULLS_LAST, OrderByNullType::ORDER_DEFAULT}); + result->orders.emplace_back(order_type, null_type, GenerateExpression()); + // continue with a random chance + if (RandomPercentage(50)) { + break; + } + } + return result; +} + +unique_ptr StatementGenerator::GenerateOperator() { + ExpressionType type; + idx_t min_parameters; + idx_t max_parameters; + switch (RandomValue(9)) { + case 0: + type = ExpressionType::COMPARE_IN; + min_parameters = 2; + max_parameters = 10; + break; + case 1: + type = ExpressionType::COMPARE_NOT_IN; + min_parameters = 2; + max_parameters = 10; + break; + case 2: + type = ExpressionType::OPERATOR_NOT; + min_parameters = 1; + max_parameters = 1; + break; + case 3: + type = ExpressionType::OPERATOR_COALESCE; + min_parameters = 2; + max_parameters = 10; + break; + case 4: + type = ExpressionType::OPERATOR_IS_NULL; + min_parameters = 1; + max_parameters = 1; + break; + case 5: + type = ExpressionType::OPERATOR_IS_NOT_NULL; + min_parameters = 1; + max_parameters = 1; + break; + case 6: + type = ExpressionType::ARRAY_EXTRACT; + min_parameters = 2; + max_parameters = 2; + break; + case 7: + type = ExpressionType::ARRAY_SLICE; + min_parameters = 3; + max_parameters = 3; + break; + case 8: + type = ExpressionType::ARRAY_CONSTRUCTOR; + min_parameters = 0; + max_parameters = 10; + break; + default: + throw InternalException("StatementGenerator::GenerateOperator"); + } + auto children = GenerateChildren(min_parameters, max_parameters); + return make_uniq(type, std::move(children)); +} + +vector> StatementGenerator::GenerateChildren(idx_t min, idx_t max) { + if (min > max) { + throw InternalException("StatementGenerator::GenerateChildren min > max"); + } + vector> children; + if (min == 0 && max == 0) { + return children; + } + idx_t upper_bound = min == max ? min : min + RandomValue(max - min); + for (idx_t i = 0; i < upper_bound; i++) { + children.push_back(GenerateExpression()); + } + return children; +} + +class WindowChecker { +public: + explicit WindowChecker(StatementGenerator &generator) : generator(generator) { + generator.in_window = true; + } + ~WindowChecker() { + generator.in_window = false; + } + + StatementGenerator &generator; +}; + +unique_ptr StatementGenerator::GenerateWindowFunction(optional_ptr function) { + if (in_window) { + // we cannot nest window functions + return GenerateColumnRef(); + } + ExpressionType type; + string name; + idx_t min_parameters; + idx_t max_parameters; + if (function) { + type = ExpressionType::WINDOW_AGGREGATE; + name = function->name; + min_parameters = function->arguments.size(); + max_parameters = min_parameters; + } else { + name = Choose({"rank", "rank_dense", "percent_rank", "row_number", "first_value", "last_value", + "nth_value", "cume_dist", "lead", "lag", "ntile"}); + type = WindowExpression::WindowToExpressionType(name); + switch (type) { + case ExpressionType::WINDOW_RANK: + case ExpressionType::WINDOW_RANK_DENSE: + case ExpressionType::WINDOW_ROW_NUMBER: + case ExpressionType::WINDOW_PERCENT_RANK: + case ExpressionType::WINDOW_CUME_DIST: + min_parameters = 0; + break; + case ExpressionType::WINDOW_NTILE: + case ExpressionType::WINDOW_FIRST_VALUE: + case ExpressionType::WINDOW_LAST_VALUE: + case ExpressionType::WINDOW_LEAD: + case ExpressionType::WINDOW_LAG: + min_parameters = 1; + break; + case ExpressionType::WINDOW_NTH_VALUE: + min_parameters = 2; + break; + default: + throw InternalException("StatementGenerator::GenerateWindow"); + } + max_parameters = min_parameters; + } + WindowChecker checker(*this); + auto result = make_uniq(type, INVALID_CATALOG, INVALID_SCHEMA, std::move(name)); + result->children = GenerateChildren(min_parameters, max_parameters); + while (RandomPercentage(50)) { + result->partitions.push_back(GenerateExpression()); + } + if (RandomPercentage(30)) { + result->orders = std::move(GenerateOrderBy()->orders); + } + if (function) { + result->filter_expr = RandomExpression(30); + if (RandomPercentage(30)) { + result->ignore_nulls = true; + } + } + vector window_boundaries { + WindowBoundary::UNBOUNDED_PRECEDING, WindowBoundary::UNBOUNDED_FOLLOWING, WindowBoundary::CURRENT_ROW_RANGE, + WindowBoundary::CURRENT_ROW_ROWS, WindowBoundary::EXPR_PRECEDING_ROWS, WindowBoundary::EXPR_FOLLOWING_ROWS, + WindowBoundary::EXPR_PRECEDING_RANGE, WindowBoundary::EXPR_FOLLOWING_RANGE}; + do { + result->start = Choose(window_boundaries); + } while (result->start == WindowBoundary::UNBOUNDED_FOLLOWING); + do { + result->end = Choose(window_boundaries); + } while (result->end == WindowBoundary::UNBOUNDED_PRECEDING); + switch (result->start) { + case WindowBoundary::EXPR_PRECEDING_ROWS: + case WindowBoundary::EXPR_PRECEDING_RANGE: + case WindowBoundary::EXPR_FOLLOWING_ROWS: + case WindowBoundary::EXPR_FOLLOWING_RANGE: + result->start_expr = GenerateExpression(); + break; + default: + break; + } + switch (result->end) { + case WindowBoundary::EXPR_PRECEDING_ROWS: + case WindowBoundary::EXPR_PRECEDING_RANGE: + case WindowBoundary::EXPR_FOLLOWING_ROWS: + case WindowBoundary::EXPR_FOLLOWING_RANGE: + result->end_expr = GenerateExpression(); + break; + default: + break; + } + switch (type) { + case ExpressionType::WINDOW_LEAD: + case ExpressionType::WINDOW_LAG: + result->offset_expr = RandomExpression(30); + result->default_expr = RandomExpression(30); + break; + default: + break; + } + return result; +} + +unique_ptr StatementGenerator::RandomExpression(idx_t percentage) { + if (RandomPercentage(percentage)) { + return GenerateExpression(); + } + return nullptr; +} + +unique_ptr StatementGenerator::GenerateConjunction() { + auto left = GenerateExpression(); + auto right = GenerateExpression(); + ExpressionType conjunction_type; + switch (RandomValue(2)) { + case 0: + conjunction_type = ExpressionType::CONJUNCTION_AND; + break; + case 1: + conjunction_type = ExpressionType::CONJUNCTION_OR; + break; + default: + throw InternalException("StatementGenerator::GenerateConjunction"); + } + return make_uniq(conjunction_type, std::move(left), std::move(right)); +} + +unique_ptr StatementGenerator::GenerateStar() { + auto result = make_uniq(); + if (!current_relation_names.empty()) { + if (RandomPercentage(10)) { + result->relation_name = GenerateRelationName(); + } + } + + while (RandomPercentage(20)) { + auto column_name = GenerateColumnName(); + if (column_name.empty()) { + break; + } + result->exclude_list.insert(column_name); + } + while (RandomPercentage(20)) { + auto column_name = GenerateColumnName(); + if (column_name.empty()) { + break; + } + result->replace_list.insert(make_pair(column_name, GenerateExpression())); + } + if (RandomPercentage(50) || expression_depth > 0) { + result->columns = true; + if (RandomPercentage(50)) { + result->expr = GenerateLambda(); + } + } + return result; +} + +unique_ptr StatementGenerator::GenerateLambda() { + // generate the lambda name and add the lambda column names to the set of possibly-generated column names + auto lambda_parameter = GenerateIdentifier(); + // generate the lhs + auto lhs = make_uniq(lambda_parameter); + auto rhs = GenerateExpression(); + current_column_names.erase(std::find(current_column_names.begin(), current_column_names.end(), lambda_parameter)); + + return make_uniq(std::move(lhs), std::move(rhs)); +} + +string StatementGenerator::GenerateTableIdentifier() { + auto identifier = "t" + to_string(GetIndex()); + current_relation_names.push_back(identifier); + return identifier; +} + +string StatementGenerator::GenerateIdentifier() { + auto identifier = "c" + to_string(GetIndex()); + current_column_names.push_back(identifier); + return identifier; +} + +idx_t StatementGenerator::GetIndex() { + if (parent) { + return parent->GetIndex(); + } + return ++index; +} + +unique_ptr StatementGenerator::GenerateSubquery() { + if (depth >= MAX_DEPTH || in_window) { + return GenerateConstant(); + } + auto subquery = make_uniq(); + + { + StatementGenerator child_generator(*this); + subquery->subquery = unique_ptr_cast(child_generator.GenerateSelect()); + } + subquery->subquery_type = + Choose({SubqueryType::ANY, SubqueryType::EXISTS, SubqueryType::SCALAR, SubqueryType::NOT_EXISTS}); + if (subquery->subquery_type == SubqueryType::ANY || subquery->subquery_type == SubqueryType::SCALAR) { + subquery->child = GenerateExpression(); + } + if (subquery->subquery_type == SubqueryType::ANY) { + subquery->comparison_type = GenerateComparisonType(); + } + return subquery; +} + +unique_ptr StatementGenerator::GenerateCast() { + auto child = GenerateExpression(); + auto type = GenerateLogicalType(); + return make_uniq(std::move(type), std::move(child), RandomBoolean()); +} + +unique_ptr StatementGenerator::GenerateBetween() { + auto input = GenerateExpression(); + auto lower = GenerateExpression(); + auto upper = GenerateExpression(); + return make_uniq(std::move(input), std::move(lower), std::move(upper)); +} + +ExpressionType StatementGenerator::GenerateComparisonType() { + static vector comparisons {ExpressionType::COMPARE_EQUAL, + ExpressionType::COMPARE_NOTEQUAL, + ExpressionType::COMPARE_LESSTHAN, + ExpressionType::COMPARE_GREATERTHAN, + ExpressionType::COMPARE_LESSTHANOREQUALTO, + ExpressionType::COMPARE_GREATERTHANOREQUALTO, + ExpressionType::COMPARE_DISTINCT_FROM, + ExpressionType::COMPARE_NOT_DISTINCT_FROM}; + return Choose(comparisons); +} + +unique_ptr StatementGenerator::GenerateComparison() { + auto lhs = GenerateExpression(); + auto rhs = GenerateExpression(); + return make_uniq(GenerateComparisonType(), std::move(lhs), std::move(rhs)); +} + +unique_ptr StatementGenerator::GeneratePositionalReference() { + return make_uniq(1 + RandomValue(10)); +} + +unique_ptr StatementGenerator::GenerateCase() { + auto case_stmt = make_uniq(); + case_stmt->else_expr = GenerateExpression(); + while (true) { + CaseCheck check; + check.then_expr = GenerateExpression(); + check.when_expr = GenerateExpression(); + case_stmt->case_checks.push_back(std::move(check)); + if (RandomPercentage(50)) { + break; + } + } + return std::move(case_stmt); +} + +//===--------------------------------------------------------------------===// +// Helpers +//===--------------------------------------------------------------------===// +string StatementGenerator::GenerateRelationName() { + if (parent) { + auto parent_relation = parent->GenerateRelationName(); + if (current_relation_names.empty()) { + return parent_relation; + } + if (parent_relation.empty() || RandomPercentage(80)) { + return Choose(current_relation_names); + } + return parent_relation; + } else { + if (current_relation_names.empty()) { + return string(); + } + return Choose(current_relation_names); + } +} + +string StatementGenerator::GenerateColumnName() { + if (parent) { + auto parent_column = parent->GenerateColumnName(); + if (current_column_names.empty()) { + return parent_column; + } + if (parent_column.empty() || RandomPercentage(80)) { + return Choose(current_column_names); + } + return parent_column; + } else { + if (current_column_names.empty()) { + return string(); + } + return Choose(current_column_names); + } +} + +idx_t StatementGenerator::RandomValue(idx_t max) { + if (max == 0) { + return 0; + } + return RandomEngine::Get(context).NextRandomInteger() % max; +} + +bool StatementGenerator::RandomBoolean() { + return RandomValue(2) == 0; +} + +bool StatementGenerator::RandomPercentage(idx_t percentage) { + if (percentage > 100) { + return true; + } + return RandomValue(100) <= percentage; +} + +} // namespace duckdb diff --git a/scripts/run_fuzzer.py b/scripts/run_fuzzer.py index 759648ecb703..b9055e8dcaa3 100644 --- a/scripts/run_fuzzer.py +++ b/scripts/run_fuzzer.py @@ -15,6 +15,8 @@ for param in sys.argv: if param == '--sqlsmith': fuzzer = 'sqlsmith' + elif param == '--duckfuzz': + fuzzer = 'duckfuzz' elif param == '--alltypes': db = 'alltypes' elif param == '--tpch': @@ -25,7 +27,7 @@ seed = int(param.replace('--seed=', '')) if fuzzer is None: - print("Unrecognized fuzzer to run, expected e.g. --sqlsmith") + print("Unrecognized fuzzer to run, expected e.g. --sqlsmith or --duckfuzz") exit(1) if db is None: @@ -52,6 +54,8 @@ def create_db_script(db): def run_fuzzer_script(fuzzer): if fuzzer == 'sqlsmith': return "call sqlsmith(max_queries=${MAX_QUERIES}, seed=${SEED}, verbose_output=1, log='${LAST_LOG_FILE}', complete_log='${COMPLETE_LOG_FILE}');" + elif fuzzer == 'duckfuzz': + return "call fuzzyduck(max_queries=${MAX_QUERIES}, seed=${SEED}, verbose_output=1, log='${LAST_LOG_FILE}', complete_log='${COMPLETE_LOG_FILE}');" else: raise Exception("Unknown fuzzer type") diff --git a/src/common/types/value.cpp b/src/common/types/value.cpp index e41ecdf2b142..d169f6040b41 100644 --- a/src/common/types/value.cpp +++ b/src/common/types/value.cpp @@ -1319,6 +1319,7 @@ string Value::ToSQLString() const { case LogicalTypeId::BLOB: return "'" + ToString() + "'::" + type_.ToString(); case LogicalTypeId::VARCHAR: + case LogicalTypeId::ENUM: return "'" + StringUtil::Replace(ToString(), "'", "''") + "'"; case LogicalTypeId::STRUCT: { string ret = "{"; diff --git a/src/include/duckdb/parser/tableref/emptytableref.hpp b/src/include/duckdb/parser/tableref/emptytableref.hpp index cb495f69fdac..f73611df2c76 100644 --- a/src/include/duckdb/parser/tableref/emptytableref.hpp +++ b/src/include/duckdb/parser/tableref/emptytableref.hpp @@ -11,7 +11,7 @@ #include "duckdb/parser/tableref.hpp" namespace duckdb { -//! Represents a cross product + class EmptyTableRef : public TableRef { public: static constexpr const TableReferenceType TYPE = TableReferenceType::EMPTY; diff --git a/src/planner/binder/tableref/bind_table_function.cpp b/src/planner/binder/tableref/bind_table_function.cpp index cfa2f935dc38..a6132b1339e9 100644 --- a/src/planner/binder/tableref/bind_table_function.cpp +++ b/src/planner/binder/tableref/bind_table_function.cpp @@ -161,6 +161,9 @@ Binder::BindTableFunctionInternal(TableFunction &table_function, const string &f table_function.extra_info = "(Multi-Threaded)"; } } + } else { + throw InvalidInputException("Cannot call function \"%s\" directly - it has no bind function", + table_function.name); } if (return_types.size() != return_names.size()) { throw InternalException("Failed to bind \"%s\": return_types/names must have same size", table_function.name);