From 0c40a7638cbfb026a65503218aabe390b62e29ef Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Wed, 15 Oct 2025 12:36:03 +0100 Subject: [PATCH 1/4] Use DuckDB C API in query evaluator --- src/query_evaluator.cpp | 296 ++++++++++++++++++++++++++++++++-------- 1 file changed, 239 insertions(+), 57 deletions(-) diff --git a/src/query_evaluator.cpp b/src/query_evaluator.cpp index 50cc958..97b14fd 100644 --- a/src/query_evaluator.cpp +++ b/src/query_evaluator.cpp @@ -3,11 +3,14 @@ #include #include #include +#include +#include +#include #include #include #include -#include +#include #include "arrow_result.h" #include "options.h" @@ -23,17 +26,84 @@ DuckDbException::DuckDbException( ) : std::runtime_error(msg) {} -template -T dd_check( - T result -) { - if (result->HasError()) { - throw DuckDbException("Error doing DuckDb action. " + result->GetErrorObject().Message()); +namespace { + +struct DatabaseCloser { + void operator()(duckdb_database db) const { + if (db) { + duckdb_close(&db); + } + } +}; + +struct ConnectionCloser { + void operator()(duckdb_connection connection) const { + if (connection) { + duckdb_disconnect(&connection); + } + } +}; + +struct PreparedStatementDestroyer { + void operator()(duckdb_prepared_statement statement) const { + if (statement) { + duckdb_destroy_prepare(&statement); + } } - return result; +}; + +struct DataChunkDestroyer { + void operator()(duckdb_data_chunk chunk) const { + if (chunk) { + duckdb_destroy_data_chunk(&chunk); + } + } +}; + +struct ArrowOptionsDestroyer { + void operator()(duckdb_arrow_options options) const { + if (options) { + duckdb_destroy_arrow_options(&options); + } + } +}; + +using UniqueDatabase = std::unique_ptr, DatabaseCloser>; +using UniqueConnection = std::unique_ptr, ConnectionCloser>; +using UniquePreparedStatement = std::unique_ptr, PreparedStatementDestroyer>; +using UniqueDataChunk = std::unique_ptr, DataChunkDestroyer>; +using UniqueArrowOptions = std::unique_ptr, ArrowOptionsDestroyer>; + +struct ResultGuard { + duckdb_result *result; + ~ResultGuard() { + if (result) { + duckdb_destroy_result(result); + } + } +}; + +[[noreturn]] void throw_duckdb_error(const std::string &context, const char *error_message) { + if (error_message) { + throw DuckDbException(context + " " + error_message); + } + throw DuckDbException(context); } -static duckdb::Value infer_value_from_schema( +template +duckdb_state bind_checked(Binder &&binder, duckdb_prepared_statement statement, const std::string &context) { + const auto state = binder(); + if (state == DuckDBError) { + throw_duckdb_error(context, duckdb_prepare_error(statement)); + } + return state; +} + +} // namespace + +static void bind_inferred_value( + duckdb_prepared_statement statement, + idx_t param_index, const ColumnQueryParam ¶m, const std::unordered_map ¶m_types ) { @@ -45,7 +115,13 @@ static duckdb::Value infer_value_from_schema( if (col_type == "BIGINT" || col_type == "INTEGER" || col_type == "SMALLINT") { try { - return duckdb::Value::CreateValue(std::stoll(string_value)); + const auto integer_value = std::stoll(string_value); + bind_checked( + [&] { return duckdb_bind_int64(statement, param_index, integer_value); }, + statement, + "Could not bind inferred integer parameter."s + ); + return; } catch (const std::exception &e) { throw std::runtime_error( "Could not convert paramter "s + string_value + " to integer for column '" + param.column + "': " + @@ -55,7 +131,13 @@ static duckdb::Value infer_value_from_schema( } if (col_type == "DOUBLE" || col_type == "FLOAT") { try { - return duckdb::Value::CreateValue(std::stod(string_value)); + const auto double_value = std::stod(string_value); + bind_checked( + [&] { return duckdb_bind_double(statement, param_index, double_value); }, + statement, + "Could not bind inferred double parameter."s + ); + return; } catch (const std::exception &e) { throw std::runtime_error( "Could not convert paramter "s + string_value + " to float for column '" + param.column + "': " + e. @@ -64,73 +146,123 @@ static duckdb::Value infer_value_from_schema( } } if (col_type == "VARCHAR" || col_type == "TEXT") { - return duckdb::Value::CreateValue(param.value.get()); + const auto &string_param_value = param.value.get(); + bind_checked( + [&] { return duckdb_bind_varchar(statement, param_index, string_param_value.c_str()); }, + statement, + "Could not bind inferred text parameter."s + ); + return; } throw DuckDbException("Unable to infer type for param for column '" + param.column + "'."); } throw std::runtime_error("Could not find column '" + param.column + "' in schema."); } -static duckdb::vector convert_params_to_duckdb( +static void bind_params_to_statement( + duckdb_prepared_statement statement, const std::vector &query_params, const std::unordered_map ¶m_types ) { - duckdb::vector duckdb_params; + using std::string_literals::operator ""s; - int i = 0; + idx_t param_index = 1; for (const auto ¶m: query_params) { - duckdb::Value value; switch (param.value.type()) { case ParamType::NUMERIC: - value = duckdb::Value::CreateValue(param.value.get()); + bind_checked( + [&] { return duckdb_bind_int64(statement, param_index, param.value.get()); }, + statement, + "Could not bind numeric parameter."s + ); break; - case ParamType::TEXT: - value = duckdb::Value::CreateValue(param.value.get()); + case ParamType::TEXT: { + const auto &string_value = param.value.get(); + bind_checked( + [&] { return duckdb_bind_varchar(statement, param_index, string_value.c_str()); }, + statement, + "Could not bind text parameter."s + ); break; + } case ParamType::UNKNOWN: - value = infer_value_from_schema(param, param_types); + bind_inferred_value(statement, param_index, param, param_types); break; default: - throw DuckDbException("Unable to convert param " + std::to_string(i) + " to DuckDb type."); + throw DuckDbException("Unable to convert param " + std::to_string(param_index - 1) + " to DuckDb type."); } - duckdb_params.push_back(value); - ++i; + ++param_index; } - - return duckdb_params; } static std::shared_ptr duckdb_schema_to_arrow( - const std::unique_ptr &result + duckdb_connection connection, + duckdb_prepared_statement statement ) { - ArrowSchema duck_arrow_schema{}; - duckdb::ArrowConverter::ToArrowSchema(&duck_arrow_schema, result->types, result->names, result->client_properties); + duckdb_arrow_options raw_arrow_options = nullptr; + if (duckdb_connection_get_arrow_options(connection, &raw_arrow_options) == DuckDBError || !raw_arrow_options) { + throw DuckDbException("Could not fetch DuckDB Arrow options."); + } + UniqueArrowOptions arrow_options(raw_arrow_options); + + const auto column_count = duckdb_prepared_statement_column_count(statement); + std::vector logical_types; + logical_types.reserve(column_count); + std::vector column_names; + column_names.reserve(column_count); + + for (idx_t idx = 0; idx < column_count; ++idx) { + logical_types.push_back(duckdb_prepared_statement_column_logical_type(statement, idx)); + column_names.push_back(duckdb_prepared_statement_column_name(statement, idx)); + } - return assign_or_raise(arrow::ImportSchema(&duck_arrow_schema)); + ArrowSchema arrow_schema{}; + duckdb_error_data error_data = duckdb_to_arrow_schema( + arrow_options.get(), + logical_types.data(), + column_names.data(), + column_count, + &arrow_schema + ); + + for (auto &logical_type: logical_types) { + duckdb_destroy_logical_type(&logical_type); + } + + if (duckdb_error_data_has_error(error_data)) { + const auto *message = duckdb_error_data_message(error_data); + duckdb_destroy_error_data(&error_data); + throw_duckdb_error("Could not convert schema to Arrow.", message); + } + duckdb_destroy_error_data(&error_data); + + return assign_or_raise(arrow::ImportSchema(&arrow_schema)); } std::shared_ptr chunk_to_record_batch( - const std::unique_ptr &data_chunk, - std::shared_ptr arrow_schema, - const std::unique_ptr &result + duckdb_arrow_options arrow_options, + duckdb_data_chunk chunk, + std::shared_ptr arrow_schema ) { ArrowArray arrow_array{}; - - // TODO: No need to recreate this multiple times - const std::unordered_map> - extension_type_cast; - duckdb::ArrowConverter::ToArrowArray(*data_chunk, &arrow_array, result->client_properties, extension_type_cast); + duckdb_error_data error_data = duckdb_data_chunk_to_arrow(arrow_options, chunk, &arrow_array); + if (duckdb_error_data_has_error(error_data)) { + const auto *message = duckdb_error_data_message(error_data); + duckdb_destroy_error_data(&error_data); + throw_duckdb_error("Could not convert chunk to Arrow.", message); + } + duckdb_destroy_error_data(&error_data); return assign_or_raise(arrow::ImportRecordBatch(&arrow_array, std::move(arrow_schema))); } static std::unordered_map get_schema( const QueryPlan &query_plan, - duckdb::Connection &conn + duckdb_connection connection ) { using namespace std::string_literals; @@ -153,15 +285,30 @@ static std::unordered_map get_schema( std::unordered_map column_types; - const auto prepared_statement = dd_check(conn.Prepare(describe_query)); - const auto result = dd_check(prepared_statement->Execute()); + duckdb_result result{}; + if (duckdb_query(connection, describe_query.c_str(), &result) == DuckDBError) { + const auto *error_message = duckdb_result_error(&result); + duckdb_destroy_result(&result); + throw_duckdb_error("Error executing DESCRIBE query.", error_message); + } - for (auto data_chunk = result->Fetch(); data_chunk && data_chunk->size() > 0; data_chunk = result->Fetch()) { - for (duckdb::idx_t row = 0; row < data_chunk->size(); ++row) { - const auto column_name = data_chunk->GetValue(0, row).ToString(); - const auto column_type = data_chunk->GetValue(1, row).ToString(); - column_types[column_name] = column_type; + ResultGuard result_guard{&result}; + + const auto row_count = duckdb_row_count(&result); + for (idx_t row = 0; row < row_count; ++row) { + std::unique_ptr column_name( + duckdb_value_varchar(&result, 0, row), + duckdb_free + ); + std::unique_ptr column_type( + duckdb_value_varchar(&result, 1, row), + duckdb_free + ); + + if (!column_name || !column_type) { + throw DuckDbException("Failed to read schema information from DuckDB."); } + column_types[column_name.get()] = column_type.get(); } return column_types; @@ -180,26 +327,61 @@ ExitStatus evaluate_query( return ExitStatus::QUERY_GENERATION_ERROR; } - duckdb::DuckDB db(nullptr); - duckdb::Connection con(db); + duckdb_database raw_db = nullptr; + if (duckdb_open(nullptr, &raw_db) == DuckDBError) { + throw DuckDbException("Failed to open DuckDB in-memory database."); + } + UniqueDatabase database(raw_db); + + duckdb_connection raw_connection = nullptr; + if (duckdb_connect(database.get(), &raw_connection) == DuckDBError) { + throw DuckDbException("Failed to connect to DuckDB database."); + } + UniqueConnection connection(raw_connection); auto [query_str, query_params] = *query; try { - const auto param_types = get_schema(query_plan, con); + const auto param_types = get_schema(query_plan, connection.get()); + + duckdb_prepared_statement raw_statement = nullptr; + if (duckdb_prepare(connection.get(), query_str.c_str(), &raw_statement) == DuckDBError) { + const auto *error_message = raw_statement ? duckdb_prepare_error(raw_statement) : nullptr; + if (raw_statement) { + duckdb_destroy_prepare(&raw_statement); + } + throw_duckdb_error("Failed to prepare statement.", error_message); + } + UniquePreparedStatement prepared_statement(raw_statement); + + bind_params_to_statement(prepared_statement.get(), query_params, param_types); - const auto prepared_statement = dd_check(con.Prepare(query_str)); - auto duckdb_params = convert_params_to_duckdb(query_params, param_types); - const auto result = dd_check(prepared_statement->Execute(duckdb_params, true)); - const auto arrow_schema = duckdb_schema_to_arrow(result); + duckdb_result result{}; + if (duckdb_execute_prepared(prepared_statement.get(), &result) == DuckDBError) { + const auto *error_message = duckdb_result_error(&result); + duckdb_destroy_result(&result); + throw_duckdb_error("Error executing prepared statement.", error_message); + } + + ResultGuard result_guard{&result}; + + duckdb_arrow_options raw_arrow_options = duckdb_result_get_arrow_options(&result); + if (!raw_arrow_options) { + throw DuckDbException("DuckDB did not provide Arrow options for the result."); + } + UniqueArrowOptions arrow_options(raw_arrow_options); + + const auto arrow_schema = duckdb_schema_to_arrow(connection.get(), prepared_statement.get()); const auto writer = writer_factory(arrow_schema); - while (true) { - auto data_chunk = result->Fetch(); - if (!data_chunk || data_chunk->size() == 0) { - break; + const auto chunk_count = duckdb_result_chunk_count(result); + for (idx_t chunk_index = 0; chunk_index < chunk_count; ++chunk_index) { + duckdb_data_chunk raw_chunk = duckdb_result_get_chunk(result, chunk_index); + UniqueDataChunk chunk(raw_chunk); + if (!chunk || duckdb_data_chunk_get_size(chunk.get()) == 0) { + continue; } - const auto batch_result = chunk_to_record_batch(data_chunk, arrow_schema, result); + const auto batch_result = chunk_to_record_batch(arrow_options.get(), chunk.get(), arrow_schema); writer->write(batch_result); } writer->flush(); From 1944b19a1a16899c16399ceaf08ecca2a70cf20e Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Wed, 15 Oct 2025 13:13:48 +0100 Subject: [PATCH 2/4] Fix parameter conversion error messages --- src/query_evaluator.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/query_evaluator.cpp b/src/query_evaluator.cpp index 97b14fd..8cd0786 100644 --- a/src/query_evaluator.cpp +++ b/src/query_evaluator.cpp @@ -124,7 +124,7 @@ static void bind_inferred_value( return; } catch (const std::exception &e) { throw std::runtime_error( - "Could not convert paramter "s + string_value + " to integer for column '" + param.column + "': " + + "Could not convert parameter "s + string_value + " to integer for column '" + param.column + "': " + e.what() ); } @@ -140,8 +140,8 @@ static void bind_inferred_value( return; } catch (const std::exception &e) { throw std::runtime_error( - "Could not convert paramter "s + string_value + " to float for column '" + param.column + "': " + e. - what() + "Could not convert parameter "s + string_value + " to float for column '" + param.column + "': " + + e.what() ); } } From 5989bed81d74ae605abd357e6d2a38aa3c78de77 Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Wed, 15 Oct 2025 13:13:53 +0100 Subject: [PATCH 3/4] Improve DuckDB C API RAII usage --- src/query_evaluator.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/query_evaluator.cpp b/src/query_evaluator.cpp index 8cd0786..9e3be1c 100644 --- a/src/query_evaluator.cpp +++ b/src/query_evaluator.cpp @@ -203,7 +203,8 @@ static std::shared_ptr duckdb_schema_to_arrow( duckdb_prepared_statement statement ) { duckdb_arrow_options raw_arrow_options = nullptr; - if (duckdb_connection_get_arrow_options(connection, &raw_arrow_options) == DuckDBError || !raw_arrow_options) { + duckdb_connection_get_arrow_options(connection, &raw_arrow_options); + if (!raw_arrow_options) { throw DuckDbException("Could not fetch DuckDB Arrow options."); } UniqueArrowOptions arrow_options(raw_arrow_options); @@ -286,14 +287,12 @@ static std::unordered_map get_schema( std::unordered_map column_types; duckdb_result result{}; + ResultGuard result_guard{&result}; if (duckdb_query(connection, describe_query.c_str(), &result) == DuckDBError) { const auto *error_message = duckdb_result_error(&result); - duckdb_destroy_result(&result); throw_duckdb_error("Error executing DESCRIBE query.", error_message); } - ResultGuard result_guard{&result}; - const auto row_count = duckdb_row_count(&result); for (idx_t row = 0; row < row_count; ++row) { std::unique_ptr column_name( @@ -344,26 +343,22 @@ ExitStatus evaluate_query( const auto param_types = get_schema(query_plan, connection.get()); duckdb_prepared_statement raw_statement = nullptr; - if (duckdb_prepare(connection.get(), query_str.c_str(), &raw_statement) == DuckDBError) { - const auto *error_message = raw_statement ? duckdb_prepare_error(raw_statement) : nullptr; - if (raw_statement) { - duckdb_destroy_prepare(&raw_statement); - } + const auto prepare_state = duckdb_prepare(connection.get(), query_str.c_str(), &raw_statement); + UniquePreparedStatement prepared_statement(raw_statement); + if (prepare_state == DuckDBError) { + const auto *error_message = prepared_statement ? duckdb_prepare_error(prepared_statement.get()) : nullptr; throw_duckdb_error("Failed to prepare statement.", error_message); } - UniquePreparedStatement prepared_statement(raw_statement); bind_params_to_statement(prepared_statement.get(), query_params, param_types); duckdb_result result{}; + ResultGuard result_guard{&result}; if (duckdb_execute_prepared(prepared_statement.get(), &result) == DuckDBError) { const auto *error_message = duckdb_result_error(&result); - duckdb_destroy_result(&result); throw_duckdb_error("Error executing prepared statement.", error_message); } - ResultGuard result_guard{&result}; - duckdb_arrow_options raw_arrow_options = duckdb_result_get_arrow_options(&result); if (!raw_arrow_options) { throw DuckDbException("DuckDB did not provide Arrow options for the result."); From bca991147ba14d7fd38be78de24510ae8eb77f3a Mon Sep 17 00:00:00 2001 From: Stefans Mezulis Date: Wed, 15 Oct 2025 17:17:42 +0100 Subject: [PATCH 4/4] Clang-tidy --- src/cut.cpp | 4 ++ src/eval.cpp | 6 +- src/grep.cpp | 4 +- src/head.cpp | 4 +- src/join.cpp | 7 ++- src/options.h | 4 +- src/query_evaluator.cpp | 126 +++++++++++++++++++++++++--------------- src/sort.cpp | 4 +- src/writer.cpp | 11 ++-- 9 files changed, 106 insertions(+), 64 deletions(-) diff --git a/src/cut.cpp b/src/cut.cpp index 5c419a7..be789b9 100644 --- a/src/cut.cpp +++ b/src/cut.cpp @@ -45,6 +45,10 @@ int main( std::cerr << "Unable to parse query plan from standard input.\n"; return 1; } + if (!query_plan->select) { + std::cerr << "No 'SELECT' clause present in query plan.\n"; + return 1; + } query_plan->select.emplace( query_plan->select->get_tablename(), diff --git a/src/eval.cpp b/src/eval.cpp index 09306e1..8808ed9 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -28,8 +28,8 @@ class EvalOptions final : public Options { bool parse( const int argc, - const char *argv[] - ) override { // NOLINT(*-avoid-c-arrays) + const char *argv[] // NOLINT(*-avoid-c-arrays) + ) override { if (bool const parent_result = Options::parse(argc, argv); !parent_result) { return parent_result; } @@ -137,7 +137,7 @@ int main( } const auto [query_str, query_params] = *query; std::cout << query_str << '\n'; - for (const auto &[column, value] : query_params) { + for (const auto &[column, value]: query_params) { std::cout << "-- Colum " << column << ": " << value << '\n'; } return static_cast(ExitStatus::SUCCESS); diff --git a/src/grep.cpp b/src/grep.cpp index ea19b5c..d57c1f7 100644 --- a/src/grep.cpp +++ b/src/grep.cpp @@ -28,8 +28,8 @@ class GrepOptions final : public Options { bool parse( const int argc, - const char *argv[] - ) override { // NOLINT(*-avoid-c-arrays) + const char *argv[] // NOLINT(*-avoid-c-arrays) + ) override { if (bool const parent_result = Options::parse(argc, argv); !parent_result) { return parent_result; } diff --git a/src/head.cpp b/src/head.cpp index 7c7350e..3642a70 100644 --- a/src/head.cpp +++ b/src/head.cpp @@ -23,8 +23,8 @@ class HeadOptions final : public Options { bool parse( const int argc, - const char *argv[] - ) override { // NOLINT(*-avoid-c-arrays) + const char *argv[] // NOLINT(*-avoid-c-arrays) + ) override { if (bool const parent_result = Options::parse(argc, argv); !parent_result) { return parent_result; } diff --git a/src/join.cpp b/src/join.cpp index 90b50a9..d806de9 100644 --- a/src/join.cpp +++ b/src/join.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -61,7 +62,7 @@ class JoinOptions final : public Options { void parse_option_switches( std::vector &conditions ) const { - const auto num_args = std::min(left_.size(), std::min(right_.size(), predicate_.size())); + const auto num_args = std::min({left_.size(), right_.size(), predicate_.size()}); if (left_.size() != num_args || right_.size() != num_args || predicate_.size() != num_args) { std::cerr << "Number of left, right and predicate arguments must be the same. Got " << left_.size() << ", " << right_.size() << " and " << predicate_.size() << ". Using the first " << num_args << "\n"; @@ -84,8 +85,8 @@ class JoinOptions final : public Options { conditions.push_back( JoinCondition{ .left = positional_args_[i * 3], - .predicate = positional_args_[i * 3 + 1], - .right = positional_args_[i * 3 + 2] + .predicate = positional_args_[(i * 3) + 1], + .right = positional_args_[(i * 3) + 2] } ); } diff --git a/src/options.h b/src/options.h index 646e4bd..6c05cae 100644 --- a/src/options.h +++ b/src/options.h @@ -38,8 +38,8 @@ class Options { virtual bool parse( const int argc, - const char *argv[] - ) { // NOLINT(*-avoid-c-arrays) + const char *argv[] // NOLINT(*-avoid-c-arrays) + ) { namespace po = boost::program_options; try { diff --git a/src/query_evaluator.cpp b/src/query_evaluator.cpp index 9e3be1c..3813301 100644 --- a/src/query_evaluator.cpp +++ b/src/query_evaluator.cpp @@ -27,42 +27,51 @@ DuckDbException::DuckDbException( std::runtime_error(msg) {} namespace { - struct DatabaseCloser { - void operator()(duckdb_database db) const { - if (db) { + void operator()( + duckdb_database db + ) const { + if (db != nullptr) { duckdb_close(&db); } } }; struct ConnectionCloser { - void operator()(duckdb_connection connection) const { - if (connection) { + void operator()( + duckdb_connection connection + ) const { + if (connection != nullptr) { duckdb_disconnect(&connection); } } }; struct PreparedStatementDestroyer { - void operator()(duckdb_prepared_statement statement) const { - if (statement) { + void operator()( + duckdb_prepared_statement statement + ) const { + if (statement != nullptr) { duckdb_destroy_prepare(&statement); } } }; struct DataChunkDestroyer { - void operator()(duckdb_data_chunk chunk) const { - if (chunk) { + void operator()( + duckdb_data_chunk chunk + ) const { + if (chunk != nullptr) { duckdb_destroy_data_chunk(&chunk); } } }; struct ArrowOptionsDestroyer { - void operator()(duckdb_arrow_options options) const { - if (options) { + void operator()( + duckdb_arrow_options options + ) const { + if (options != nullptr) { duckdb_destroy_arrow_options(&options); } } @@ -70,40 +79,53 @@ struct ArrowOptionsDestroyer { using UniqueDatabase = std::unique_ptr, DatabaseCloser>; using UniqueConnection = std::unique_ptr, ConnectionCloser>; -using UniquePreparedStatement = std::unique_ptr, PreparedStatementDestroyer>; +using UniquePreparedStatement = std::unique_ptr, + PreparedStatementDestroyer>; using UniqueDataChunk = std::unique_ptr, DataChunkDestroyer>; using UniqueArrowOptions = std::unique_ptr, ArrowOptionsDestroyer>; struct ResultGuard { duckdb_result *result; + + ResultGuard(const ResultGuard &) = delete; + ResultGuard(ResultGuard &&) = delete; + ResultGuard &operator=(const ResultGuard &) = delete; + ResultGuard &operator=(ResultGuard &&) = delete; + ~ResultGuard() { - if (result) { + if (result != nullptr) { duckdb_destroy_result(result); } } }; -[[noreturn]] void throw_duckdb_error(const std::string &context, const char *error_message) { - if (error_message) { +[[noreturn]] void throw_duckdb_error( + const std::string &context, + const char *error_message +) { + if (error_message != nullptr) { throw DuckDbException(context + " " + error_message); } throw DuckDbException(context); } template -duckdb_state bind_checked(Binder &&binder, duckdb_prepared_statement statement, const std::string &context) { +duckdb_state bind_checked( + const Binder &binder, + const duckdb_prepared_statement statement, + const std::string &context +) { const auto state = binder(); if (state == DuckDBError) { throw_duckdb_error(context, duckdb_prepare_error(statement)); } return state; } - } // namespace static void bind_inferred_value( - duckdb_prepared_statement statement, - idx_t param_index, + const duckdb_prepared_statement statement, + const idx_t param_index, const ColumnQueryParam ¶m, const std::unordered_map ¶m_types ) { @@ -117,7 +139,9 @@ static void bind_inferred_value( try { const auto integer_value = std::stoll(string_value); bind_checked( - [&] { return duckdb_bind_int64(statement, param_index, integer_value); }, + [&] { + return duckdb_bind_int64(statement, param_index, integer_value); + }, statement, "Could not bind inferred integer parameter."s ); @@ -133,22 +157,26 @@ static void bind_inferred_value( try { const auto double_value = std::stod(string_value); bind_checked( - [&] { return duckdb_bind_double(statement, param_index, double_value); }, + [&] { + return duckdb_bind_double(statement, param_index, double_value); + }, statement, "Could not bind inferred double parameter."s ); return; } catch (const std::exception &e) { throw std::runtime_error( - "Could not convert parameter "s + string_value + " to float for column '" + param.column + "': " + - e.what() + "Could not convert parameter "s + string_value + " to float for column '" + param.column + "': " + e + .what() ); } } if (col_type == "VARCHAR" || col_type == "TEXT") { const auto &string_param_value = param.value.get(); bind_checked( - [&] { return duckdb_bind_varchar(statement, param_index, string_param_value.c_str()); }, + [&] { + return duckdb_bind_varchar(statement, param_index, string_param_value.c_str()); + }, statement, "Could not bind inferred text parameter."s ); @@ -160,7 +188,7 @@ static void bind_inferred_value( } static void bind_params_to_statement( - duckdb_prepared_statement statement, + const duckdb_prepared_statement statement, const std::vector &query_params, const std::unordered_map ¶m_types ) { @@ -171,7 +199,9 @@ static void bind_params_to_statement( switch (param.value.type()) { case ParamType::NUMERIC: bind_checked( - [&] { return duckdb_bind_int64(statement, param_index, param.value.get()); }, + [&] { + return duckdb_bind_int64(statement, param_index, param.value.get()); + }, statement, "Could not bind numeric parameter."s ); @@ -180,7 +210,9 @@ static void bind_params_to_statement( case ParamType::TEXT: { const auto &string_value = param.value.get(); bind_checked( - [&] { return duckdb_bind_varchar(statement, param_index, string_value.c_str()); }, + [&] { + return duckdb_bind_varchar(statement, param_index, string_value.c_str()); + }, statement, "Could not bind text parameter."s ); @@ -192,22 +224,24 @@ static void bind_params_to_statement( break; default: - throw DuckDbException("Unable to convert param " + std::to_string(param_index - 1) + " to DuckDb type."); + throw DuckDbException( + "Unable to convert param " + std::to_string(param_index - 1) + " to DuckDb type." + ); } ++param_index; } } -static std::shared_ptr duckdb_schema_to_arrow( - duckdb_connection connection, +static auto duckdb_schema_to_arrow( + const duckdb_connection connection, duckdb_prepared_statement statement -) { +) -> std::shared_ptr { duckdb_arrow_options raw_arrow_options = nullptr; duckdb_connection_get_arrow_options(connection, &raw_arrow_options); - if (!raw_arrow_options) { + if (raw_arrow_options == nullptr) { throw DuckDbException("Could not fetch DuckDB Arrow options."); } - UniqueArrowOptions arrow_options(raw_arrow_options); + const UniqueArrowOptions arrow_options(raw_arrow_options); const auto column_count = duckdb_prepared_statement_column_count(statement); std::vector logical_types; @@ -244,11 +278,11 @@ static std::shared_ptr duckdb_schema_to_arrow( } -std::shared_ptr chunk_to_record_batch( - duckdb_arrow_options arrow_options, +auto chunk_to_record_batch( + const duckdb_arrow_options arrow_options, duckdb_data_chunk chunk, std::shared_ptr arrow_schema -) { +) -> std::shared_ptr { ArrowArray arrow_array{}; duckdb_error_data error_data = duckdb_data_chunk_to_arrow(arrow_options, chunk, &arrow_array); if (duckdb_error_data_has_error(error_data)) { @@ -287,7 +321,7 @@ static std::unordered_map get_schema( std::unordered_map column_types; duckdb_result result{}; - ResultGuard result_guard{&result}; + const ResultGuard result_guard{&result}; if (duckdb_query(connection, describe_query.c_str(), &result) == DuckDBError) { const auto *error_message = duckdb_result_error(&result); throw_duckdb_error("Error executing DESCRIBE query.", error_message); @@ -295,11 +329,11 @@ static std::unordered_map get_schema( const auto row_count = duckdb_row_count(&result); for (idx_t row = 0; row < row_count; ++row) { - std::unique_ptr column_name( + const std::unique_ptr column_name( duckdb_value_varchar(&result, 0, row), duckdb_free ); - std::unique_ptr column_type( + const std::unique_ptr column_type( duckdb_value_varchar(&result, 1, row), duckdb_free ); @@ -330,13 +364,13 @@ ExitStatus evaluate_query( if (duckdb_open(nullptr, &raw_db) == DuckDBError) { throw DuckDbException("Failed to open DuckDB in-memory database."); } - UniqueDatabase database(raw_db); + const UniqueDatabase database(raw_db); duckdb_connection raw_connection = nullptr; if (duckdb_connect(database.get(), &raw_connection) == DuckDBError) { throw DuckDbException("Failed to connect to DuckDB database."); } - UniqueConnection connection(raw_connection); + const UniqueConnection connection(raw_connection); auto [query_str, query_params] = *query; try { @@ -344,7 +378,7 @@ ExitStatus evaluate_query( duckdb_prepared_statement raw_statement = nullptr; const auto prepare_state = duckdb_prepare(connection.get(), query_str.c_str(), &raw_statement); - UniquePreparedStatement prepared_statement(raw_statement); + const UniquePreparedStatement prepared_statement(raw_statement); if (prepare_state == DuckDBError) { const auto *error_message = prepared_statement ? duckdb_prepare_error(prepared_statement.get()) : nullptr; throw_duckdb_error("Failed to prepare statement.", error_message); @@ -353,17 +387,17 @@ ExitStatus evaluate_query( bind_params_to_statement(prepared_statement.get(), query_params, param_types); duckdb_result result{}; - ResultGuard result_guard{&result}; + const ResultGuard result_guard{&result}; if (duckdb_execute_prepared(prepared_statement.get(), &result) == DuckDBError) { const auto *error_message = duckdb_result_error(&result); throw_duckdb_error("Error executing prepared statement.", error_message); } duckdb_arrow_options raw_arrow_options = duckdb_result_get_arrow_options(&result); - if (!raw_arrow_options) { + if (raw_arrow_options == nullptr) { throw DuckDbException("DuckDB did not provide Arrow options for the result."); } - UniqueArrowOptions arrow_options(raw_arrow_options); + const UniqueArrowOptions arrow_options(raw_arrow_options); const auto arrow_schema = duckdb_schema_to_arrow(connection.get(), prepared_statement.get()); const auto writer = writer_factory(arrow_schema); @@ -371,7 +405,7 @@ ExitStatus evaluate_query( const auto chunk_count = duckdb_result_chunk_count(result); for (idx_t chunk_index = 0; chunk_index < chunk_count; ++chunk_index) { duckdb_data_chunk raw_chunk = duckdb_result_get_chunk(result, chunk_index); - UniqueDataChunk chunk(raw_chunk); + const UniqueDataChunk chunk(raw_chunk); if (!chunk || duckdb_data_chunk_get_size(chunk.get()) == 0) { continue; } diff --git a/src/sort.cpp b/src/sort.cpp index d620e4b..57ec163 100644 --- a/src/sort.cpp +++ b/src/sort.cpp @@ -24,8 +24,8 @@ class SortOptions final : public Options { bool parse( const int argc, - const char *argv[] - ) override { // NOLINT(*-avoid-c-arrays) + const char *argv[] // NOLINT(*-avoid-c-arrays) + ) override { if (const bool parent_result = Options::parse(argc, argv); !parent_result) { return parent_result; } diff --git a/src/writer.cpp b/src/writer.cpp index da0db4f..8dc11bb 100644 --- a/src/writer.cpp +++ b/src/writer.cpp @@ -91,7 +91,7 @@ void ColumnarWriter::write( const auto cols = batch->columns(); std::int64_t num_rows = 0; - if (cols.size() == 0) { + if (cols.empty()) { throw std::runtime_error("No columns were provided"); } num_rows = cols[0]->length(); @@ -101,7 +101,7 @@ void ColumnarWriter::write( for (std::int64_t i = 0; i < num_rows; ++i) { for (std::size_t j = 0; j < cols.size(); ++j) { - const auto column = cols[j]; + const auto& column = cols[j]; const auto slice = column->Slice(i, 1); if (const auto status = arrow::PrettyPrint(*slice, print_options_, &stream); !status.ok()) { throw std::runtime_error("Error printing column: " + status.ToString()); @@ -124,8 +124,11 @@ void ColumnarWriter::write( void ColumnarWriter::flush() { for (const auto &row: rendered_rows_) { for (std::size_t i = 0; i < row.size(); ++i) { - const auto width = max_col_width_[i]; - const auto delimiter = i == 0 ? "" : " "; + const auto width = std::in_range(max_col_width_[i]) + ? static_cast(max_col_width_[i]) + : std::numeric_limits::max(); + + const auto *delimiter = i == 0 ? "" : " "; *stream_ << delimiter << std::left << std::setw(width); *stream_ << row[i]; }