From e298f705f210929c3a465a7a660ed3df9ea725b7 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Wed, 14 Oct 2026 18:32:13 +0000 Subject: [PATCH 01/13] Update vendored DuckDB sources to d4355d01c59 --- CMakeLists.txt | 1 + .../aggregate/distributive/arg_min_max.cpp | 10 +- .../aggregate/holistic/approx_top_k.cpp | 13 +- .../aggregate/holistic/lttb.cpp | 5 +- .../core_functions/aggregate/holistic/mad.cpp | 16 +- .../variant/variant_group_object.cpp | 12 +- .../aggregate/quantile_state.hpp | 3 +- .../include/core_functions/array_kernels.hpp | 43 +- .../scalar/array/array_functions.cpp | 76 +- .../core_functions/scalar/bit/bitstring.cpp | 4 +- .../core_functions/scalar/date/date_trunc.cpp | 4 +- .../scalar/enum/enum_functions.cpp | 24 +- .../scalar/list/list_aggregates.cpp | 83 +- .../core_functions/scalar/map/switch.cpp | 112 +- .../scalar/struct/struct_insert.cpp | 5 + .../scalar/struct/struct_update.cpp | 125 +- .../scalar/union/union_value.cpp | 12 + src/duckdb/extension/icu/icu-datefunc.cpp | 8 +- src/duckdb/extension/icu/icu-datepart.cpp | 27 +- src/duckdb/extension/icu/icu-strptime.cpp | 25 +- .../extension/json/include/json_common.hpp | 4 + src/duckdb/extension/json/json_common.cpp | 73 + .../json/json_functions/json_deep_merge.cpp | 14 +- .../json/json_functions/json_merge_patch.cpp | 8 +- .../json/json_functions/read_json.cpp | 3 +- src/duckdb/extension/json/json_reader.cpp | 15 +- src/duckdb/extension/json/json_scan.cpp | 12 +- .../extension/parquet/column_reader.cpp | 15 +- .../delta_length_byte_array_decoder.cpp | 32 +- .../parquet/include/column_reader.hpp | 4 +- .../parquet/include/parquet_int96.hpp | 26 + .../parquet/include/parquet_reader.hpp | 25 +- .../parquet/include/parquet_timestamp.hpp | 5 + .../reader/expression_column_reader.hpp | 6 +- .../include/reader/list_column_reader.hpp | 4 +- .../reader/row_number_column_reader.hpp | 6 +- .../include/reader/struct_column_reader.hpp | 2 +- .../include/reader/variant_column_reader.hpp | 2 +- .../parquet/include/thrift_tools.hpp | 19 +- .../extension/parquet/parquet_extension.cpp | 33 +- .../extension/parquet/parquet_int96.cpp | 66 + .../parquet/parquet_multi_file_info.cpp | 29 +- .../extension/parquet/parquet_reader.cpp | 137 +- .../extension/parquet/parquet_statistics.cpp | 35 +- .../extension/parquet/parquet_timestamp.cpp | 57 + .../parquet/read_single_parquet_file.cpp | 40 +- .../parquet/reader/list_column_reader.cpp | 6 +- .../parquet/reader/string_column_reader.cpp | 5 + .../parquet/reader/struct_column_reader.cpp | 6 +- .../parquet/reader/variant_column_reader.cpp | 16 +- .../extension/parquet/serialize_parquet.cpp | 2 + .../writer/primitive_column_writer.cpp | 4 +- .../parquet/writer/struct_column_writer.cpp | 2 - ...nerated_extension_loader_package_build.cpp | 8 +- .../column_dependency_manager.cpp | 9 + .../catalog_entry/duck_table_entry.cpp | 5 +- .../table_function_catalog_entry.cpp | 3 + src/duckdb/src/common/adbc/adbc.cpp | 179 +- .../src/common/compressed_file_system.cpp | 17 +- src/duckdb/src/common/csv_writer.cpp | 2 +- src/duckdb/src/common/enum_util.cpp | 58 +- .../multi_file/multi_file_column_mapper.cpp | 12 + .../common/multi_file/multi_file_reader.cpp | 152 +- .../multi_file/table_function_multi_file.cpp | 25 +- .../src/common/operator/cast_operators.cpp | 8 + src/duckdb/src/common/optional_idx.cpp | 4 - .../serializer/async_memory_governor.cpp | 8 +- .../common/serializer/async_write_queue.cpp | 20 +- src/duckdb/src/common/sort/sort.cpp | 11 +- src/duckdb/src/common/string_util.cpp | 17 +- src/duckdb/src/common/types.cpp | 281 +- src/duckdb/src/common/types/bignum.cpp | 255 +- .../types/column/column_data_allocator.cpp | 27 +- .../types/column/column_data_consumer.cpp | 1 - src/duckdb/src/common/types/date.cpp | 11 +- .../types/row/tuple_data_collection.cpp | 3 +- src/duckdb/src/common/types/type_manager.cpp | 10 + .../variant/parquet_variant_encoding.cpp | 30 +- src/duckdb/src/common/types/vector_buffer.cpp | 15 +- .../src/common/vector/dictionary_vector.cpp | 12 +- .../comparison_operators.cpp | 7 + .../src/execution/aggregate_hashtable.cpp | 30 +- .../src/execution/expression_executor.cpp | 2 + .../expression_executor/execute_case.cpp | 54 +- .../expression_executor/execute_operator.cpp | 61 + src/duckdb/src/execution/ie_join_union.cpp | 4 +- .../src/execution/index/unbound_index.cpp | 4 +- src/duckdb/src/execution/join_hashtable.cpp | 178 +- .../aggregate/physical_limited_distinct.cpp | 4 +- .../physical_perfecthash_aggregate.cpp | 4 +- .../operator/aggregate/physical_window.cpp | 83 +- .../csv_scanner/buffer_manager/csv_buffer.cpp | 25 +- .../csv_scanner/encode/csv_encoder.cpp | 6 +- .../table_function/csv_file_scanner.cpp | 6 +- .../operator/csv_scanner/util/csv_error.cpp | 14 +- .../csv_scanner/util/csv_reader_options.cpp | 6 +- .../operator/helper/physical_limit.cpp | 33 +- .../helper/physical_limit_percent.cpp | 9 +- .../operator/helper/physical_pragma.cpp | 2 +- .../helper/physical_reservoir_sample.cpp | 4 +- .../operator/helper/physical_reset.cpp | 5 +- .../operator/helper/physical_set.cpp | 13 +- .../operator/join/physical_hash_join.cpp | 295 +- .../join/physical_nested_loop_join.cpp | 12 +- .../operator/order/physical_top_n.cpp | 2 +- .../physical_batch_copy_to_file.cpp | 29 +- .../persistent/physical_copy_to_file.cpp | 10 +- .../operator/persistent/physical_delete.cpp | 73 +- .../scan/physical_column_data_scan.cpp | 99 +- .../execution/radix_partitioned_hashtable.cpp | 13 +- .../src/execution/sample/reservoir_sample.cpp | 6 +- .../aggregate/distributive/minmax.cpp | 25 +- .../aggregate/sorted_aggregate_function.cpp | 56 +- .../src/function/builtin_function_lookup.cpp | 10 +- src/duckdb/src/function/cast/array_casts.cpp | 16 +- .../function/cast/nested_to_varchar_cast.cpp | 2 +- .../function/cast/variant/from_variant.cpp | 60 +- src/duckdb/src/function/copy_blob.cpp | 3 + src/duckdb/src/function/encoding_function.cpp | 7 + src/duckdb/src/function/function.cpp | 34 + src/duckdb/src/function/function_binder.cpp | 64 +- src/duckdb/src/function/pragma_function.cpp | 3 + .../src/function/scalar/list/list_resize.cpp | 3 +- .../src/function/scalar/list/list_select.cpp | 14 +- .../function/scalar/operator/arithmetic.cpp | 8 +- .../src/function/scalar/sequence/nextval.cpp | 7 +- .../src/function/scalar/strftime_format.cpp | 3 +- .../src/function/scalar/string/like.cpp | 6 +- .../src/function/scalar/string/regexp.cpp | 3 + .../string/regexp/regexp_extract_all.cpp | 16 +- .../scalar/string/regexp/regexp_util.cpp | 3 + .../function/scalar/string/string_split.cpp | 69 +- .../src/function/scalar/string/substring.cpp | 2 +- .../scalar/system/aggregate_export.cpp | 46 +- .../scalar/system/parse_log_message.cpp | 7 + .../src/function/scalar/system/write_log.cpp | 2 + .../src/function/table/arrow_conversion.cpp | 22 +- .../src/function/table/query_function.cpp | 7 +- src/duckdb/src/function/table/read_duckdb.cpp | 31 +- .../table/system/pragma_storage_info.cpp | 6 +- .../table/system/pragma_table_info.cpp | 6 +- .../table/system/pragma_table_sample.cpp | 6 +- .../function/table/version/pragma_version.cpp | 6 +- src/duckdb/src/function/table_function.cpp | 32 +- .../window/window_aggregate_states.cpp | 18 +- .../src/function/window/window_collection.cpp | 20 +- .../window/window_distinct_aggregator.cpp | 10 +- .../window/window_merge_sort_tree.cpp | 4 +- .../function/window/window_segment_tree.cpp | 90 +- .../src/function/window/window_token_tree.cpp | 5 +- src/duckdb/src/include/duckdb.h | 28 +- .../column_dependency_manager.hpp | 3 + .../include/duckdb/common/adbc/wrappers.hpp | 2 + .../duckdb/common/clustered_aggregate.hpp | 2 +- .../duckdb/common/compressed_file_system.hpp | 5 +- .../src/include/duckdb/common/enum_util.hpp | 24 +- .../duckdb/common/enums/connection_type.hpp | 22 + .../duckdb/common/enums/result_eagerness.hpp | 20 - .../duckdb/common/enums/statement_type.hpp | 10 +- .../common/multi_file/multi_file_function.hpp | 10 + .../common/multi_file/multi_file_options.hpp | 6 + .../common/multi_file/multi_file_states.hpp | 8 + .../multi_file/table_function_multi_file.hpp | 11 +- .../src/include/duckdb/common/once_flag.hpp | 48 + .../operator/numeric_binary_operators.hpp | 6 +- .../include/duckdb/common/optional_idx.hpp | 21 +- .../serializer/async_memory_governor.hpp | 4 + .../duckdb/common/serializer/serializer.hpp | 10 +- .../include/duckdb/common/stl_allocator.hpp | 93 + .../include/duckdb/common/type_visitor.hpp | 195 +- .../src/include/duckdb/common/typed_index.hpp | 5 +- .../types/column/column_data_allocator.hpp | 1 + .../column/column_data_collection_segment.hpp | 6 +- .../types/column/column_data_consumer.hpp | 2 +- .../src/include/duckdb/common/types/date.hpp | 2 + .../duckdb/common/types/double_na_equal.hpp | 4 +- .../include/duckdb/common/types/timestamp.hpp | 10 + .../duckdb/common/types/type_manager.hpp | 2 + .../common/vector/dictionary_vector.hpp | 3 +- .../vector_operations/aggregate_executor.hpp | 63 +- .../duckdb/execution/expression_executor.hpp | 2 + .../duckdb/execution/join_hashtable.hpp | 89 +- .../duckdb/execution/merge_sort_tree.hpp | 24 +- .../operator/csv_scanner/csv_error.hpp | 5 +- .../operator/helper/physical_limit.hpp | 3 + .../operator/helper/physical_set.hpp | 2 + .../operator/join/join_filter_pushdown.hpp | 10 +- .../function/builtin_function_lookup.hpp | 4 + .../src/include/duckdb/function/function.hpp | 6 + .../duckdb/function/pragma_function.hpp | 17 + .../duckdb/function/scalar/struct_utils.hpp | 2 + .../window/window_aggregate_states.hpp | 12 +- .../function/window/window_collection.hpp | 14 +- .../duckdb/main/active_query_context.hpp | 2 + .../capi_v2/capi_v2_function_internal.hpp | 67 +- .../duckdb/main/capi_v2/capi_v2_internal.hpp | 313 +- .../main/capi_v2/capi_v2_result_internal.hpp | 64 +- .../duckdb/main/capi_v2/extension_api_v2.hpp | 965 +- .../include/duckdb/main/client_context.hpp | 39 +- .../duckdb/main/client_context_state.hpp | 54 +- src/duckdb/src/include/duckdb/main/config.hpp | 6 +- .../src/include/duckdb/main/connection.hpp | 4 + .../main/extension/extension_loader.hpp | 2 + .../include/duckdb/main/extension_entries.hpp | 11 +- .../duckdb/main/extension_install_info.hpp | 2 + .../include/duckdb/main/http/http_util.hpp | 3 + .../include/duckdb/main/query_parameters.hpp | 4 - .../include/duckdb/main/query_profiler.hpp | 5 +- .../duckdb/main/query_result_stream.hpp | 11 +- .../src/include/duckdb/main/setting_info.hpp | 29 + .../optimizer/aggregate_function_rewriter.hpp | 2 - .../optimizer/column_binding_replacer.hpp | 2 +- .../optimizer/compressed_materialization.hpp | 9 +- .../optimizer/join_order/join_predicate.hpp | 21 +- .../join_order/join_relation_set.hpp | 2 + .../join_order/query_graph_manager.hpp | 1 + .../optimizer/statistics_propagator.hpp | 4 + .../duckdb/optimizer/unnest_rewriter.hpp | 3 +- .../parallel/pipeline_broadcast_exchange.hpp | 2 +- .../duckdb/parallel/scan_read_ahead.hpp | 7 +- .../include/duckdb/parallel/task_executor.hpp | 4 +- .../duckdb/parallel/task_scheduler.hpp | 6 +- .../duckdb/parallel/task_scheduler_queue.hpp | 2 +- .../duckdb/parallel/task_scheduler_token.hpp | 14 + .../parser/expression/window_expression.hpp | 3 +- .../include/duckdb/parser/grammar_change.hpp | 15 +- .../parsed_data/alter_table_function_info.hpp | 6 +- .../duckdb/parser/parsed_expression.hpp | 3 + .../duckdb/parser/peg/inlined_grammar.hpp | 11 +- .../duckdb/parser/peg/matcher_stack.hpp | 3 + .../duckdb/parser/peg/parsed_grammar.hpp | 33 +- .../peg/transformer/peg_transformer.hpp | 32 +- .../include/duckdb/parser/qualified_name.hpp | 2 + .../src/include/duckdb/parser/query_node.hpp | 2 + .../src/include/duckdb/parser/tableref.hpp | 2 + .../src/include/duckdb/planner/binder.hpp | 3 + .../planner/bound_expression_sql_exporter.hpp | 2 +- .../planner/expression/window_range_info.hpp | 10 +- .../table_function_binder.hpp | 14 +- .../duckdb/planner/expression_nullability.hpp | 3 + .../filter/table_filter_function_helpers.hpp | 12 + .../logical_plan_sql_export_context.hpp | 19 + .../planner/logical_plan_sql_exporter.hpp | 16 +- .../duckdb/planner/logical_plan_verifier.hpp | 8 +- .../operator/logical_comparison_join.hpp | 2 + .../planner/operator/logical_delete.hpp | 3 + .../planner/operator/logical_insert.hpp | 3 + .../planner/operator/logical_merge_into.hpp | 3 + .../planner/operator/logical_update.hpp | 3 + .../logical_plan_sql_exporter_internal.hpp | 17 + .../duckdb/storage/arena_allocator.hpp | 15 + .../duckdb/storage/buffer/block_handle.hpp | 48 +- .../duckdb/storage/buffer/buffer_pool.hpp | 13 +- .../storage/checkpoint/table_data_writer.hpp | 1 + .../caching_file_system.hpp | 8 +- .../storage/standard_buffer_manager.hpp | 4 +- .../duckdb/storage/table/append_state.hpp | 2 - .../duckdb/storage/table/row_group.hpp | 5 + .../storage/table/row_group_collection.hpp | 3 +- .../duckdb/storage/table/scan_state.hpp | 1 - src/duckdb/src/include/duckdb_extension_v2.h | 1266 +- src/duckdb/src/include/duckdb_v2.h | 12678 ++++++++-------- src/duckdb/src/logging/log_manager.cpp | 1 + src/duckdb/src/main/capi/v1/helper-c.cpp | 4 + src/duckdb/src/main/capi/v1/pending-c.cpp | 13 +- src/duckdb/src/main/capi/v1/prepared-c.cpp | 6 +- src/duckdb/src/main/capi/v1/stream-c.cpp | 7 +- src/duckdb/src/main/capi/v2/capi_v2.cpp | 74 +- src/duckdb/src/main/capi/v2/capi_v2_arrow.cpp | 6 +- .../v2/capi_v2_column_data_collection.cpp | 31 +- .../src/main/capi/v2/capi_v2_config.cpp | 153 + .../src/main/capi/v2/capi_v2_connection.cpp | 72 +- .../main/capi/v2/capi_v2_custom_option.cpp | 303 + .../src/main/capi/v2/capi_v2_custom_type.cpp | 114 +- .../src/main/capi/v2/capi_v2_data_chunk.cpp | 51 +- .../src/main/capi/v2/capi_v2_extension.cpp | 5 +- .../src/main/capi/v2/capi_v2_factory.cpp | 29 + .../src/main/capi/v2/capi_v2_file_system.cpp | 123 +- src/duckdb/src/main/capi/v2/capi_v2_func.cpp | 15 +- .../main/capi/v2/capi_v2_func_aggregate.cpp | 122 +- .../src/main/capi/v2/capi_v2_func_cast.cpp | 110 +- .../src/main/capi/v2/capi_v2_func_copy.cpp | 152 +- .../src/main/capi/v2/capi_v2_func_docs.cpp | 35 + .../src/main/capi/v2/capi_v2_func_macro.cpp | 198 + .../src/main/capi/v2/capi_v2_func_pragma.cpp | 240 + .../src/main/capi/v2/capi_v2_func_scalar.cpp | 138 +- .../src/main/capi/v2/capi_v2_func_table.cpp | 294 +- .../src/main/capi/v2/capi_v2_instance.cpp | 235 +- .../src/main/capi/v2/capi_v2_log_sink.cpp | 243 + .../src/main/capi/v2/capi_v2_logging.cpp | 39 +- .../src/main/capi/v2/capi_v2_logical_type.cpp | 178 +- .../src/main/capi/v2/capi_v2_option.cpp | 79 +- .../capi/v2/capi_v2_prepared_statement.cpp | 10 +- src/duckdb/src/main/capi/v2/capi_v2_qname.cpp | 6 +- .../main/capi/v2/capi_v2_replacement_scan.cpp | 124 +- .../src/main/capi/v2/capi_v2_result.cpp | 73 +- .../src/main/capi/v2/capi_v2_statement.cpp | 84 +- src/duckdb/src/main/capi/v2/capi_v2_value.cpp | 746 +- .../src/main/capi/v2/capi_v2_vector.cpp | 45 + src/duckdb/src/main/client_context.cpp | 118 +- .../src/main/client_context_execution.cpp | 25 +- src/duckdb/src/main/config.cpp | 46 +- src/duckdb/src/main/connection.cpp | 11 +- src/duckdb/src/main/database.cpp | 2 +- .../extension/extension_install_dynamic.cpp | 33 + .../src/main/extension/extension_loader.cpp | 12 +- .../src/main/extension_install_info.cpp | 24 + .../main/external_resource_type_registry.cpp | 20 +- .../src/main/http/http_transport_manager.cpp | 2 +- src/duckdb/src/main/http/http_util.cpp | 9 + src/duckdb/src/main/query_profiler.cpp | 6 + src/duckdb/src/main/query_result.cpp | 5 +- src/duckdb/src/main/query_result_stream.cpp | 13 +- .../src/main/settings/custom_settings.cpp | 81 +- .../optimizer/aggregate_function_rewriter.cpp | 83 +- .../optimizer/build_probe_side_optimizer.cpp | 2 +- .../src/optimizer/column_binding_replacer.cpp | 5 +- .../optimizer/compressed_materialization.cpp | 22 +- .../compress_aggregate.cpp | 15 + .../optimizer/cte_join_filter_collector.cpp | 3 +- src/duckdb/src/optimizer/filter_combiner.cpp | 40 +- .../join_filter_pushdown_optimizer.cpp | 3 + .../join_order/cardinality_estimator.cpp | 9 + .../optimizer/join_order/join_predicate.cpp | 59 +- .../join_order/join_relation_set.cpp | 36 +- .../optimizer/join_order/plan_enumerator.cpp | 121 +- .../join_order/query_graph_manager.cpp | 103 +- src/duckdb/src/optimizer/optimizer.cpp | 2 +- .../optimizer/pullup/pullup_projection.cpp | 7 + .../src/optimizer/remove_unused_columns.cpp | 20 + .../expression/propagate_conjunction.cpp | 60 + .../operator/propagate_aggregate.cpp | 5 + .../statistics/operator/propagate_filter.cpp | 3 +- .../statistics/operator/propagate_join.cpp | 33 +- .../src/optimizer/statistics_propagator.cpp | 4 +- .../src/optimizer/topn_window_elimination.cpp | 2 + src/duckdb/src/optimizer/unnest_rewriter.cpp | 17 +- src/duckdb/src/parallel/executor.cpp | 7 +- .../parallel/pipeline_broadcast_exchange.cpp | 68 +- src/duckdb/src/parallel/scan_read_ahead.cpp | 14 +- src/duckdb/src/parallel/task_executor.cpp | 68 +- src/duckdb/src/parallel/task_notifier.cpp | 8 +- src/duckdb/src/parallel/task_scheduler.cpp | 23 +- .../src/parallel/task_scheduler_queue.cpp | 40 +- .../src/parser/expression/cast_expression.cpp | 6 + .../parser/expression/function_expression.cpp | 4 +- .../src/parser/expression/type_expression.cpp | 162 +- src/duckdb/src/parser/literal.cpp | 5 + .../parsed_data/alter_table_function_info.cpp | 8 +- .../create_table_function_info.cpp | 2 +- .../parser/parsed_data/extra_drop_info.cpp | 3 +- src/duckdb/src/parser/parsed_expression.cpp | 110 + .../src/parser/parsed_expression_iterator.cpp | 12 +- .../src/parser/peg/compiled_grammar.cpp | 3 +- .../src/parser/peg/dialect_extension.cpp | 3 +- src/duckdb/src/parser/peg/grammar_change.cpp | 19 +- src/duckdb/src/parser/peg/matcher_factory.cpp | 4 +- src/duckdb/src/parser/peg/matcher_stack.cpp | 7 + src/duckdb/src/parser/peg/parsed_grammar.cpp | 11 +- src/duckdb/src/parser/peg/sql_formatter.cpp | 152 +- .../peg/transformer/peg_transformer.cpp | 76 +- .../transformer/peg_transformer_factory.cpp | 26 + .../transformer/transform_create_table.cpp | 10 +- .../peg/transformer/transform_expression.cpp | 55 +- .../transform_generated_trampoline.cpp | 112 +- src/duckdb/src/parser/qualified_name.cpp | 47 +- src/duckdb/src/parser/query_node.cpp | 59 + .../parser/query_node/set_operation_node.cpp | 6 + src/duckdb/src/parser/tableref.cpp | 55 + src/duckdb/src/planner/binder.cpp | 18 +- .../bind_positional_reference_expression.cpp | 7 +- .../expression/bind_unnest_expression.cpp | 37 +- .../expression/bind_window_expression.cpp | 14 +- .../query_node/bind_trigger_expansion.cpp | 3 - .../planner/binder/statement/bind_attach.cpp | 1 - .../planner/binder/statement/bind_call.cpp | 6 +- .../planner/binder/statement/bind_connect.cpp | 2 - .../planner/binder/statement/bind_copy.cpp | 48 +- .../binder/statement/bind_copy_database.cpp | 1 - .../planner/binder/statement/bind_create.cpp | 4 +- .../binder/statement/bind_create_table.cpp | 2 - .../planner/binder/statement/bind_delete.cpp | 1 - .../planner/binder/statement/bind_detach.cpp | 1 - .../planner/binder/statement/bind_drop.cpp | 1 - .../planner/binder/statement/bind_explain.cpp | 14 +- .../planner/binder/statement/bind_export.cpp | 1 - .../statement/bind_external_resource.cpp | 5 +- .../planner/binder/statement/bind_insert.cpp | 1 - .../planner/binder/statement/bind_load.cpp | 1 - .../binder/statement/bind_logical_plan.cpp | 1 - .../binder/statement/bind_merge_into.cpp | 6 +- .../planner/binder/statement/bind_prepare.cpp | 1 - .../planner/binder/statement/bind_select.cpp | 1 - .../planner/binder/statement/bind_update.cpp | 1 - .../planner/binder/tableref/bind_pivot.cpp | 4 + .../binder/tableref/bind_table_function.cpp | 1 + .../planner/binder/tableref/plan_joinref.cpp | 7 +- src/duckdb/src/planner/collation_binding.cpp | 3 +- .../expression/bound_cast_expression.cpp | 5 + .../expression/bound_constant_expression.cpp | 16 +- .../planner/expression/window_range_info.cpp | 16 +- .../table_function_binder.cpp | 2 +- .../src/planner/expression_nullability.cpp | 6 +- .../filter/table_filter_bloom_function.cpp | 66 +- .../filter/table_filter_dynamic_function.cpp | 8 +- .../table_filter_prefix_range_function.cpp | 41 +- ...e_filter_selectivity_optional_function.cpp | 10 +- .../src/planner/logical_plan_verifier.cpp | 49 +- .../operator/logical_comparison_join.cpp | 22 + .../src/planner/operator/logical_explain.cpp | 16 +- .../bound_expression_sql_exporter.cpp | 12 +- .../sql_export/logical_plan_sql_exporter.cpp | 182 +- .../src/planner/sql_export/sql_export_cte.cpp | 72 +- .../src/planner/sql_export/sql_export_dml.cpp | 641 + .../planner/sql_export/sql_export_joins.cpp | 7 +- .../planner/sql_export/sql_export_limit.cpp | 5 + .../sql_export/sql_export_relations.cpp | 3 +- .../src/planner/statement_preprocessor.cpp | 2 +- src/duckdb/src/storage/arena_allocator.cpp | 27 + .../src/storage/buffer/block_handle.cpp | 18 +- src/duckdb/src/storage/buffer/buffer_pool.cpp | 23 +- src/duckdb/src/storage/checkpoint_manager.cpp | 2 +- .../src/storage/compression/bitpacking.cpp | 69 +- .../compression/dict_fsst/decompression.cpp | 4 + .../compression/dictionary/analyze.cpp | 2 +- .../compression/dictionary/compression.cpp | 5 +- src/duckdb/src/storage/compression/fsst.cpp | 36 +- .../storage/compression/numeric_constant.cpp | 3 + src/duckdb/src/storage/data_table.cpp | 6 +- .../caching_file_system.cpp | 57 +- .../external_file_cache.cpp | 52 +- .../storage/serialization/serialize_nodes.cpp | 2 + .../serialize_parsed_expression.cpp | 1 + .../serialization/serialize_query_node.cpp | 8 + .../serialization/serialize_tableref.cpp | 1 + .../src/storage/standard_buffer_manager.cpp | 27 +- .../statistics/distinct_statistics.cpp | 103 +- src/duckdb/src/storage/storage_manager.cpp | 2 +- src/duckdb/src/storage/table/column_data.cpp | 19 +- .../src/storage/table/column_segment.cpp | 4 +- .../src/storage/table/list_column_data.cpp | 6 +- src/duckdb/src/storage/table/row_group.cpp | 20 +- .../storage/table/row_group_collection.cpp | 22 +- src/duckdb/src/storage/table/scan_state.cpp | 2 +- .../src/storage/table/struct_column_data.cpp | 3 +- .../src/storage/table/table_statistics.cpp | 2 + src/duckdb/src/storage/wal_replay.cpp | 37 +- src/duckdb/src/storage/write_ahead_log.cpp | 4 +- .../src/transaction/transaction_context.cpp | 10 +- .../third_party/fmt/include/fmt/format.h | 2 + src/duckdb/third_party/httplib/httplib.hpp | 5 +- .../third_party/miniz/miniz_wrapper.hpp | 3 + src/duckdb/ub_src_main_capi_v2.cpp | 14 + src/duckdb/ub_src_planner_sql_export.cpp | 2 + 454 files changed, 17973 insertions(+), 11487 deletions(-) create mode 100644 src/duckdb/extension/parquet/include/parquet_int96.hpp create mode 100644 src/duckdb/extension/parquet/parquet_int96.cpp create mode 100644 src/duckdb/src/include/duckdb/common/enums/connection_type.hpp delete mode 100644 src/duckdb/src/include/duckdb/common/enums/result_eagerness.hpp create mode 100644 src/duckdb/src/include/duckdb/common/once_flag.hpp create mode 100644 src/duckdb/src/include/duckdb/common/stl_allocator.hpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_config.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_custom_option.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_factory.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_func_docs.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_func_macro.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_func_pragma.cpp create mode 100644 src/duckdb/src/main/capi/v2/capi_v2_log_sink.cpp create mode 100644 src/duckdb/src/planner/sql_export/sql_export_dml.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b68a81c81..090360c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,7 @@ set(DUCKDB_SRC_FILES src/duckdb/extension/parquet/parquet_file_metadata_cache.cpp src/duckdb/extension/parquet/parquet_float16.cpp src/duckdb/extension/parquet/parquet_geometry.cpp + src/duckdb/extension/parquet/parquet_int96.cpp src/duckdb/extension/parquet/parquet_metadata.cpp src/duckdb/extension/parquet/parquet_multi_file_info.cpp src/duckdb/extension/parquet/parquet_prefetch_cost_model.cpp diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/arg_min_max.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/arg_min_max.cpp index e8a10f9d6..4059847d2 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/arg_min_max.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/arg_min_max.cpp @@ -210,9 +210,7 @@ struct ArgMinMaxBase { auto &context = input.GetClientContext(); auto &function = input.GetBoundFunction(); auto &arguments = input.GetArguments(); - if (arguments[1]->GetReturnType().InternalType() == PhysicalType::VARCHAR) { - ExpressionBinder::PushCollation(context, arguments[1], arguments[1]->GetReturnType()); - } + ExpressionBinder::PushCollation(context, arguments[1], arguments[1]->GetReturnType()); function.GetArguments()[0] = arguments[0]->GetReturnType(); function.GetArguments()[1] = arguments[1]->GetReturnType(); function.SetReturnType(arguments[0]->GetReturnType()); @@ -379,9 +377,7 @@ struct VectorArgMinMaxBase : ArgMinMaxBase { auto &context = input.GetClientContext(); auto &function = input.GetBoundFunction(); auto &arguments = input.GetArguments(); - if (arguments[1]->GetReturnType().InternalType() == PhysicalType::VARCHAR) { - ExpressionBinder::PushCollation(context, arguments[1], arguments[1]->GetReturnType()); - } + ExpressionBinder::PushCollation(context, arguments[1], arguments[1]->GetReturnType()); function.GetArguments()[0] = arguments[0]->GetReturnType(); function.GetArguments()[1] = arguments[1]->GetReturnType(); function.SetReturnType(arguments[0]->GetReturnType()); @@ -886,6 +882,7 @@ void SpecializeArgMinMaxNullNFunction(PhysicalType val_type, PhysicalType arg_ty template unique_ptr ArgMinMaxNBind(BindAggregateFunctionInput &input) { + auto &context = input.GetClientContext(); auto &function = input.GetBoundFunction(); auto &arguments = input.GetArguments(); for (auto &arg : arguments) { @@ -893,6 +890,7 @@ unique_ptr ArgMinMaxNBind(BindAggregateFunctionInput &input) { throw ParameterNotResolvedException(); } } + ExpressionBinder::PushCollation(context, arguments[1], arguments[1]->GetReturnType()); const auto val_type = arguments[0]->GetReturnType().InternalType(); const auto arg_type = arguments[1]->GetReturnType().InternalType(); diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/approx_top_k.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/approx_top_k.cpp index ab354bb1a..4f553ece8 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/approx_top_k.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/approx_top_k.cpp @@ -510,6 +510,10 @@ void ApproxTopKImportState(AggregateImportInputData &input) { // NULL input - leave the state empty continue; } + if (FlatVector::IsNull(fields[0], i) || FlatVector::IsNull(value_lists, i) || + FlatVector::IsNull(filter_lists, i)) { + throw InvalidInputException("Invalid approx_top_k state - the k, values and filter cannot be NULL"); + } auto internal_state = make_uniq(); auto &target = *internal_state; target.Initialize(k_data[i]); @@ -537,6 +541,9 @@ void ApproxTopKImportState(AggregateImportInputData &input) { if (!inserted) { throw InvalidInputException("Invalid approx_top_k state - the state values must be unique"); } + if (FlatVector::IsNull(value_fields[1], idx)) { + throw InvalidInputException("Invalid approx_top_k state - the state counts cannot be NULL"); + } val.count = count_data[idx]; if (val.count == 0) { throw InvalidInputException("Invalid approx_top_k state - the state values must have a count > 0"); @@ -547,7 +554,11 @@ void ApproxTopKImportState(AggregateImportInputData &input) { } } for (idx_t filter_idx = 0; filter_idx < filter_entries[i].length; filter_idx++) { - target.filter[filter_idx] = filter_data[filter_entries[i].offset + filter_idx]; + const auto filter_entry_idx = filter_entries[i].offset + filter_idx; + if (FlatVector::IsNull(ListVector::GetChild(filter_lists), filter_entry_idx)) { + throw InvalidInputException("Invalid approx_top_k state - the filter values cannot be NULL"); + } + target.filter[filter_idx] = filter_data[filter_entry_idx]; } target.Verify(); state.state = internal_state.release(); diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/lttb.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/lttb.cpp index d653532fd..535dfc0fc 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/lttb.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/lttb.cpp @@ -207,7 +207,10 @@ auto LTTBFinalize(Vector &vec, AggregateFinalizeInputData &data, Vector &result, } // Reusable vector for materializing each groups points, sized up-front to the largest group. - Vector points(struct_type, MaxValue(max_v, 1)); + // A group can be the whole input, so the vector is allocated through the (tracked) base allocator. + DataChunk points_chunk; + points_chunk.Initialize(data.allocator.GetUnderlyingAllocator(), {struct_type}, MaxValue(max_v, 1)); + auto &points = points_chunk.data[0]; // Now do the selection for each list for (idx_t state_idx = 0; state_idx < count; state_idx++) { diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/mad.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/mad.cpp index 9e26ed530..51fd31c32 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/mad.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/mad.cpp @@ -24,8 +24,20 @@ struct MadAccessor { } inline RESULT_TYPE operator()(const INPUT_TYPE &input) const { - const RESULT_TYPE delta = input - UnsafeNumericCast(median); - return TryAbsOperator::Operation(delta); + if constexpr (std::is_same::value) { + // values of opposite sign (e.g. DECIMAL(38)) can be further apart than the range of a hugeint - such + // deviations are larger than any other, so saturate them + RESULT_TYPE delta; + if (!TrySubtractOperator::Operation( + input, UnsafeNumericCast(median), delta) || + delta == NumericLimits::Minimum()) { + return NumericLimits::Maximum(); + } + return TryAbsOperator::Operation(delta); + } else { + const RESULT_TYPE delta = input - UnsafeNumericCast(median); + return TryAbsOperator::Operation(delta); + } } }; diff --git a/src/duckdb/extension/core_functions/aggregate/variant/variant_group_object.cpp b/src/duckdb/extension/core_functions/aggregate/variant/variant_group_object.cpp index 9a88aca86..64c724d33 100644 --- a/src/duckdb/extension/core_functions/aggregate/variant/variant_group_object.cpp +++ b/src/duckdb/extension/core_functions/aggregate/variant/variant_group_object.cpp @@ -81,7 +81,10 @@ class VariantObjSource { return true; } - Vector packed(GetInternalBufferType(), logical_count); + // a group can be the whole input, so the flattened pairs are allocated through the (tracked) base allocator + DataChunk packed_chunk; + packed_chunk.Initialize(allocator.GetUnderlyingAllocator(), {GetInternalBufferType()}, logical_count); + auto &packed = packed_chunk.data[0]; functions.BuildListVector(state.linked_list, packed, 0); FlatVector::SetSize(packed, logical_count); @@ -89,6 +92,13 @@ class VariantObjSource { const auto &keys = entries[0]; const auto &values = entries[1]; + // keys are verified to be non-NULL on update, but the state can also be imported + const auto key_validity = keys.Validity(); + for (idx_t i = 0; key_validity.CanHaveNull() && i < key_validity.size(); i++) { + if (!key_validity.IsValid(i)) { + throw InvalidInputException("variant_group_object key cannot be NULL"); + } + } // The VARIANT type does not support duplicate keys if (const auto duplicate = HasDuplicateKeys(keys)) { throw InvalidInputException("variant_group_object contains duplicate key \"%s\"", *duplicate); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/quantile_state.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/quantile_state.hpp index 05a80ad2f..b2ffb3836 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/quantile_state.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/quantile_state.hpp @@ -42,7 +42,8 @@ struct FlattenedQuantileValues : FunctionLocalState { // (re-)allocate the cached chunk values.capacity = NextPowerOfTwo(required_capacity); values.chunk.Destroy(); - values.chunk.Initialize(Allocator::DefaultAllocator(), {type}, values.capacity); + // the flattened group can be the whole input, so it is allocated through the (tracked) base allocator + values.chunk.Initialize(finalize_data.input.allocator.GetUnderlyingAllocator(), {type}, values.capacity); } else { // re-use the allocated chunk - resetting clears the string heap of the previously flattened state values.chunk.Reset(); diff --git a/src/duckdb/extension/core_functions/include/core_functions/array_kernels.hpp b/src/duckdb/extension/core_functions/include/core_functions/array_kernels.hpp index ddcdb92b6..3d22d6216 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/array_kernels.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/array_kernels.hpp @@ -57,9 +57,50 @@ struct CosineSimilarityOp { norm_r += y * y; } - auto similarity = distance / std::sqrt(norm_l * norm_r); + const auto norm_product = norm_l * norm_r; + if (!std::isfinite(distance) || !std::isfinite(norm_product) || + norm_product < std::numeric_limits::min()) { + // the computation overflowed or underflowed (or there are NaN / infinite values) + return ScaledSimilarity(lhs_data, rhs_data, count); + } + return Clamp(distance / std::sqrt(norm_product)); + } + +private: + template + static TYPE Clamp(TYPE similarity) { + if (std::isnan(similarity)) { + return similarity; + } return std::max(static_cast(-1.0), std::min(similarity, static_cast(1.0))); } + + //! Computes the similarity with both vectors scaled by their largest absolute value, which does not change the + //! similarity, but avoids overflow and underflow + template + static TYPE ScaledSimilarity(const TYPE *lhs_data, const TYPE *rhs_data, const idx_t count) { + TYPE max_l = 0; + TYPE max_r = 0; + for (idx_t i = 0; i < count; i++) { + max_l = std::max(max_l, std::abs(lhs_data[i])); + max_r = std::max(max_r, std::abs(rhs_data[i])); + } + if (max_l == 0 || max_r == 0 || !std::isfinite(max_l) || !std::isfinite(max_r)) { + // the similarity is undefined for zero and infinite vectors + return std::numeric_limits::quiet_NaN(); + } + TYPE distance = 0; + TYPE norm_l = 0; + TYPE norm_r = 0; + for (idx_t i = 0; i < count; i++) { + const auto x = lhs_data[i] / max_l; + const auto y = rhs_data[i] / max_r; + distance += x * y; + norm_l += x * x; + norm_r += y * y; + } + return Clamp(distance / std::sqrt(norm_l * norm_r)); + } }; struct CosineDistanceOp { diff --git a/src/duckdb/extension/core_functions/scalar/array/array_functions.cpp b/src/duckdb/extension/core_functions/scalar/array/array_functions.cpp index db6ba59c5..e794f2a29 100644 --- a/src/duckdb/extension/core_functions/scalar/array/array_functions.cpp +++ b/src/duckdb/extension/core_functions/scalar/array/array_functions.cpp @@ -58,6 +58,20 @@ static void ArrayGenericBinaryResolveTypes(ResolveScalarFunctionTypesInput &inpu bound_function.GetArguments()[1] = LogicalType::ARRAY(common_type, rhs_size); } +//! Read the elements of an array into a contiguous buffer - throws if any of them are NULL +template +static void ReadArrayElements(const VectorIterator &child_values, idx_t offset, idx_t array_size, TYPE *target, + const char *side, const Identifier &func_name) { + for (idx_t i = 0; i < array_size; i++) { + auto entry = child_values[offset + i]; + if (!entry.IsValid()) { + throw InvalidInputException( + StringUtil::Format("%s: %s argument can not contain NULL values", SQLIdentifier(func_name), side)); + } + target[i] = entry.GetValue(); + } +} + //------------------------------------------------------------------------------ // Element-wise combine functions //------------------------------------------------------------------------------ @@ -91,22 +105,19 @@ static void ArrayFixedCombine(DataChunk &args, ExpressionState &state, Vector &r const auto &func_name = expr.Function().GetName(); const auto count = args.size(); - auto &lhs_child = ArrayVector::GetChildMutable(args.data[0]); - auto &rhs_child = ArrayVector::GetChildMutable(args.data[1]); + auto lhs_values = ArrayVector::GetChild(args.data[0]).Values(); + auto rhs_values = ArrayVector::GetChild(args.data[1]).Values(); auto &res_child = ArrayVector::GetChildMutable(result); - const auto &lhs_child_validity = FlatVector::Validity(lhs_child); - const auto &rhs_child_validity = FlatVector::Validity(rhs_child); - UnifiedVectorFormat lhs_format; UnifiedVectorFormat rhs_format; args.data[0].ToUnifiedFormat(lhs_format); args.data[1].ToUnifiedFormat(rhs_format); - auto lhs_data = FlatVector::GetData(lhs_child); - auto rhs_data = FlatVector::GetData(rhs_child); auto res_data = FlatVector::GetDataMutable(res_child); + TYPE lhs_data[N]; + TYPE rhs_data[N]; for (idx_t i = 0; i < count; i++) { const auto lhs_idx = lhs_format.sel->get_index(i); @@ -117,24 +128,9 @@ static void ArrayFixedCombine(DataChunk &args, ExpressionState &state, Vector &r continue; } - const auto left_offset = lhs_idx * N; - if (!lhs_child_validity.CheckAllValid(left_offset + N, left_offset)) { - throw InvalidInputException( - StringUtil::Format("%s: left argument can not contain NULL values", SQLIdentifier(func_name))); - } - - const auto right_offset = rhs_idx * N; - if (!rhs_child_validity.CheckAllValid(right_offset + N, right_offset)) { - throw InvalidInputException( - StringUtil::Format("%s: right argument can not contain NULL values", SQLIdentifier(func_name))); - } - const auto result_offset = i * N; - - const auto lhs_data_ptr = lhs_data + left_offset; - const auto rhs_data_ptr = rhs_data + right_offset; - const auto res_data_ptr = res_data + result_offset; - - OP::Operation(lhs_data_ptr, rhs_data_ptr, res_data_ptr, N); + ReadArrayElements(lhs_values, lhs_idx * N, N, lhs_data, "left", func_name); + ReadArrayElements(rhs_values, rhs_idx * N, N, rhs_data, "right", func_name); + OP::Operation(lhs_data, rhs_data, res_data + i * N, N); } if (count == 1) { @@ -154,11 +150,8 @@ static void ArrayGenericFold(DataChunk &args, ExpressionState &state, Vector &re const auto &func_name = expr.Function().GetName(); const auto count = args.size(); - auto &lhs_child = ArrayVector::GetChildMutable(args.data[0]); - auto &rhs_child = ArrayVector::GetChildMutable(args.data[1]); - - const auto &lhs_child_validity = FlatVector::Validity(lhs_child); - const auto &rhs_child_validity = FlatVector::Validity(rhs_child); + auto lhs_values = ArrayVector::GetChild(args.data[0]).Values(); + auto rhs_values = ArrayVector::GetChild(args.data[1]).Values(); UnifiedVectorFormat lhs_format; UnifiedVectorFormat rhs_format; @@ -166,12 +159,12 @@ static void ArrayGenericFold(DataChunk &args, ExpressionState &state, Vector &re args.data[0].ToUnifiedFormat(lhs_format); args.data[1].ToUnifiedFormat(rhs_format); - auto lhs_data = FlatVector::GetData(lhs_child); - auto rhs_data = FlatVector::GetData(rhs_child); auto res_data = FlatVector::GetDataMutable(result); const auto array_size = ArrayType::GetSize(args.data[0].GetType()); D_ASSERT(array_size == ArrayType::GetSize(args.data[1].GetType())); + vector lhs_data(array_size); + vector rhs_data(array_size); for (idx_t i = 0; i < count; i++) { const auto lhs_idx = lhs_format.sel->get_index(i); @@ -182,22 +175,9 @@ static void ArrayGenericFold(DataChunk &args, ExpressionState &state, Vector &re continue; } - const auto left_offset = lhs_idx * array_size; - if (!lhs_child_validity.CheckAllValid(left_offset + array_size, left_offset)) { - throw InvalidInputException( - StringUtil::Format("%s: left argument can not contain NULL values", SQLIdentifier(func_name))); - } - - const auto right_offset = rhs_idx * array_size; - if (!rhs_child_validity.CheckAllValid(right_offset + array_size, right_offset)) { - throw InvalidInputException( - StringUtil::Format("%s: right argument can not contain NULL values", SQLIdentifier(func_name))); - } - - const auto lhs_data_ptr = lhs_data + left_offset; - const auto rhs_data_ptr = rhs_data + right_offset; - - res_data[i] = OP::Operation(lhs_data_ptr, rhs_data_ptr, array_size); + ReadArrayElements(lhs_values, lhs_idx * array_size, array_size, lhs_data.data(), "left", func_name); + ReadArrayElements(rhs_values, rhs_idx * array_size, array_size, rhs_data.data(), "right", func_name); + res_data[i] = OP::Operation(lhs_data.data(), rhs_data.data(), array_size); } if (count == 1) { diff --git a/src/duckdb/extension/core_functions/scalar/bit/bitstring.cpp b/src/duckdb/extension/core_functions/scalar/bit/bitstring.cpp index a74e5568b..2a3ece121 100644 --- a/src/duckdb/extension/core_functions/scalar/bit/bitstring.cpp +++ b/src/duckdb/extension/core_functions/scalar/bit/bitstring.cpp @@ -91,7 +91,7 @@ namespace { struct GetBitOperator { template static inline TR Operation(TA input, TB n) { - if (n < 0 || (idx_t)n > Bit::BitLength(input) - 1) { + if (n < 0 || UnsafeNumericCast(n) >= Bit::BitLength(input)) { throw OutOfRangeException("bit index %s out of valid range (0..%s)", NumericHelper::ToString(n), NumericHelper::ToString(Bit::BitLength(input) - 1)); } @@ -117,7 +117,7 @@ static void SetBitOperation(DataChunk &args, ExpressionState &state, Vector &res if (new_value != 0 && new_value != 1) { throw InvalidInputException("The new bit must be 1 or 0"); } - if (n < 0 || (idx_t)n > Bit::BitLength(input) - 1) { + if (n < 0 || UnsafeNumericCast(n) >= Bit::BitLength(input)) { throw OutOfRangeException("bit index %s out of valid range (0..%s)", NumericHelper::ToString(n), NumericHelper::ToString(Bit::BitLength(input) - 1)); } diff --git a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp index 41643d8c0..e6bd79669 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp @@ -83,9 +83,7 @@ struct DateTrunc { template static inline TR Operation(TA input) { date_t date = Date::GetMondayOfCurrentWeek(input); - date.days -= (Date::ExtractISOWeekNumber(date) - 1) * Interval::DAYS_PER_WEEK; - - return date; + return Date::SubtractDays(date, (Date::ExtractISOWeekNumber(date) - 1) * Interval::DAYS_PER_WEEK); } }; diff --git a/src/duckdb/extension/core_functions/scalar/enum/enum_functions.cpp b/src/duckdb/extension/core_functions/scalar/enum/enum_functions.cpp index f493d42bd..1b2fa8618 100644 --- a/src/duckdb/extension/core_functions/scalar/enum/enum_functions.cpp +++ b/src/duckdb/extension/core_functions/scalar/enum/enum_functions.cpp @@ -21,16 +21,22 @@ static void EnumLastFunction(DataChunk &input, ExpressionState &state, Vector &r } static void EnumRangeFunction(DataChunk &input, ExpressionState &state, Vector &result) { - auto types = input.GetTypes(); - D_ASSERT(types.size() == 1); - auto enum_size = EnumType::GetSize(types[0]); - auto &enum_vector = EnumType::GetValuesInsertOrder(types[0]); - vector enum_values; - for (idx_t i = 0; i < enum_size; i++) { - enum_values.emplace_back(enum_vector.GetValue(i)); + D_ASSERT(input.ColumnCount() == 1); + auto &enum_type = input.data[0].GetType(); + auto &enum_vector = EnumType::GetValuesInsertOrder(enum_type); + auto enum_strings = enum_vector.Values(); + const auto enum_size = EnumType::GetSize(enum_type); + + // every row gets the same list: write the enum's values into the result once and make it a constant, instead of + // materializing them as Values (three copies of the enum per call, dozens of bytes per value) + { + auto writer = FlatVector::Writer>(result, 1); + idx_t enum_idx = 0; + for (auto &child_writer : writer.WriteList(enum_size)) { + child_writer.WriteValue(enum_strings[enum_idx++].GetValue()); + } } - auto val = Value::LIST(LogicalType::VARCHAR, enum_values); - result.Reference(val, count_t(input.size())); + result.SetVectorType(VectorType::CONSTANT_VECTOR); } static void EnumRangeBoundaryFunction(DataChunk &input, ExpressionState &state, Vector &result) { diff --git a/src/duckdb/extension/core_functions/scalar/list/list_aggregates.cpp b/src/duckdb/extension/core_functions/scalar/list/list_aggregates.cpp index f32f76e5a..e7c38d8f2 100644 --- a/src/duckdb/extension/core_functions/scalar/list/list_aggregates.cpp +++ b/src/duckdb/extension/core_functions/scalar/list/list_aggregates.cpp @@ -13,23 +13,13 @@ #include "duckdb/planner/expression/bound_cast_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" +#include "duckdb/planner/expression/bound_reference_expression.hpp" #include "duckdb/planner/expression_binder.hpp" namespace duckdb { namespace { -struct ListAggregatesLocalState : public FunctionLocalState { - explicit ListAggregatesLocalState(Allocator &allocator) : arena_allocator(allocator) { - } - - ArenaAllocator arena_allocator; -}; - -unique_ptr ListAggregatesInitLocalState(ExpressionState &state, const BoundFunctionExpression &expr, - FunctionData *bind_data) { - return make_uniq(BufferAllocator::Get(state.GetContext())); -} // FIXME: benchmark the use of cluster_update against using update (if applicable) unique_ptr ListAggregatesBindFailure(BoundScalarFunction &bound_function) { @@ -93,6 +83,36 @@ ListAggregatesBindData::ListAggregatesBindData(const LogicalType &stype_p, uniqu ListAggregatesBindData::~ListAggregatesBindData() { } +struct ListAggregatesLocalState : public FunctionLocalState { + ListAggregatesLocalState(ClientContext &context, optional_ptr bind_data) + : arena_allocator(BufferAllocator::Get(context)) { + if (!bind_data) { + return; + } + // arguments that the bind of the aggregate derived from its input (e.g. a collation of the compared value) are + // evaluated over the list elements + auto &aggr = bind_data->Cast().aggr_expr->Cast(); + auto &children = aggr.GetChildren(); + for (idx_t child_idx = 1; child_idx < children.size(); child_idx++) { + if (children[child_idx]->GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { + continue; + } + if (!derived_executor) { + derived_executor = make_uniq(context); + } + derived_executor->AddExpression(*children[child_idx]); + } + } + + ArenaAllocator arena_allocator; + unique_ptr derived_executor; +}; + +unique_ptr ListAggregatesInitLocalState(ExpressionState &state, const BoundFunctionExpression &expr, + FunctionData *bind_data) { + return make_uniq(state.GetContext(), bind_data); +} + struct StateVector { StateVector(idx_t count_p, unique_ptr aggr_expr_p) : count(count_p), aggr_expr(std::move(aggr_expr_p)), state_vector(Vector(LogicalType::POINTER, count_p)) { @@ -226,7 +246,8 @@ void ListAggregatesFunction(DataChunk &args, ExpressionState &state, Vector &res auto &func_expr = state.expr.Cast(); auto &info = func_expr.BindInfo()->Cast(); auto &aggr = info.aggr_expr->Cast(); - auto &allocator = ExecuteFunctionState::GetFunctionState(state)->Cast().arena_allocator; + auto &local_state = ExecuteFunctionState::GetFunctionState(state)->Cast(); + auto &allocator = local_state.arena_allocator; allocator.Reset(); AggregateFinalizeInputData aggr_input_data(aggr, allocator); @@ -260,12 +281,34 @@ void ListAggregatesFunction(DataChunk &args, ExpressionState &state, Vector &res // the aggregate's trailing arguments (e.g. the separator of string_agg) are constants that its bind folded into // the bind data - they are still part of its argument list, so they are passed along as constant vectors + DataChunk derived_input; + DataChunk derived_result; + if (local_state.derived_executor) { + derived_input.InitializeEmpty({child_vector.GetType()}); + vector derived_types; + for (auto &derived_expr : local_state.derived_executor->expressions) { + derived_types.push_back(derived_expr->GetReturnType()); + } + derived_result.Initialize(Allocator::DefaultAllocator(), derived_types); + } auto update_states = [&](idx_t update_count) { vector inputs; inputs.reserve(aggr.GetChildren().size()); inputs.emplace_back(child_vector, sel_vector, update_count); + if (local_state.derived_executor) { + derived_input.data[0].Slice(child_vector, sel_vector, update_count); + derived_input.SetChildCardinality(update_count); + derived_result.Reset(); + local_state.derived_executor->Execute(derived_input, derived_result); + } + idx_t derived_idx = 0; for (idx_t child_idx = 1; child_idx < aggr.GetChildren().size(); child_idx++) { - auto &constant = aggr.GetChildren()[child_idx]->Cast().GetValue(); + auto &child = *aggr.GetChildren()[child_idx]; + if (child.GetExpressionClass() != ExpressionClass::BOUND_CONSTANT) { + inputs.emplace_back(Vector::Ref(derived_result.data[derived_idx++])); + continue; + } + auto &constant = child.Cast().GetValue(); inputs.emplace_back(constant, count_t(update_count)); } aggr.Function().GetStateUpdateCallback()(inputs.data(), aggr_input_data, inputs.size(), state_vector_update, @@ -431,9 +474,9 @@ unique_ptr BindListAggregate(ClientContext &context, L throw BinderException("No matching aggregate function\n%s", error.Message()); } - // create the child expression and its type + // create the child expression and its type - it refers to the list elements vector> children; - children.push_back(make_uniq(Value(child_type))); + children.push_back(make_uniq(child_type, idx_t(0))); for (auto &argument : extra_arguments) { children.push_back(std::move(argument)); } @@ -508,6 +551,7 @@ unique_ptr ListAggregatesBind(BindScalarFunctionInput &input) { for (idx_t i = 2; i < arguments.size(); i++) { extra_arguments.push_back(std::move(arguments[i])); } + const idx_t extra_argument_count = extra_arguments.size(); if (arguments.size() > 2) { arguments.resize(2); } @@ -516,9 +560,10 @@ unique_ptr ListAggregatesBind(BindScalarFunctionInput &input) { auto bound_aggr_function = BindListAggregate(context, child_type, function_name, std::move(extra_arguments)); - // the extra arguments are passed to the aggregate as constant vectors, so they have to be constant + // the extra arguments are passed to the aggregate as constant vectors, so they have to be constant - arguments + // that the bind of the aggregate added after them are derived from the list elements auto &aggr_children = bound_aggr_function->GetChildrenMutable(); - for (idx_t child_idx = 1; child_idx < aggr_children.size(); child_idx++) { + for (idx_t child_idx = 1; child_idx < aggr_children.size() && child_idx <= extra_argument_count; child_idx++) { auto &child = aggr_children[child_idx]; if (!child->IsFoldable()) { throw InvalidInputException( @@ -529,8 +574,8 @@ unique_ptr ListAggregatesBind(BindScalarFunctionInput &input) { } // Preserve folded SQL arguments without evaluating them again. - if (aggr_children.size() + 1 == bound_function.GetLogicalArguments().size()) { - for (idx_t child_idx = 1; child_idx < aggr_children.size(); child_idx++) { + if (extra_argument_count + 2 == bound_function.GetLogicalArguments().size()) { + for (idx_t child_idx = 1; child_idx <= extra_argument_count && child_idx < aggr_children.size(); child_idx++) { arguments.push_back(aggr_children[child_idx]->Copy()); } } diff --git a/src/duckdb/extension/core_functions/scalar/map/switch.cpp b/src/duckdb/extension/core_functions/scalar/map/switch.cpp index c99b606a2..c060299bd 100644 --- a/src/duckdb/extension/core_functions/scalar/map/switch.cpp +++ b/src/duckdb/extension/core_functions/scalar/map/switch.cpp @@ -9,26 +9,24 @@ namespace duckdb { namespace { struct SwitchFunctionBindData : FunctionData { - explicit SwitchFunctionBindData(const LogicalType &return_type_p, idx_t map_index_p) - : return_type(return_type_p), map_index(map_index_p) { + SwitchFunctionBindData(Value cases_p, idx_t map_index_p) : cases(std::move(cases_p)), map_index(map_index_p) { } - LogicalType return_type; + //! The constant MAP of (WHEN, THEN) pairs + Value cases; idx_t map_index; + const LogicalType &ReturnType() const { + return MapType::ValueType(cases.type()); + } + bool Equals(const FunctionData &other_p) const override { const auto &other = other_p.Cast(); - if (return_type != other.return_type) { - return false; - } - if (map_index != other.map_index) { - return false; - } - return true; + return cases == other.cases && map_index == other.map_index; } unique_ptr Copy() const override { - return make_uniq(return_type, map_index); + return make_uniq(cases, map_index); } }; @@ -40,45 +38,26 @@ unique_ptr SwitchBindReturnType(BindScalarFunctionInput &input) { auto &arguments = input.GetArguments(); constexpr idx_t map_index = MAP_INDEX; D_ASSERT(map_index < arguments.size()); - // the cases might have been cast to the argument type of the function - reference cases = *arguments[map_index]; - if (BoundCastExpression::IsCast(cases.get())) { - cases = BoundCastExpression::Child(cases.get().Cast()); - } - if (cases.get().GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) { + // the cases are a constant MAP - usually folded to a constant by the function binder already, otherwise a + // foldable expression (including the cast to the argument type of the function) that is evaluated here + auto &cases = *arguments[map_index]; + if (!cases.IsFoldable()) { throw BinderException("SWITCH expected a constant map for the cases"); } - auto &func = cases.get().Cast(); - if (func.Function().GetName() != "map" || !cases.get().IsFoldable()) { + auto map_value = ExpressionExecutor::EvaluateScalar(context, cases); + if (map_value.IsNull()) { throw BinderException("SWITCH expected a constant map for the cases"); } - auto map_value = ExpressionExecutor::EvaluateScalar(context, cases.get()); - auto values_type = MapType::ValueType(map_value.type()); - return make_uniq(values_type, map_index); -} - -void ExtractConstantExprFromList(unique_ptr &expr, vector> &result) { - if (expr->GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) { - throw BinderException("Expected a function for the cases"); - } - auto &list_function = expr->Cast(); - if (list_function.Function().GetName() != "list_value") { - throw BinderException("Expected a list function"); - } - if (list_function.GetChildren().empty()) { + if (MapValue::GetChildren(map_value).empty()) { throw BinderException("No values provided for SWITCH expression"); } - for (auto &list_child : list_function.GetChildrenMutable()) { - if (list_child->GetExpressionClass() != ExpressionClass::BOUND_CONSTANT) { - throw NotImplementedException("Only constant expressions are supported for keys inside SWITCH"); - } - result.push_back(std::move(list_child)); - } + return make_uniq(std::move(map_value), map_index); } unique_ptr SwitchBindExpression(FunctionBindExpressionInput &input) { - auto function_data = input.bind_data->Cast(); - auto result = make_uniq(function_data.return_type); + auto &function_data = input.bind_data->Cast(); + auto &return_type = function_data.ReturnType(); + auto result = make_uniq(return_type); idx_t map_index = function_data.map_index; unique_ptr base_expr = nullptr; unique_ptr default_expr = nullptr; @@ -91,56 +70,33 @@ unique_ptr SwitchBindExpression(FunctionBindExpressionInput &input) // If there is an argument after the map_index, we have a default expression default_expr = std::move(input.children[map_index + 1]); } - unique_ptr cases; - if (BoundCastExpression::IsCast(*input.children[map_index])) { - auto &cast_expr = input.children[map_index]->Cast(); - if (BoundCastExpression::Child(cast_expr).GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) { - throw BinderException("SWITCH expected a map function for the cases"); - } - cases = std::move(BoundCastExpression::ChildMutable(cast_expr)); - } else if (input.children[map_index]->GetExpressionClass() == ExpressionClass::BOUND_FUNCTION) { - cases = std::move(input.children[map_index]); - } else { - throw BinderException("SWITCH expected a map function for the cases"); - } - auto &cases_func = cases->Cast(); - D_ASSERT(cases_func.GetChildren().size() == 2); - vector> keys_unpacked; - vector> values_unpacked; - ExtractConstantExprFromList(cases_func.GetChildrenMutable()[0], keys_unpacked); - ExtractConstantExprFromList(cases_func.GetChildrenMutable()[1], values_unpacked); - - result->CaseChecksMutable().reserve(keys_unpacked.size()); - for (idx_t i = 0; i < keys_unpacked.size(); i++) { + // every entry of the cases map becomes a WHEN key THEN value check + auto &entries = MapValue::GetChildren(function_data.cases); + result->CaseChecksMutable().reserve(entries.size()); + for (auto &entry : entries) { + auto &key_value = StructValue::GetChildren(entry)[0]; + auto &then_value = StructValue::GetChildren(entry)[1]; + auto key = make_uniq(key_value); BoundCaseCheck case_check; if (base_expr) { - auto max_type = LogicalType::MaxLogicalType(input.context, base_expr->GetReturnType(), - keys_unpacked[i]->GetReturnType()); + auto max_type = + LogicalType::MaxLogicalType(input.context, base_expr->GetReturnType(), key->GetReturnType()); case_check.when_expr = BoundComparisonExpression::Create( ExpressionType::COMPARE_EQUAL, base_expr->Copy(), - BoundCastExpression::AddCastToType(input.context, std::move(keys_unpacked[i]), max_type)); + BoundCastExpression::AddCastToType(input.context, std::move(key), max_type)); } else { case_check.when_expr = - BoundCastExpression::AddCastToType(input.context, std::move(keys_unpacked[i]), LogicalType::BOOLEAN); - } - auto then_type = values_unpacked[i]->GetReturnType(); - if (!LogicalType::TryGetMaxLogicalType(input.context, function_data.return_type, then_type, - function_data.return_type)) { - // LCOV_EXCL_START - throw BinderException( - "Cannot mix values of type %s and %s in CASE expression - an explicit cast is required", - function_data.return_type.ToString(), then_type.ToString()); - // LCOV_EXCL_STOP + BoundCastExpression::AddCastToType(input.context, std::move(key), LogicalType::BOOLEAN); } - case_check.then_expr = std::move(values_unpacked[i]); + case_check.then_expr = make_uniq(then_value); result->CaseChecksMutable().push_back(std::move(case_check)); } if (default_expr) { result->ElseMutable() = std::move(default_expr); } else { - result->ElseMutable() = BoundCastExpression::AddCastToType( - input.context, make_uniq(Value()), function_data.return_type); + result->ElseMutable() = + BoundCastExpression::AddCastToType(input.context, make_uniq(Value()), return_type); } return std::move(result); } diff --git a/src/duckdb/extension/core_functions/scalar/struct/struct_insert.cpp b/src/duckdb/extension/core_functions/scalar/struct/struct_insert.cpp index 86e5bd039..9a6f6cb3b 100644 --- a/src/duckdb/extension/core_functions/scalar/struct/struct_insert.cpp +++ b/src/duckdb/extension/core_functions/scalar/struct/struct_insert.cpp @@ -34,6 +34,11 @@ static unique_ptr StructInsertBind(BindScalarFunctionInput &input) auto &bound_function = input.GetBoundFunction(); auto &arguments = input.GetArguments(); auto &names = *input.GetArgumentNames(); + for (auto &argument : arguments) { + if (argument->GetReturnType().id() == LogicalTypeId::UNKNOWN) { + throw ParameterNotResolvedException(); + } + } if (LogicalTypeId::STRUCT != arguments[0]->GetReturnType().id()) { throw InvalidInputException("The first argument to struct_insert must be a STRUCT"); } diff --git a/src/duckdb/extension/core_functions/scalar/struct/struct_update.cpp b/src/duckdb/extension/core_functions/scalar/struct/struct_update.cpp index 02e343d80..1ebd026cd 100644 --- a/src/duckdb/extension/core_functions/scalar/struct/struct_update.cpp +++ b/src/duckdb/extension/core_functions/scalar/struct/struct_update.cpp @@ -11,44 +11,62 @@ namespace duckdb { -static void StructUpdateFunction(DataChunk &args, ExpressionState &state, Vector &result) { - const auto &starting_vec = args.data[0]; - starting_vec.Verify(); - - auto &starting_child_entries = StructVector::GetEntries(starting_vec); - auto &result_child_entries = StructVector::GetEntries(result); +//! The result field that every argument after the struct writes to +struct StructUpdateBindData : public FunctionData { + StructUpdateBindData(LogicalType stype_p, vector field_indexes_p) + : stype(std::move(stype_p)), field_indexes(std::move(field_indexes_p)) { + } - auto &starting_types = StructType::GetChildTypes(starting_vec.GetType()); + LogicalType stype; + //! Indexed by argument index minus one + vector field_indexes; - auto &func_args = state.expr.Cast().GetChildren(); - auto new_entries = identifier_tree_t(); - auto is_new_field = vector(args.ColumnCount(), true); + unique_ptr Copy() const override { + return make_uniq(stype, field_indexes); + } + bool Equals(const FunctionData &other_p) const override { + auto &other = other_p.Cast(); + return stype == other.stype && field_indexes == other.field_indexes; + } - for (idx_t arg_idx = 1; arg_idx < func_args.size(); arg_idx++) { - auto &new_child = func_args[arg_idx]; - new_entries.emplace(new_child->GetAlias(), arg_idx); + static void Serialize(Serializer &serializer, const optional_ptr bind_data, + const BoundScalarFunction &function) { + auto &info = bind_data->Cast(); + serializer.WriteProperty(100, "variable_return_type", info.stype); + serializer.WritePropertyWithDefault(101, "field_indexes", info.field_indexes); } - // Assign the original child entries to the STRUCT. - for (idx_t field_idx = 0; field_idx < starting_child_entries.size(); field_idx++) { - auto &starting_child = starting_child_entries[field_idx]; - auto update = new_entries.find(starting_types[field_idx].first.c_str()); + static unique_ptr Deserialize(Deserializer &deserializer, BoundScalarFunction &bound_function) { + auto stype = deserializer.ReadProperty(100, "variable_return_type"); + auto field_indexes = deserializer.ReadPropertyWithDefault>(101, "field_indexes"); + return make_uniq(std::move(stype), std::move(field_indexes)); + } +}; - if (update == new_entries.end()) { - // No update present, copy from source - result_child_entries[field_idx].Reference(starting_child); - } else { - // We found a replacement of the same name to update - auto arg_idx = update->second; - result_child_entries[field_idx].Reference(args.data[arg_idx]); - is_new_field[arg_idx] = false; - } +//! For every result field, the argument that provides it - fields without one keep the value of the source struct +static vector FieldArguments(const StructUpdateBindData &info, idx_t field_count) { + vector field_arguments(field_count); + for (idx_t arg_idx = 1; arg_idx <= info.field_indexes.size(); arg_idx++) { + field_arguments[info.field_indexes[arg_idx - 1]] = arg_idx; } + return field_arguments; +} - // Assign the new (not updated) children to the end of the result vector. - for (idx_t arg_idx = 1, field_idx = starting_child_entries.size(); arg_idx < args.ColumnCount(); arg_idx++) { - if (is_new_field[arg_idx]) { - result_child_entries[field_idx++].Reference(args.data[arg_idx]); +static void StructUpdateFunction(DataChunk &args, ExpressionState &state, Vector &result) { + const auto &starting_vec = args.data[0]; + starting_vec.Verify(); + + auto &starting_child_entries = StructVector::GetEntries(starting_vec); + auto &result_child_entries = StructVector::GetEntries(result); + auto &info = state.expr.Cast().BindInfo()->Cast(); + auto field_arguments = FieldArguments(info, result_child_entries.size()); + + for (idx_t field_idx = 0; field_idx < result_child_entries.size(); field_idx++) { + auto &argument = field_arguments[field_idx]; + if (argument.IsValid()) { + result_child_entries[field_idx].Reference(args.data[argument.GetIndex()]); + } else { + result_child_entries[field_idx].Reference(starting_child_entries[field_idx]); } } } @@ -71,6 +89,7 @@ static unique_ptr StructUpdateBind(BindScalarFunctionInput &input) auto incoming_children = identifier_tree_t(); auto is_new_field = vector(arguments.size(), true); + vector field_indexes(arguments.size() - 1); // Record the names of the fields that are updated auto &names = *input.GetArgumentNames(); @@ -89,65 +108,49 @@ static unique_ptr StructUpdateBind(BindScalarFunctionInput &input) auto arg_idx = update->second; new_children.emplace_back(make_pair(names[arg_idx], arguments[arg_idx]->GetReturnType())); is_new_field[arg_idx] = false; + field_indexes[arg_idx - 1] = field_idx; } } // Loop through the additional arguments (name/value pairs) for (idx_t arg_idx = 1; arg_idx < arguments.size(); arg_idx++) { if (is_new_field[arg_idx]) { + field_indexes[arg_idx - 1] = new_children.size(); new_children.emplace_back(make_pair(names[arg_idx], arguments[arg_idx]->GetReturnType())); } } bound_function.SetReturnType(LogicalType::STRUCT(new_children)); - return make_uniq(bound_function.GetReturnType()); + return make_uniq(bound_function.GetReturnType(), std::move(field_indexes)); } static unique_ptr StructUpdateStats(ClientContext &context, FunctionStatisticsInput &input) { auto &child_stats = input.child_stats; auto &expr = input.expr; + auto &info = input.bind_data->Cast(); - auto incoming_children = identifier_tree_t(); - auto is_new_field = vector(expr.GetChildren().size(), true); auto new_stats = StructStats::CreateUnknown(expr.GetReturnType()); new_stats.Set(StatsInfo::CANNOT_HAVE_NULL_VALUES); - for (idx_t arg_idx = 1; arg_idx < expr.GetChildren().size(); arg_idx++) { - auto &new_child = expr.GetChildren()[arg_idx]; - incoming_children.emplace(new_child->GetAlias(), arg_idx); - } - - auto existing_type = child_stats[0].GetType(); - auto existing_count = StructType::GetChildCount(existing_type); auto existing_stats = StructStats::GetChildStats(child_stats[0]); - for (idx_t field_idx = 0; field_idx < existing_count; field_idx++) { - auto &existing_child = existing_stats[field_idx]; - auto update = incoming_children.find(StructType::GetChildName(existing_type, field_idx)); - if (update == incoming_children.end()) { - StructStats::SetChildStats(new_stats, field_idx, existing_child); + auto field_count = StructType::GetChildCount(expr.GetReturnType()); + auto field_arguments = FieldArguments(info, field_count); + for (idx_t field_idx = 0; field_idx < field_count; field_idx++) { + auto &argument = field_arguments[field_idx]; + if (argument.IsValid()) { + StructStats::SetChildStats(new_stats, field_idx, child_stats[argument.GetIndex()]); } else { - auto arg_idx = update->second; - StructStats::SetChildStats(new_stats, field_idx, child_stats[arg_idx]); - is_new_field[arg_idx] = false; + StructStats::SetChildStats(new_stats, field_idx, existing_stats[field_idx]); } } - - for (idx_t arg_idx = 1, field_idx = existing_count; arg_idx < expr.GetChildren().size(); arg_idx++) { - if (is_new_field[arg_idx]) { - StructStats::SetChildStats(new_stats, field_idx++, child_stats[arg_idx]); - } - } - return new_stats.ToUnique(); } static unique_ptr StructUpdateUnbind(FunctionUnbindInput &input) { + auto &info = input.expression.BindInfo()->Cast(); vector arguments; for (idx_t i = 0; i < input.children.size(); i++) { - auto name = i == 0 ? Identifier() : input.expression.GetChildren()[i]->GetAlias(); - if (i > 0 && name.empty()) { - return nullptr; - } + auto name = i == 0 ? Identifier() : StructType::GetChildName(info.stype, info.field_indexes[i - 1]); arguments.emplace_back(std::move(name), std::move(input.children[i])); } return make_uniq(input.expression.Function().GetDefinition()->GetQualifiedName(), @@ -160,8 +163,8 @@ ScalarFunction StructUpdateFun::GetFunction() { fun.GetSignature().AddParameter("struct", LogicalType::ANY).AddKwargs("kwargs", LogicalType::ANY); fun.GetProperties().SetRequiresExpressionNames(true); fun.SetUnbindCallback(StructUpdateUnbind); - fun.SetSerializeCallback(VariableReturnBindData::Serialize); - fun.SetDeserializeCallback(VariableReturnBindData::Deserialize); + fun.SetSerializeCallback(StructUpdateBindData::Serialize); + fun.SetDeserializeCallback(StructUpdateBindData::Deserialize); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/union/union_value.cpp b/src/duckdb/extension/core_functions/scalar/union/union_value.cpp index 041c3df0a..443100fef 100644 --- a/src/duckdb/extension/core_functions/scalar/union/union_value.cpp +++ b/src/duckdb/extension/core_functions/scalar/union/union_value.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/string_util.hpp" #include "duckdb/execution/expression_executor.hpp" #include "duckdb/function/scalar/nested_functions.hpp" +#include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/planner/expression/bound_parameter_expression.hpp" @@ -46,6 +47,16 @@ unique_ptr UnionValueBind(BindScalarFunctionInput &input) { return make_uniq(bound_function.GetReturnType()); } +unique_ptr UnionValueUnbind(FunctionUnbindInput &input) { + auto &expression = input.expression; + D_ASSERT(input.children.size() == 1); + D_ASSERT(UnionType::GetMemberCount(expression.GetReturnType()) == 1); + vector arguments; + arguments.emplace_back(UnionType::GetMemberName(expression.GetReturnType(), 0), std::move(input.children[0])); + return make_uniq(expression.Function().GetDefinition()->GetQualifiedName(), + std::move(arguments)); +} + } // namespace ScalarFunction UnionValueFun::GetFunction() { @@ -55,6 +66,7 @@ ScalarFunction UnionValueFun::GetFunction() { fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); fun.SetSerializeCallback(VariableReturnBindData::Serialize); fun.SetDeserializeCallback(VariableReturnBindData::Deserialize); + fun.SetUnbindCallback(UnionValueUnbind); return fun; } diff --git a/src/duckdb/extension/icu/icu-datefunc.cpp b/src/duckdb/extension/icu/icu-datefunc.cpp index 1b681ff47..d839b22db 100644 --- a/src/duckdb/extension/icu/icu-datefunc.cpp +++ b/src/duckdb/extension/icu/icu-datefunc.cpp @@ -117,7 +117,13 @@ timestamp_tz_t ICUDateFunc::GetTimeUnsafe(Calendar *calendar, uint64_t micros) { if (calendar->HasFailed()) { throw InternalException("Unable to get calendar time."); } - return timestamp_tz_t(millis * Interval::MICROS_PER_MSEC + int64_t(micros)); + // The time is a whole number of milliseconds, so converting it back to microseconds can overflow. + int64_t result; + if (!TryMultiplyOperator::Operation(millis, Interval::MICROS_PER_MSEC, result) || + !TryAddOperator::Operation(result, int64_t(micros), result)) { + throw OutOfRangeException("Unable to get calendar time: timestamp out of range"); + } + return timestamp_tz_t(result); } bool ICUDateFunc::TryGetTime(Calendar *calendar, uint64_t micros, timestamp_tz_t &result) { diff --git a/src/duckdb/extension/icu/icu-datepart.cpp b/src/duckdb/extension/icu/icu-datepart.cpp index fa7228c51..3b1caae00 100644 --- a/src/duckdb/extension/icu/icu-datepart.cpp +++ b/src/duckdb/extension/icu/icu-datepart.cpp @@ -3,6 +3,8 @@ #include "include/icu-datepart.hpp" #include "include/icu-datefunc.hpp" +#include "duckdb/catalog/catalog.hpp" +#include "duckdb/catalog/catalog_entry/scalar_function_catalog_entry.hpp" #include "duckdb/main/extension/extension_loader.hpp" #include "duckdb/common/enums/date_part_specifier.hpp" #include "duckdb/common/types/date.hpp" @@ -10,6 +12,7 @@ #include "duckdb/common/vector_operations/unary_executor.hpp" #include "duckdb/common/vector_operations/binary_executor.hpp" #include "duckdb/execution/expression_executor.hpp" +#include "duckdb/function/function_binder.hpp" #include "duckdb/main/client_context.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" @@ -472,7 +475,7 @@ struct ICUDatePart : public ICUDateFunc { auto &context = input.GetClientContext(); auto &arguments = input.GetArguments(); - // If we have a constant first argument, then patch in the unary function. + // Bind the equivalent unary function for a constant first argument. do { auto part_constant = input.TryGetConstant(0); if (!part_constant || part_constant->IsNull()) { @@ -482,18 +485,16 @@ struct ICUDatePart : public ICUDateFunc { const auto part_name = part_constant->ToString(); const auto part_code = GetDatePartSpecifier(part_name); arguments.erase(arguments.begin()); - bound_function.GetArguments().erase(bound_function.GetArguments().begin()); - bound_function.SetName(Identifier(DatePartUnaryFunctionName(part_code))); - - if (IsBigintDatepart(part_code)) { - bound_function.SetReturnType(LogicalType::BIGINT); - bound_function.SetFunctionCallback(UnaryTimestampFunction); - } else { - bound_function.SetReturnType(LogicalType::DOUBLE); - bound_function.SetFunctionCallback(UnaryTimestampFunction); - } - - return BindUnaryDatePart(input); + auto unary_name = bound_function.GetDefinition()->GetQualifiedName().WithName( + Identifier(DatePartUnaryFunctionName(part_code))); + auto &entry = Catalog::GetEntry(context, unary_name); + auto unary_function = entry.functions.GetFunctionByArguments(context, {arguments[0]->GetReturnType()}); + FunctionBinder binder(context); + auto unary_expression = binder.BindScalarFunction(std::move(unary_function), std::move(arguments)); + auto &unary = unary_expression->Cast(); + bound_function = std::move(unary.FunctionMutable()); + arguments = std::move(unary.GetChildrenMutable()); + return std::move(unary.BindInfoMutable()); } while (false); using data_t = BindAdapterData; diff --git a/src/duckdb/extension/icu/icu-strptime.cpp b/src/duckdb/extension/icu/icu-strptime.cpp index 8a98b426f..60c2b8b91 100644 --- a/src/duckdb/extension/icu/icu-strptime.cpp +++ b/src/duckdb/extension/icu/icu-strptime.cpp @@ -38,8 +38,12 @@ TimestampComponents ICUHelpers::GetComponents(timestamp_tz_t ts, Calendar *calen TimestampComponents ICUHelpers::GetComponents(timestamp_tz_ns_t tsns, Calendar *calendar) { // Get the parts in the given time zone - auto ts_data = GetComponents(timestamp_tz_t(tsns.value / Interval::NANOS_PER_MICRO), calendar); - ts_data.nanosecond = UnsafeNumericCast(tsns.value % Interval::NANOS_PER_MICRO); + int64_t micros; + int64_t nanos; + Timestamp::SplitTime(tsns.value, Interval::NANOS_PER_MICRO, micros, nanos); + + auto ts_data = GetComponents(timestamp_tz_t(micros), calendar); + ts_data.nanosecond = UnsafeNumericCast(nanos); return ts_data; } @@ -422,10 +426,13 @@ struct ICUStrptime : public ICUDateFunc { auto &cache = parameters.local_state->Cast(); CalendarPtr cal(info.calendar->Copy()); + bool all_converted = true; UnaryExecutor::Execute(source, result, count, [&](string_t input) { - return VarcharToTimestampTZUS(cal, cache, input, parameters); + auto converted = VarcharToTimestampTZUS(cal, cache, input, parameters); + all_converted = all_converted && converted; + return converted; }); - return true; + return all_converted; } static bool VarcharToTimestampTZNS(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { @@ -434,11 +441,13 @@ struct ICUStrptime : public ICUDateFunc { auto &cache = parameters.local_state->Cast(); CalendarPtr cal(info.calendar->Copy()); + bool all_converted = true; UnaryExecutor::Execute( source, result, count, [&](string_t input) -> optional { int32_t nanos = 0; auto ts_us = VarcharToTimestampTZUS(cal, cache, input, parameters, &nanos); if (!ts_us) { + all_converted = false; return nullopt; } @@ -446,12 +455,13 @@ struct ICUStrptime : public ICUDateFunc { timestamp_ns_t result; if (!Timestamp::TryFromTimestampNanos(us, nanos, result)) { HandleCastError::AssignError(Timestamp::RangeError(input), parameters); + all_converted = false; return nullopt; } return timestamp_tz_ns_t(result); }); - return true; + return all_converted; } static bool VarcharToTimeTZ(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { @@ -459,6 +469,7 @@ struct ICUStrptime : public ICUDateFunc { auto &info = cast_data.info->Cast(); CalendarPtr cal(info.calendar->Copy()); + bool all_converted = true; UnaryExecutor::Execute( source, result, count, [&](string_t input) -> optional { dtime_tz_t result; @@ -469,6 +480,7 @@ struct ICUStrptime : public ICUDateFunc { if (!Time::TryConvertTimeTZ(str, len, pos, result, has_offset, false)) { auto msg = Time::ConversionError(string(str, len)); HandleCastError::AssignError(msg, parameters); + all_converted = false; return nullopt; } else if (!has_offset) { // Convert parts to a TZ (default or parsed) if no offset was provided @@ -478,6 +490,7 @@ struct ICUStrptime : public ICUDateFunc { int32_t offset; if (!TryGetTimeTZOffset(calendar, offset)) { HandleCastError::AssignError(TimeTZOffsetError(offset), parameters); + all_converted = false; return nullopt; } @@ -487,7 +500,7 @@ struct ICUStrptime : public ICUDateFunc { return result; }); - return true; + return all_converted; } static BoundCastInfo BindCastFromVarchar(BindCastInput &input, const LogicalType &source, diff --git a/src/duckdb/extension/json/include/json_common.hpp b/src/duckdb/extension/json/include/json_common.hpp index 13b55ff95..c3f41aaaa 100644 --- a/src/duckdb/extension/json/include/json_common.hpp +++ b/src/duckdb/extension/json/include/json_common.hpp @@ -92,6 +92,10 @@ class JSONPathIterator { //! Common JSON functionality for most JSON functions struct JSONCommon { public: + //! Deep copy of a mutable value - equivalent to yyjson_mut_val_mut_copy, but iterative instead of recursive, so + //! that deeply nested values do not overflow the stack + static yyjson_mut_val *MutValMutCopy(yyjson_mut_doc *doc, yyjson_mut_val *val); + //! Maximum recursion depth for recursive functions static constexpr idx_t MAX_RECURSION_DEPTH = 128; //! Read/Write flags diff --git a/src/duckdb/extension/json/json_common.cpp b/src/duckdb/extension/json/json_common.cpp index fd8ab6c02..b0db86a97 100644 --- a/src/duckdb/extension/json/json_common.cpp +++ b/src/duckdb/extension/json/json_common.cpp @@ -426,4 +426,77 @@ void JSONCommon::GetWildcardPath(yyjson_val *val, const char *ptr, const idx_t & GetWildcardPathInternal(val, ptr, end, vals); } +//! Shallow copy of a mutable value - the children of containers are copied by MutValMutCopy +static yyjson_mut_val *MutValShallowCopy(yyjson_mut_doc *doc, yyjson_mut_val *val) { + auto result = unsafe_yyjson_mut_val(doc, 1); + if (!result) { + return nullptr; + } + result->tag = val->tag; + switch (unsafe_yyjson_get_type(val)) { + case YYJSON_TYPE_OBJ: + case YYJSON_TYPE_ARR: + result->uni.ptr = nullptr; + break; + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + result->uni.str = unsafe_yyjson_mut_strncpy(doc, val->uni.str, unsafe_yyjson_get_len(val)); + if (!result->uni.str) { + return nullptr; + } + break; + default: + result->uni = val->uni; + break; + } + return result; +} + +yyjson_mut_val *JSONCommon::MutValMutCopy(yyjson_mut_doc *doc, yyjson_mut_val *val) { + if (!doc || !val) { + return nullptr; + } + struct CopyEntry { + yyjson_mut_val *source; + yyjson_mut_val *target; + }; + vector containers; + auto result = MutValShallowCopy(doc, val); + if (!result) { + return nullptr; + } + if (unsafe_yyjson_is_ctn(val) && unsafe_yyjson_get_len(val) > 0) { + containers.push_back({val, result}); + } + while (!containers.empty()) { + auto entry = containers.back(); + containers.pop_back(); + // the children of a container form a circular list - the container points to the last child + auto last = reinterpret_cast(entry.source->uni.ptr); + auto source_child = last; + yyjson_mut_val *first_copy = nullptr; + yyjson_mut_val *prev_copy = nullptr; + do { + auto child_copy = MutValShallowCopy(doc, source_child); + if (!child_copy) { + return nullptr; + } + if (unsafe_yyjson_is_ctn(source_child) && unsafe_yyjson_get_len(source_child) > 0) { + containers.push_back({source_child, child_copy}); + } + if (prev_copy) { + prev_copy->next = child_copy; + } else { + first_copy = child_copy; + } + prev_copy = child_copy; + source_child = source_child->next; + } while (source_child != last); + // the copies were made starting from the last child - close the circle, and point the copy at the last child + prev_copy->next = first_copy; + entry.target->uni.ptr = first_copy; + } + return result; +} + } // namespace duckdb diff --git a/src/duckdb/extension/json/json_functions/json_deep_merge.cpp b/src/duckdb/extension/json/json_functions/json_deep_merge.cpp index 29c15d7c7..6afdd555c 100644 --- a/src/duckdb/extension/json/json_functions/json_deep_merge.cpp +++ b/src/duckdb/extension/json/json_functions/json_deep_merge.cpp @@ -9,9 +9,9 @@ namespace duckdb { static yyjson_mut_val *DeepMerge(yyjson_mut_doc *doc, yyjson_mut_val *orig_root, yyjson_mut_val *patch_root) { if (!yyjson_mut_is_obj(orig_root) || !yyjson_mut_is_obj(patch_root)) { if (unsafe_yyjson_is_null(patch_root)) { - return yyjson_mut_val_mut_copy(doc, orig_root); + return JSONCommon::MutValMutCopy(doc, orig_root); } - return yyjson_mut_val_mut_copy(doc, patch_root); + return JSONCommon::MutValMutCopy(doc, patch_root); } auto root_builder = yyjson_mut_obj(doc); @@ -41,8 +41,8 @@ static yyjson_mut_val *DeepMerge(yyjson_mut_doc *doc, yyjson_mut_val *orig_root, auto patch_val = yyjson_mut_obj_getn(nodes.patch_node, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); if (!patch_val || unsafe_yyjson_is_null(patch_val)) { - auto mut_key = yyjson_mut_val_mut_copy(doc, key); - auto mut_val = yyjson_mut_val_mut_copy(doc, orig_val); + auto mut_key = JSONCommon::MutValMutCopy(doc, key); + auto mut_val = JSONCommon::MutValMutCopy(doc, orig_val); yyjson_mut_obj_add(builder, mut_key, mut_val); } } @@ -60,7 +60,7 @@ static yyjson_mut_val *DeepMerge(yyjson_mut_doc *doc, yyjson_mut_val *orig_root, auto orig_val = yyjson_mut_obj_getn(nodes.orig_node, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); - auto mut_key = yyjson_mut_val_mut_copy(doc, key); + auto mut_key = JSONCommon::MutValMutCopy(doc, key); // at least one of (patch_val, orig_val) is not an object, then we copy the patch if it's not null, and // the original otherwise @@ -71,9 +71,9 @@ static yyjson_mut_val *DeepMerge(yyjson_mut_doc *doc, yyjson_mut_val *orig_root, yyjson_mut_val *mut_val; if (unsafe_yyjson_is_null(patch_val) && orig_val) { - mut_val = yyjson_mut_val_mut_copy(doc, orig_val); + mut_val = JSONCommon::MutValMutCopy(doc, orig_val); } else { - mut_val = yyjson_mut_val_mut_copy(doc, patch_val); + mut_val = JSONCommon::MutValMutCopy(doc, patch_val); } yyjson_mut_obj_add(builder, mut_key, mut_val); } else { diff --git a/src/duckdb/extension/json/json_functions/json_merge_patch.cpp b/src/duckdb/extension/json/json_functions/json_merge_patch.cpp index c203ea280..e6f925fbf 100644 --- a/src/duckdb/extension/json/json_functions/json_merge_patch.cpp +++ b/src/duckdb/extension/json/json_functions/json_merge_patch.cpp @@ -41,8 +41,8 @@ static inline yyjson_mut_val *MergePatch(yyjson_mut_doc *doc, yyjson_mut_val *or auto patch_val = yyjson_mut_obj_getn(nodes.patch, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); if (!patch_val) { - yyjson_mut_obj_add(nodes.builder, yyjson_mut_val_mut_copy(doc, key), - yyjson_mut_val_mut_copy(doc, orig_val)); + yyjson_mut_obj_add(nodes.builder, JSONCommon::MutValMutCopy(doc, key), + JSONCommon::MutValMutCopy(doc, orig_val)); } } } @@ -55,9 +55,9 @@ static inline yyjson_mut_val *MergePatch(yyjson_mut_doc *doc, yyjson_mut_val *or if (unsafe_yyjson_is_null(patch_val)) { continue; } - auto mut_key = yyjson_mut_val_mut_copy(doc, key); + auto mut_key = JSONCommon::MutValMutCopy(doc, key); if (!yyjson_mut_is_obj(patch_val)) { - yyjson_mut_obj_add(nodes.builder, mut_key, yyjson_mut_val_mut_copy(doc, patch_val)); + yyjson_mut_obj_add(nodes.builder, mut_key, JSONCommon::MutValMutCopy(doc, patch_val)); continue; } auto orig_val = yyjson_mut_obj_getn(nodes.orig, unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); diff --git a/src/duckdb/extension/json/json_functions/read_json.cpp b/src/duckdb/extension/json/json_functions/read_json.cpp index fa54c21b3..c7fbb3384 100644 --- a/src/duckdb/extension/json/json_functions/read_json.cpp +++ b/src/duckdb/extension/json/json_functions/read_json.cpp @@ -77,7 +77,8 @@ class JSONSchemaTask : public BaseExecutorTask { auto &reader = *json_reader; auto_detect_state.sampled_readers[file_idx] = std::move(json_reader); - auto &global_allocator = Allocator::Get(context); + // the sampled readers keep their first buffer for the scan, so it has to count against the memory limit + auto &global_allocator = BufferAllocator::Get(context); idx_t buffer_capacity = json_data.options.maximum_object_size * 2; JSONReaderScanState scan_state(context, global_allocator, buffer_capacity); auto &options = json_data.options; diff --git a/src/duckdb/extension/json/json_reader.cpp b/src/duckdb/extension/json/json_reader.cpp index 2920dfd54..8e32310db 100644 --- a/src/duckdb/extension/json/json_reader.cpp +++ b/src/duckdb/extension/json/json_reader.cpp @@ -752,7 +752,7 @@ static pair DetectFormatAndRecordType(char *const bu } // We weren't able to parse an array, could be broken or an array larger than our buffer size, let's skip over '[' - SkipWhitespace(buffer_ptr, ++buffer_offset, --remaining); + SkipWhitespace(buffer_ptr, ++buffer_offset, buffer_size); remaining = buffer_size - buffer_offset; // If it's '{' we know there's RECORDS in the ARRAY, else it's VALUES @@ -768,7 +768,10 @@ bool JSONReader::ParseJSON(JSONReaderScanState &scan_state, char *const json_sta const idx_t remaining) { yyjson_doc *doc; yyjson_read_err err; - if (options.type == JSONScanType::READ_JSON_OBJECTS) { // If we return strings, we cannot parse INSITU + // If we return strings, we cannot parse INSITU. If we ignore errors, we cannot parse INSITU either: a malformed + // document could extend into (and modify) the next one + const bool ignorable_errors = options.ignore_errors && GetFormat() == JSONFormat::NEWLINE_DELIMITED; + if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors) { doc = JSONCommon::ReadDocumentUnsafe(json_start, json_size, JSONCommon::READ_STOP_FLAG, scan_state.allocator.GetYYAlc(), &err); } else { @@ -874,6 +877,14 @@ void JSONReader::AutoDetect(Allocator &allocator, idx_t buffer_capacity) { } // store the buffer in the file so it can be re-used by the first reader of the file if (!file_handle->IsPipe()) { + // a file that is much smaller than the buffer only keeps what was read - with many files (e.g. union_by_name) + // one full-sized buffer per file adds up to far more than the data + const idx_t required = read_size + YYJSON_PADDING_SIZE; + if (required * 2 <= buffer_capacity) { + auto shrunk = allocator.Allocate(required); + memcpy(shrunk.get(), read_buffer.get(), required); + read_buffer = std::move(shrunk); + } auto_detect_data = std::move(read_buffer); auto_detect_data_size = read_size; } else { diff --git a/src/duckdb/extension/json/json_scan.cpp b/src/duckdb/extension/json/json_scan.cpp index 01ae61531..b34970e77 100644 --- a/src/duckdb/extension/json/json_scan.cpp +++ b/src/duckdb/extension/json/json_scan.cpp @@ -315,8 +315,16 @@ bool JSONScan::ParseOption(ClientContext &context, const Identifier &key, const if (key == "records") { // records is tri-state: 'auto', 'true' or 'false'. Accept a boolean for the latter two, since // records = false reads more naturally than records = 'false'. - auto arg = value.type().id() == LogicalTypeId::BOOLEAN ? string(BooleanValue::Get(value) ? "true" : "false") - : StringValue::Get(value); + string arg; + if (value.type().id() == LogicalTypeId::BOOLEAN) { + arg = BooleanValue::Get(value) ? "true" : "false"; + } else if (value.type().id() == LogicalTypeId::VARCHAR) { + arg = StringValue::Get(value); + } else { + throw BinderException("read_json \"records\" parameter must be a string ('auto', 'true' or 'false') or " + "a boolean, not %s", + value.type().ToString()); + } if (arg == "auto") { options.record_type = JSONRecordType::AUTO_DETECT; } else if (arg == "true") { diff --git a/src/duckdb/extension/parquet/column_reader.cpp b/src/duckdb/extension/parquet/column_reader.cpp index 688d14e07..453be1314 100644 --- a/src/duckdb/extension/parquet/column_reader.cpp +++ b/src/duckdb/extension/parquet/column_reader.cpp @@ -166,12 +166,11 @@ void ColumnReader::ValidateColumnMetadata(idx_t row_group_num_rows, const Column Reader().GetFileName().c_str()); } -uint64_t ColumnReader::TotalCompressedSize() { +void ColumnReader::GatherColumnChunks(unordered_set &column_chunks) { if (IsSkipped()) { - return 0; + return; } - - return chunk->meta_data.total_compressed_size; + column_chunks.insert(ColumnIndex()); } // Note: It's not trivial to determine where all Column data is stored. Chunk->file_offset @@ -1084,6 +1083,14 @@ unique_ptr ColumnReader::CreateReader(const ParquetReader &reader, case LogicalTypeId::TIMESTAMP_NS: case LogicalTypeId::TIMESTAMP_TZ_NS: switch (schema.type_info) { + case ParquetExtraTypeInfo::IMPALA_TIMESTAMP: + return make_uniq>(reader, schema); + case ParquetExtraTypeInfo::UNIT_MS: + return make_uniq>(reader, + schema); + case ParquetExtraTypeInfo::UNIT_MICROS: + return make_uniq>(reader, + schema); case ParquetExtraTypeInfo::UNIT_NS: return make_uniq>(reader, schema); diff --git a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp index e4992abdb..8c54b9243 100644 --- a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp +++ b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp @@ -45,13 +45,33 @@ bool IsContinuationByte(uint8_t b) { return (b & 0xC0) == 0x80; } -bool TouchesUtf8Boundary(const char *data, uint32_t len) { +//! Whether a string starts or ends in the middle of a multi-byte character. These are the only strings that can be +//! invalid on their own when the batch they are part of is valid UTF-8 +bool CutsUtf8Character(const char *data, uint32_t len) { if (len == 0) { return false; } - const uint8_t first = static_cast(data[0]); - const uint8_t last = static_cast(data[len - 1]); - return IsContinuationByte(first) || (last & 0x80) != 0; + const auto bytes = const_data_ptr_cast(data); + if (IsContinuationByte(bytes[0])) { + return true; + } + // find the first byte of the last character - a character is at most 4 bytes + idx_t last_start = len - 1; + while (last_start > 0 && len - last_start < 4 && IsContinuationByte(bytes[last_start])) { + last_start--; + } + const auto first_byte = bytes[last_start]; + idx_t char_length; + if (first_byte < 0x80) { + char_length = 1; + } else if (first_byte >= 0xF0) { + char_length = 4; + } else if (first_byte >= 0xE0) { + char_length = 3; + } else { + char_length = 2; + } + return len - last_start != char_length; } void DeltaLengthByteArrayDecoder::Read(shared_ptr &block_ref, uint8_t *defines, idx_t read_count, @@ -111,10 +131,10 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo auto verified = string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); result_data.WriteValue(verified); } else { - if (TouchesUtf8Boundary(char_ptr_cast(block.ptr), str_len)) { + if (CutsUtf8Character(char_ptr_cast(block.ptr), str_len)) { string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); } - result_data.WriteValue(string_t(char_ptr_cast(block.ptr), str_len)); + result_data.WriteStringRef(string_t(char_ptr_cast(block.ptr), str_len)); } block.unsafe_inc(str_len); } diff --git a/src/duckdb/extension/parquet/include/column_reader.hpp b/src/duckdb/extension/parquet/include/column_reader.hpp index f06aae93b..edf0a320b 100644 --- a/src/duckdb/extension/parquet/include/column_reader.hpp +++ b/src/duckdb/extension/parquet/include/column_reader.hpp @@ -29,6 +29,7 @@ #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/types/string_type.hpp" #include "duckdb/common/types/vector.hpp" +#include "duckdb/common/unordered_set.hpp" #include "duckdb/common/types/vector_cache.hpp" #include "duckdb/common/encryption_functions.hpp" #include "duckdb/common/assert.hpp" @@ -167,7 +168,8 @@ class ColumnReader { } virtual idx_t FileOffset() const; - virtual uint64_t TotalCompressedSize(); + //! Adds the indexes of the row group's column chunks this reader reads + virtual void GatherColumnChunks(unordered_set &column_chunks); virtual idx_t GroupRowsAvailable(); // register the range this reader will touch for prefetching diff --git a/src/duckdb/extension/parquet/include/parquet_int96.hpp b/src/duckdb/extension/parquet/include/parquet_int96.hpp new file mode 100644 index 000000000..0db7c0b4f --- /dev/null +++ b/src/duckdb/extension/parquet/include/parquet_int96.hpp @@ -0,0 +1,26 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// parquet_int96.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb.hpp" + +namespace duckdb { + +class ClientContext; +class Expression; + +//! Builds an expression that converts a raw 12-byte INT96 value (read as BLOB) into +//! STRUCT(date DATE, time TIME_NS). Used for the int96_as='struct' read option. +unique_ptr CreateInt96AsStructExpression(ClientContext &context); + +//! Builds an expression that returns the child at child_index of the struct conversion +//! (0 = date, 1 = time). Used for pushdown struct extracts on the int96_as='struct' column. +unique_ptr CreateInt96AsStructChildExpression(ClientContext &context, idx_t child_index); + +} // namespace duckdb diff --git a/src/duckdb/extension/parquet/include/parquet_reader.hpp b/src/duckdb/extension/parquet/include/parquet_reader.hpp index 57f3bc636..479ce7fb6 100644 --- a/src/duckdb/extension/parquet/include/parquet_reader.hpp +++ b/src/duckdb/extension/parquet/include/parquet_reader.hpp @@ -18,6 +18,7 @@ #include "duckdb.hpp" #include "duckdb/common/enum_util.hpp" #include "duckdb/common/helper.hpp" +#include "duckdb/common/once_flag.hpp" #include "duckdb/storage/external_file_cache/caching_file_system.hpp" #include "duckdb/common/common.hpp" #include "duckdb/common/encryption_functions.hpp" @@ -99,6 +100,8 @@ struct ParquetReaderPrefetchConfig { static constexpr double WHOLE_GROUP_PREFETCH_MINIMUM_SCAN = 0.95; //! How many row groups need to produce at least one surviving row (from filtering) static constexpr double PREFETCH_FILTER_MINIMUM_MATCH_RATIO = 0.9; + //! Before observing matches, only speculate on filter columns that are cheap to read + static constexpr double FIRST_GROUP_PREFETCH_MAXIMUM_FILTER_SCAN = 0.05; }; enum class ParquetPrefetchStrategy : uint8_t { @@ -123,6 +126,21 @@ const char *EnumUtil::ToChars(ParquetPrefetchStra template <> ParquetPrefetchStrategyOption EnumUtil::FromString(const char *value); +//! How INT96 (deprecated Impala timestamp) columns are exposed by the Parquet reader +enum class ParquetInt96AsOption : uint8_t { + TIMESTAMP, //! Read as TIMESTAMP (microsecond precision) - the default + TIMESTAMP_NS, //! Read as TIMESTAMP_NS, preserving the nanosecond part (out-of-range values saturate) + STRUCT //! Read as STRUCT(date DATE, time TIME_NS), preserving the full range and precision +}; + +ParquetInt96AsOption ParquetInt96AsOptionFromString(const string &value); + +template <> +const char *EnumUtil::ToChars(ParquetInt96AsOption value); + +template <> +ParquetInt96AsOption EnumUtil::FromString(const char *value); + template <> const char *EnumUtil::ToChars(StringColumnReader::Utf8ValidationOption value); @@ -285,6 +303,7 @@ struct ParquetOptions { ParquetPrefetchStrategyOption prefetch_strategy = ParquetPrefetchStrategyOption::AUTO; StringColumnReader::Utf8ValidationOption utf8_validation_option = StringColumnReader::Utf8ValidationOption::STRICT_UTF8; + ParquetInt96AsOption int96_as = ParquetInt96AsOption::TIMESTAMP; }; struct ParquetOptionsSerialization { @@ -496,12 +515,12 @@ class ParquetReader : public BaseFileReader { optional_ptr GetVariantStats(const ParquetColumnSchema &schema) const; //! Open a file handle for scanning, resolving the open flags from the prefetch mode unique_ptr OpenScanHandle(ClientContext &context) const; + //! Initialize the shared scan handle, including when pre-opening it for read-ahead + void InitializeScanHandle(ClientContext &context) const; private: unique_ptr file_handle; - //! Scan handle pre-opened by PrepareReadAhead while the file was opened, adopted by the first InitializeScan - mutable mutex prewarm_lock; - mutable unique_ptr prewarmed_scan_handle; + mutable OnceFlag scan_handle_initialized; //! Scan handle shared by all scan states of this reader mutable shared_ptr shared_scan_handle; mutable annotated_mutex variant_stats_lock; diff --git a/src/duckdb/extension/parquet/include/parquet_timestamp.hpp b/src/duckdb/extension/parquet/include/parquet_timestamp.hpp index 0f8d9491c..d99cec505 100644 --- a/src/duckdb/extension/parquet/include/parquet_timestamp.hpp +++ b/src/duckdb/extension/parquet/include/parquet_timestamp.hpp @@ -22,11 +22,16 @@ struct Int96 { }; timestamp_t ImpalaTimestampToTimestamp(const Int96 &raw_ts); +timestamp_ns_t ImpalaTimestampToTimestampNS(const Int96 &raw_ts); +date_t ImpalaTimestampToDate(const Int96 &raw_ts); +dtime_ns_t ImpalaTimestampToTimeNs(const Int96 &raw_ts); Int96 TimestampToImpalaTimestamp(timestamp_t &ts); timestamp_t ParquetTimestampMicrosToTimestamp(const int64_t &raw_ts); timestamp_t ParquetTimestampMsToTimestamp(const int64_t &raw_ts); +timestamp_ns_t ParquetTimestampMsToTimestampNs(const int64_t &raw_ms); +timestamp_ns_t ParquetTimestampUsToTimestampNs(const int64_t &raw_us); timestamp_ns_t ParquetTimestampNsToTimestampNs(const int64_t &raw_ns); date_t ParquetIntToDate(const int32_t &raw_date); diff --git a/src/duckdb/extension/parquet/include/reader/expression_column_reader.hpp b/src/duckdb/extension/parquet/include/reader/expression_column_reader.hpp index 48d2ae02b..f53fb1647 100644 --- a/src/duckdb/extension/parquet/include/reader/expression_column_reader.hpp +++ b/src/duckdb/extension/parquet/include/reader/expression_column_reader.hpp @@ -69,12 +69,10 @@ class ExpressionColumnReader : public ColumnReader { void Skip(idx_t num_values) override; idx_t GroupRowsAvailable() override; - uint64_t TotalCompressedSize() override { - idx_t total_compressed_size = 0; + void GatherColumnChunks(unordered_set &column_chunks) override { for (auto &child_reader : child_readers) { - total_compressed_size += child_reader->TotalCompressedSize(); + child_reader->GatherColumnChunks(column_chunks); } - return total_compressed_size; } idx_t FileOffset() const override { diff --git a/src/duckdb/extension/parquet/include/reader/list_column_reader.hpp b/src/duckdb/extension/parquet/include/reader/list_column_reader.hpp index 0c7a5216d..66ffee086 100644 --- a/src/duckdb/extension/parquet/include/reader/list_column_reader.hpp +++ b/src/duckdb/extension/parquet/include/reader/list_column_reader.hpp @@ -35,8 +35,8 @@ class ListColumnReader : public ColumnReader { return child_column_reader->GroupRowsAvailable() + overflow_child_count; } - uint64_t TotalCompressedSize() override { - return child_column_reader->TotalCompressedSize(); + void GatherColumnChunks(unordered_set &column_chunks) override { + child_column_reader->GatherColumnChunks(column_chunks); } void RegisterPrefetch(ThriftFileTransport &transport, bool allow_merge) override { diff --git a/src/duckdb/extension/parquet/include/reader/row_number_column_reader.hpp b/src/duckdb/extension/parquet/include/reader/row_number_column_reader.hpp index 5fd5e9ed0..616a7fd48 100644 --- a/src/duckdb/extension/parquet/include/reader/row_number_column_reader.hpp +++ b/src/duckdb/extension/parquet/include/reader/row_number_column_reader.hpp @@ -59,8 +59,7 @@ class RowNumberColumnReader : public ColumnReader { idx_t GroupRowsAvailable() override { return NumericLimits::Maximum(); }; - uint64_t TotalCompressedSize() override { - return 0; + void GatherColumnChunks(unordered_set &column_chunks) override { } idx_t FileOffset() const override { return 0; @@ -91,8 +90,7 @@ class RowGroupColumnReader : public ColumnReader { idx_t GroupRowsAvailable() override { return NumericLimits::Maximum(); }; - uint64_t TotalCompressedSize() override { - return 0; + void GatherColumnChunks(unordered_set &column_chunks) override { } idx_t FileOffset() const override { return 0; diff --git a/src/duckdb/extension/parquet/include/reader/struct_column_reader.hpp b/src/duckdb/extension/parquet/include/reader/struct_column_reader.hpp index 0602aca67..cac63ecec 100644 --- a/src/duckdb/extension/parquet/include/reader/struct_column_reader.hpp +++ b/src/duckdb/extension/parquet/include/reader/struct_column_reader.hpp @@ -33,7 +33,7 @@ class StructColumnReader : public ColumnReader { void Skip(idx_t num_values) override; idx_t GroupRowsAvailable() override; - uint64_t TotalCompressedSize() override; + void GatherColumnChunks(unordered_set &column_chunks) override; void RegisterPrefetch(ThriftFileTransport &transport, bool allow_merge) override; }; diff --git a/src/duckdb/extension/parquet/include/reader/variant_column_reader.hpp b/src/duckdb/extension/parquet/include/reader/variant_column_reader.hpp index 19f0d94b4..b4037b9cb 100644 --- a/src/duckdb/extension/parquet/include/reader/variant_column_reader.hpp +++ b/src/duckdb/extension/parquet/include/reader/variant_column_reader.hpp @@ -45,7 +45,7 @@ class VariantColumnReader : public ColumnReader { idx_t GroupRowsAvailable() override; void Convert(Vector &metadata, Vector &group, Vector &result, idx_t count); void PrepareChunk(DataChunk &chunk, idx_t &capacity, const vector &types, idx_t count); - uint64_t TotalCompressedSize() override; + void GatherColumnChunks(unordered_set &column_chunks) override; void RegisterPrefetch(ThriftFileTransport &transport, bool allow_merge) override; static bool TypedValueLayoutToType(const LogicalType &typed_value, LogicalType &logical_type); diff --git a/src/duckdb/extension/parquet/include/thrift_tools.hpp b/src/duckdb/extension/parquet/include/thrift_tools.hpp index 5b2e0aa8c..9985ee65b 100644 --- a/src/duckdb/extension/parquet/include/thrift_tools.hpp +++ b/src/duckdb/extension/parquet/include/thrift_tools.hpp @@ -136,13 +136,20 @@ struct ReadAheadBuffer { } // Returns the relevant read head - ReadHead *GetReadHead(idx_t pos) { + ReadHead *GetReadHead(idx_t pos, idx_t len = 1) { + optional_ptr unfetched_head; for (auto &read_head : read_heads) { - if (pos >= read_head->location && pos < read_head->GetEnd()) { - return read_head.get(); + if (pos >= read_head->location && pos < read_head->GetEnd() && len <= read_head->GetEnd() - pos) { + // Filter and payload ranges can overlap: reuse fetched bytes before fetching another range. + if (read_head->data_isset) { + return read_head.get(); + } + if (!unfetched_head) { + unfetched_head = read_head.get(); + } } } - return nullptr; + return unfetched_head.get(); } // Prefetch all read heads @@ -175,7 +182,7 @@ class ThriftFileTransport : public duckdb_apache::thrift::transport::TVirtualTra } uint32_t read(uint8_t *buf, uint32_t len) { - auto prefetch_buffer = ra_buffer.GetReadHead(location); + auto prefetch_buffer = ra_buffer.GetReadHead(location, len); if (prefetch_buffer != nullptr && location - prefetch_buffer->location + len <= prefetch_buffer->size) { D_ASSERT(location - prefetch_buffer->location + len <= prefetch_buffer->size); @@ -191,7 +198,7 @@ class ThriftFileTransport : public duckdb_apache::thrift::transport::TVirtualTra return len; } Prefetch(location, MinValue(PREFETCH_FALLBACK_BUFFERSIZE, file_size - location)); - auto prefetch_buffer_fallback = ra_buffer.GetReadHead(location); + auto prefetch_buffer_fallback = ra_buffer.GetReadHead(location, len); if (!prefetch_buffer_fallback || location - prefetch_buffer_fallback->location + len > prefetch_buffer_fallback->size) { file_handle.GetFileHandle()->Read(context, buf, len, location); diff --git a/src/duckdb/extension/parquet/parquet_extension.cpp b/src/duckdb/extension/parquet/parquet_extension.cpp index e6c0a17cb..f14943469 100644 --- a/src/duckdb/extension/parquet/parquet_extension.cpp +++ b/src/duckdb/extension/parquet/parquet_extension.cpp @@ -160,6 +160,7 @@ static void ParquetListCopyOptions(ClientContext &context, CopyOptionsInput &inp copy_options["binary_as_string"] = CopyOption(LogicalType::BOOLEAN, CopyOptionMode::READ_ONLY); copy_options["file_row_number"] = CopyOption(LogicalType::BOOLEAN, CopyOptionMode::READ_ONLY); copy_options["can_have_nan"] = CopyOption(LogicalType::BOOLEAN, CopyOptionMode::READ_ONLY); + copy_options["int96_as"] = CopyOption(LogicalType::VARCHAR, CopyOptionMode::READ_ONLY); copy_options["geoparquet_version"] = CopyOption(LogicalType::VARCHAR, CopyOptionMode::WRITE_ONLY); copy_options["shredding"] = CopyOption(LogicalType::ANY, CopyOptionMode::WRITE_ONLY); copy_options["write_timestamp_as_int96"] = CopyOption(LogicalType::BOOLEAN, CopyOptionMode::WRITE_ONLY); @@ -321,8 +322,8 @@ static unique_ptr ParquetWriteBind(ClientContext &context, CopyFun bind_data->enable_bloom_filters = BooleanValue::Get(option_values[0].DefaultCastAs(LogicalType::BOOLEAN)); } else if (option_name == "bloom_filter_false_positive_ratio") { auto val = option_values[0].GetValue(); - if (val <= 0) { - throw BinderException("bloom_filter_false_positive_ratio must be greater than 0"); + if (!(val > 0 && val <= 1)) { + throw BinderException("bloom_filter_false_positive_ratio must be greater than 0 and at most 1"); } bind_data->bloom_filter_false_positive_ratio = val; } else if (option_name == "compression_level") { @@ -608,6 +609,34 @@ ParquetPrefetchStrategyOption EnumUtil::FromString +const char *EnumUtil::ToChars(ParquetInt96AsOption value) { + switch (value) { + case ParquetInt96AsOption::TIMESTAMP: + return "TIMESTAMP"; + case ParquetInt96AsOption::TIMESTAMP_NS: + return "TIMESTAMP_NS"; + case ParquetInt96AsOption::STRUCT: + return "STRUCT"; + default: + throw NotImplementedException(StringUtil::Format("Enum value: '%s' not implemented", value)); + } +} + +template <> +ParquetInt96AsOption EnumUtil::FromString(const char *value) { + if (StringUtil::Equals(value, "TIMESTAMP")) { + return ParquetInt96AsOption::TIMESTAMP; + } + if (StringUtil::Equals(value, "TIMESTAMP_NS")) { + return ParquetInt96AsOption::TIMESTAMP_NS; + } + if (StringUtil::Equals(value, "STRUCT")) { + return ParquetInt96AsOption::STRUCT; + } + throw NotImplementedException(StringUtil::Format("Enum value: '%s' not implemented", value)); +} + template <> const char * EnumUtil::ToChars(StringColumnReader::Utf8ValidationOption value) { diff --git a/src/duckdb/extension/parquet/parquet_int96.cpp b/src/duckdb/extension/parquet/parquet_int96.cpp new file mode 100644 index 000000000..2178ad62e --- /dev/null +++ b/src/duckdb/extension/parquet/parquet_int96.cpp @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// parquet_int96.cpp +// +// +//===----------------------------------------------------------------------===// + +#include "parquet_int96.hpp" + +#include + +#include "duckdb/common/types/date.hpp" +#include "duckdb/common/types/time.hpp" +#include "duckdb/common/vector/vector_iterator.hpp" +#include "duckdb/common/vector/vector_writer.hpp" +#include "duckdb/function/scalar/struct_functions.hpp" +#include "duckdb/function/scalar_function.hpp" +#include "duckdb/planner/expression/bound_constant_expression.hpp" +#include "duckdb/planner/expression/bound_function_expression.hpp" +#include "duckdb/planner/expression/bound_reference_expression.hpp" +#include "parquet_timestamp.hpp" + +namespace duckdb { + +static LogicalType Int96StructType() { + return LogicalType::STRUCT({{"date", LogicalType::DATE}, {"time", LogicalType::TIME_NS}}); +} + +static void Int96AsStructFunction(DataChunk &args, ExpressionState &state, Vector &result) { + auto count = args.size(); + auto blob_values = args.data[0].Values(); + auto writer = FlatVector::Writer>(result, count); + for (auto entry : blob_values) { + if (!entry.IsValid()) { + writer.WriteNull(); + continue; + } + auto blob = entry.GetValue(); + Int96 raw_ts; + memcpy(raw_ts.value, blob.GetData(), sizeof(Int96)); + writer.WriteValue([&](auto &date_writer, auto &time_writer) { + date_writer.WriteValue(ImpalaTimestampToDate(raw_ts)); + time_writer.WriteValue(ImpalaTimestampToTimeNs(raw_ts)); + }); + } +} + +unique_ptr CreateInt96AsStructExpression(ClientContext &context) { + auto args = vector>(); + auto ref = make_uniq_base(LogicalTypeId::BLOB, 0); + args.push_back(std::move(ref)); + ScalarFunction func("__internal_int96_as_struct", vector {LogicalType::BLOB}, Int96StructType(), + Int96AsStructFunction); + return func.Bind(context, std::move(args)); +} + +unique_ptr CreateInt96AsStructChildExpression(ClientContext &context, idx_t child_index) { + auto args = vector>(); + args.push_back(CreateInt96AsStructExpression(context)); + args.push_back( + make_uniq_base(Value::BIGINT(NumericCast(child_index + 1)))); + return StructExtractAtFun::GetFunction().Bind(context, std::move(args)); +} + +} // namespace duckdb diff --git a/src/duckdb/extension/parquet/parquet_multi_file_info.cpp b/src/duckdb/extension/parquet/parquet_multi_file_info.cpp index d9befafef..3cb60934a 100644 --- a/src/duckdb/extension/parquet/parquet_multi_file_info.cpp +++ b/src/duckdb/extension/parquet/parquet_multi_file_info.cpp @@ -223,7 +223,18 @@ static vector ParquetMultiFileGetPartitionStats(ClientConte GetPartitionStatsInput &input) { auto &bind_data = input.bind_data->Cast(); if (bind_data.file_list->GetExpandResult() == FileExpandResult::SINGLE_FILE) { - return TableFunctionMultiFileWrapper::GetPartitionStats(context, input); + auto result = TableFunctionMultiFileWrapper::GetPartitionStats(context, input); + if (!bind_data.file_options.bound_schema.empty()) { + // Column accessors address physical ordinals and types, not the mapped output or generated roles. + const bool identity = + bind_data.initial_reader && bind_data.HasIdentityMapping(bind_data.initial_reader->GetColumns()); + if (!identity) { + for (auto &partition : result) { + partition.partition_row_group.reset(); + } + } + } + return result; } // without the bind of a file we do not know the key the files are encrypted with - encrypted files are then not // answered from their metadata @@ -245,7 +256,8 @@ void ParquetScanFunction::AddNamedParameters(TableFunction &table_function) { .Add("parquet_version", LogicalType::VARCHAR) .Add("can_have_nan", LogicalType::BOOLEAN) .Add("prefetch_strategy", LogicalType::VARCHAR) - .Add("utf8_validation", LogicalType::VARCHAR); + .Add("utf8_validation", LogicalType::VARCHAR) + .Add("int96_as", LogicalType::VARCHAR); }; // the multi-file function already declares options of its own - the single-file function declares none auto &signature = table_function.GetSignature(); @@ -270,6 +282,8 @@ TableFunction ParquetScanFunction::GetMultiFileFunction(Identifier name) { // the multi-file parquet reader is the single-file parquet reader wrapped into a multi-file function auto result = TableFunctionMultiFileWrapper::CreateFunction(GetSingleFileFunction(), std::move(name), GetMultiFileSettings(), ParquetMultiFileBind); + result.GetSignature().ExtendTypedKwargs( + [](TypedKwargs &options) { options.Add("bound_schema", LogicalType::ANY); }); // the callbacks below describe the scan rather than one of its files, so they are set on the wrapper result.get_row_id_columns = ParquetGetRowIdColumns; result.supports_pushdown_extract = ParquetScanSupportPushdownExtract; @@ -329,6 +343,13 @@ bool ParquetMultiFileInfo::ParseCopyOption(ClientContext &context, const Identif options.utf8_validation_option = StringColumnReader::GetUtf8ValidationOption(StringValue::Get(values[0])); return true; } + if (key == "int96_as") { + if (values.size() != 1) { + throw BinderException("Parquet int96_as cannot be empty!"); + } + options.int96_as = ParquetInt96AsOptionFromString(StringValue::Get(values[0])); + return true; + } return false; } @@ -371,6 +392,10 @@ bool ParquetMultiFileInfo::ParseOption(ClientContext &context, const Identifier options.utf8_validation_option = StringColumnReader::GetUtf8ValidationOption(StringValue::Get(val)); return true; } + if (key == "int96_as") { + options.int96_as = ParquetInt96AsOptionFromString(StringValue::Get(val)); + return true; + } return false; } diff --git a/src/duckdb/extension/parquet/parquet_reader.cpp b/src/duckdb/extension/parquet/parquet_reader.cpp index 2b45188c3..808bce83c 100644 --- a/src/duckdb/extension/parquet/parquet_reader.cpp +++ b/src/duckdb/extension/parquet/parquet_reader.cpp @@ -13,6 +13,7 @@ #include "reader/byte_array_length_column_reader.hpp" #include "reader/expression_column_reader.hpp" #include "parquet_geometry.hpp" +#include "parquet_int96.hpp" #include "reader/list_column_reader.hpp" #include "parquet_crypto.hpp" #include "parquet_file_metadata_cache.hpp" @@ -20,6 +21,7 @@ #include "reader/row_number_column_reader.hpp" #include "reader/variant_column_reader.hpp" #include "reader/struct_column_reader.hpp" +#include "reader/string_column_reader.hpp" #include "thrift_tools.hpp" #include "parquet_prefetch_cost_model.hpp" #include "duckdb/common/encryption_state.hpp" @@ -29,8 +31,10 @@ #include "duckdb/storage/object_cache.hpp" #include "duckdb/planner/table_filter_state.hpp" #include "duckdb/common/multi_file/multi_file_adaptive_filter_cache.hpp" +#include "duckdb/common/multi_file/multi_file_data.hpp" #include "duckdb/common/multi_file/multi_file_reader.hpp" #include "duckdb/common/types/geometry_crs.hpp" +#include "duckdb/common/types.hpp" #include "duckdb/common/allocator.hpp" #include "duckdb/common/assert.hpp" #include "duckdb/common/case_insensitive_map.hpp" @@ -107,6 +111,20 @@ ParquetPrefetchStrategyOption ParquetPrefetchStrategyOptionFromString(const stri throw BinderException("Unrecognized prefetch_strategy '%s' (supported: 'auto', 'whole_group')", value); } +ParquetInt96AsOption ParquetInt96AsOptionFromString(const string &value) { + auto lower = StringUtil::Lower(value); + if (lower == "timestamp") { + return ParquetInt96AsOption::TIMESTAMP; + } + if (lower == "timestamp_ns") { + return ParquetInt96AsOption::TIMESTAMP_NS; + } + if (lower == "struct") { + return ParquetInt96AsOption::STRUCT; + } + throw BinderException("Unrecognized int96_as '%s' (supported: 'timestamp', 'timestamp_ns', 'struct')", value); +} + static idx_t ParquetColumnChunkFileOffset(const duckdb_parquet::ColumnChunk &chunk) { idx_t offset = NumericCast(chunk.meta_data.data_page_offset); if (chunk.meta_data.__isset.dictionary_page_offset) { @@ -597,7 +615,16 @@ LogicalType ParquetReader::DeriveLogicalType(const SchemaElement &s_ele, const P return LogicalType::BIGINT; case Type::INT96: // always a timestamp it would seem schema.type_info = ParquetExtraTypeInfo::IMPALA_TIMESTAMP; - return LogicalType(ParquetTimestampLogicalType(schema.type_info)); + switch (parquet_options.int96_as) { + case ParquetInt96AsOption::TIMESTAMP: + return LogicalType::TIMESTAMP; + case ParquetInt96AsOption::TIMESTAMP_NS: + return LogicalType::TIMESTAMP_NS; + case ParquetInt96AsOption::STRUCT: + return LogicalType::STRUCT({{"date", LogicalType::DATE}, {"time", LogicalType::TIME_NS}}); + default: + throw InternalException("Unrecognized int96_as option"); + } case Type::FLOAT: return LogicalType::FLOAT; case Type::DOUBLE: @@ -789,6 +816,43 @@ static ColumnIndex CreateVariantTypedValuePushdown(const ParquetColumnSchema &sc return result_index; } +static unique_ptr CreateInt96StructReader(ClientContext &context, const ParquetReader &reader, + const ParquetColumnSchema &schema, + optional_ptr pushdown_child) { + // Read the raw 12-byte INT96 value as a BLOB - this is lossless over the full range INT96 can express + // (0001-01-01 through 9999-12-31), unlike TIMESTAMP/TIMESTAMP_NS which clamp or truncate + // The blob schema is a copy of the leaf schema with a different type; it is kept alive on the + // expression reader's heap (the ColumnReader base class only holds a reference to it) + if (pushdown_child) { + // The scan only exposes a single child of the INT96-as-struct column (e.g. ts['date']). + // Follow the same pattern as a pushed-down extract on a regular STRUCT column: wrap the leaf in an + // EXPRESSION schema carrying the child's type so the reader reports the child type and produces no + // (struct) row group statistics, which a filter on the child would otherwise be checked against. + auto expr = CreateInt96AsStructChildExpression(context, pushdown_child->GetPrimaryIndex()); + if (expr->GetReturnType() != pushdown_child->GetType()) { + expr = BoundCastExpression::AddCastToType(context, std::move(expr), pushdown_child->GetType()); + } + auto expr_schema = make_uniq( + ParquetColumnSchema::FromParentSchema(schema, expr->GetReturnType(), ParquetColumnSchemaType::EXPRESSION)); + // FromParentSchema embeds a copy of the leaf schema as its first child - retype that copy to BLOB so the + // embedded child is the raw INT96 leaf and stays alive through the expression schema's ownership + expr_schema->children[0].type = LogicalType::BLOB; + vector> children; + children.push_back(make_uniq(reader, expr_schema->children[0])); + return make_uniq(context, std::move(children), std::move(expr), std::move(expr_schema)); + } + auto child_schema = make_uniq(schema); + child_schema->type = LogicalType::BLOB; + auto blob_reader = make_uniq(reader, *child_schema); + + vector> children; + children.push_back(std::move(blob_reader)); + auto expr = CreateInt96AsStructExpression(context); + auto result = make_uniq(context, std::move(children), std::move(expr), schema); + result->owned_schema = std::move(child_schema); + return std::move(result); +} + unique_ptr ParquetReader::CreateReaderRecursive(ClientContext &context, const ColumnIndex &column_id, const ParquetColumnSchema &schema) const { auto &indexes = column_id.GetChildIndexes(); @@ -803,6 +867,11 @@ unique_ptr ParquetReader::CreateReaderRecursive(ClientContext &con } case ParquetColumnSchemaType::COLUMN: { if (schema.children.empty()) { + if (schema.parquet_type == Type::INT96 && schema.type.id() == LogicalTypeId::STRUCT) { + // int96_as='struct' exposes the raw INT96 value as STRUCT(date DATE, time TIME_NS) + return CreateInt96StructReader(context, *this, schema, + column_id.IsPushdownExtract() ? &indexes[0] : nullptr); + } // leaf reader return ColumnReader::CreateReader(*this, schema); } @@ -1210,6 +1279,18 @@ MultiFileColumnDefinition ParquetReader::ParseColumnDefinition(const FileMetaDat result.children.push_back(ParseColumnDefinition(file_meta_data, child)); } } + // An INT96 column exposed as a STRUCT (int96_as='struct') is a logical struct over a physical leaf - synthesize + // the struct children here, mirroring MultiFileColumnDefinition::CreateFromNameAndType, so that the column + // definition matches the global one (and the multi-file mapper can map the column trivially). + if (element.schema_type == ParquetColumnSchemaType::COLUMN && element.parquet_type == Type::INT96 && + element.type.id() == LogicalTypeId::STRUCT && parquet_options.int96_as == ParquetInt96AsOption::STRUCT) { + result.children.clear(); + result.children.reserve(StructType::GetChildTypes(element.type).size()); + for (auto &child_entry : StructType::GetChildTypes(element.type)) { + result.children.push_back( + MultiFileColumnDefinition::CreateFromNameAndType(child_entry.first, child_entry.second)); + } + } return result; } @@ -1823,9 +1904,11 @@ unique_ptr ParquetReader::OpenScanHandle(ClientContext &conte void ParquetReader::PrepareReadAhead(ClientContext &context, GlobalTableFunctionState &) { // pre-open the scan handle while the file is opened, so the first InitializeScan skips its file-open - auto handle = OpenScanHandle(context); - lock_guard guard(prewarm_lock); - prewarmed_scan_handle = std::move(handle); + InitializeScanHandle(context); +} + +void ParquetReader::InitializeScanHandle(ClientContext &context) const { + CallOnce(scan_handle_initialized, [&] { shared_scan_handle = OpenScanHandle(context); }); } void ParquetReader::InitializeScan(ClientContext &context, ParquetReaderScanState &state, idx_t group_to_read) const { @@ -1838,14 +1921,7 @@ void ParquetReader::InitializeScan(ClientContext &context, ParquetReaderScanStat state.prefetch_mode = ShouldAndCanPrefetch(context, *file_handle); // all scan states share one handle (opened with parallel access), so open handles and // connections scale with the number of readers instead of the number of row-group jobs - lock_guard guard(prewarm_lock); - if (!shared_scan_handle) { - if (prewarmed_scan_handle) { - shared_scan_handle = std::move(prewarmed_scan_handle); - } else { - shared_scan_handle = OpenScanHandle(context); - } - } + InitializeScanHandle(context); state.file_handle = shared_scan_handle; } state.scan_filters.clear(); @@ -2120,14 +2196,18 @@ ParquetPrefetchStrategy ParquetReader::RegisterRowGroupReads(ClientContext &cont // TODO: only need this if we have a deletion vector? state.group_offset = GetRowGroupOffset(*this, state.group_index); - uint64_t to_scan_compressed_bytes = 0; + auto &group = GetGroup(state); + // several readers can read the same column chunk (e.g. two fields of one variant column), count each chunk once + unordered_set scanned_column_chunks; for (idx_t i = 0; i < column_ids.size(); i++) { auto col_idx = MultiFileLocalIndex(i); PrepareRowGroupBuffer(context, state, col_idx); - to_scan_compressed_bytes += state.GetColumnReader(i).TotalCompressedSize(); + state.GetColumnReader(i).GatherColumnChunks(scanned_column_chunks); + } + uint64_t to_scan_compressed_bytes = 0; + for (auto column_chunk : scanned_column_chunks) { + to_scan_compressed_bytes += NumericCast(group.columns[column_chunk].meta_data.total_compressed_size); } - - auto &group = GetGroup(state); const bool row_group_skipped = state.offset_in_group == (idx_t)group.num_rows; if (row_group_skipped) { ++state.row_groups_skipped; @@ -2165,10 +2245,27 @@ ParquetPrefetchStrategy ParquetReader::RegisterRowGroupReads(ClientContext &cont if (filters) { if (state.prefetch_metrics.row_groups_executed == 0) { filters_look_unselective = true; - } else if (static_cast(state.prefetch_metrics.row_groups_with_matches) / - static_cast(state.prefetch_metrics.row_groups_executed) > - ParquetReaderPrefetchConfig::PREFETCH_FILTER_MINIMUM_MATCH_RATIO) { - filters_look_unselective = true; + if (scan_percentage > ParquetReaderPrefetchConfig::WHOLE_GROUP_PREFETCH_MINIMUM_SCAN) { + unordered_set filter_column_chunks; + for (auto &scan_filter : state.scan_filters) { + state.GetColumnReader(scan_filter.filter_idx.GetIndex()) + .GatherColumnChunks(filter_column_chunks); + } + uint64_t filter_compressed_bytes = 0; + for (auto column_chunk : filter_column_chunks) { + filter_compressed_bytes += + NumericCast(group.columns[column_chunk].meta_data.total_compressed_size); + } + filters_look_unselective = + filter_column_chunks.empty() || + static_cast(filter_compressed_bytes) / + static_cast(total_row_group_span) > + ParquetReaderPrefetchConfig::FIRST_GROUP_PREFETCH_MAXIMUM_FILTER_SCAN; + } + } else { + filters_look_unselective = static_cast(state.prefetch_metrics.row_groups_with_matches) / + static_cast(state.prefetch_metrics.row_groups_executed) > + ParquetReaderPrefetchConfig::PREFETCH_FILTER_MINIMUM_MATCH_RATIO; } } diff --git a/src/duckdb/extension/parquet/parquet_statistics.cpp b/src/duckdb/extension/parquet/parquet_statistics.cpp index 2d4a8d062..62ff20a12 100644 --- a/src/duckdb/extension/parquet/parquet_statistics.cpp +++ b/src/duckdb/extension/parquet/parquet_statistics.cpp @@ -335,15 +335,32 @@ Value ParquetStatisticsUtils::ConvertValueInternal(const LogicalType &type, cons } case LogicalTypeId::TIMESTAMP_TZ_NS: case LogicalTypeId::TIMESTAMP_NS: { - if (stats.size() != sizeof(int64_t)) { - throw InvalidInputException("Incorrect stats size for type TIMESTAMP_NS"); - } - if (schema_ele.type_info != ParquetExtraTypeInfo::UNIT_NS) { - throw InternalException("TIMESTAMP_NS requires nanosecond type info"); + timestamp_ns_t timestamp_value; + if (schema_ele.type_info == ParquetExtraTypeInfo::IMPALA_TIMESTAMP) { + if (stats.size() != sizeof(Int96)) { + throw InvalidInputException("Incorrect stats size for type TIMESTAMP_NS"); + } + timestamp_value = ImpalaTimestampToTimestampNS(Load(stats_data)); + } else { + if (stats.size() != sizeof(int64_t)) { + throw InvalidInputException("Incorrect stats size for type TIMESTAMP_NS"); + } + auto val = Load(stats_data); + switch (schema_ele.type_info) { + case ParquetExtraTypeInfo::UNIT_MS: + timestamp_value = ParquetTimestampMsToTimestampNs(val); + break; + case ParquetExtraTypeInfo::UNIT_NS: + timestamp_value = ParquetTimestampNsToTimestampNs(val); + break; + case ParquetExtraTypeInfo::UNIT_MICROS: + default: + timestamp_value = ParquetTimestampUsToTimestampNs(val); + break; + } } - auto timestamp_value = ParquetTimestampNsToTimestampNs(Load(stats_data)); if (type.id() == LogicalTypeId::TIMESTAMP_TZ_NS) { - return Value::TIMESTAMPTZNS(timestamp_tz_ns_t(timestamp_value)); + return Value::TIMESTAMPTZNS(timestamp_tz_ns_t(timestamp_value.value)); } return Value::TIMESTAMPNS(timestamp_value); } @@ -651,7 +668,9 @@ unique_ptr ParquetStatisticsUtils::TransformColumnStatistics(con //! Convert the root 'value' -> 'untyped_value_index' auto &value = schema.children[1]; - D_ASSERT(value.name == "value"); + if (schema.children[0].name != "metadata" || value.name != "value") { + return nullptr; + } auto value_stats = ParquetStatisticsUtils::TransformColumnStatistics(value, columns, can_have_nan); //! Best-effort: nodes whose stats can't be converted are left UNKNOWN (not fully shredded) rather //! than discarding the statistics for the entire variant column diff --git a/src/duckdb/extension/parquet/parquet_timestamp.cpp b/src/duckdb/extension/parquet/parquet_timestamp.cpp index 70302136c..1cddc882d 100644 --- a/src/duckdb/extension/parquet/parquet_timestamp.cpp +++ b/src/duckdb/extension/parquet/parquet_timestamp.cpp @@ -1,5 +1,7 @@ #include "parquet_timestamp.hpp" +#include "duckdb/common/operator/add.hpp" +#include "duckdb/common/operator/multiply.hpp" #include "duckdb/common/types/date.hpp" #include "duckdb/common/types/time.hpp" #include "duckdb/common/types/timestamp.hpp" @@ -14,6 +16,7 @@ static constexpr int64_t JULIAN_TO_UNIX_EPOCH_DAYS = 2440588LL; static constexpr int64_t MILLISECONDS_PER_DAY = 86400000LL; static constexpr int64_t MICROSECONDS_PER_DAY = MILLISECONDS_PER_DAY * 1000LL; static constexpr int64_t NANOSECONDS_PER_MICRO = 1000LL; +static constexpr int64_t NANOSECONDS_PER_DAY = MICROSECONDS_PER_DAY * 1000LL; static inline int64_t ImpalaTimestampToDays(const Int96 &impala_timestamp) { return impala_timestamp.value[2] - JULIAN_TO_UNIX_EPOCH_DAYS; @@ -26,11 +29,47 @@ static int64_t ImpalaTimestampToMicroseconds(const Int96 &impala_timestamp) { return days_since_epoch * MICROSECONDS_PER_DAY + microseconds; } +static int64_t ImpalaTimestampToNanoseconds(const Int96 &impala_timestamp) { + int64_t result; + int64_t days_since_epoch = ImpalaTimestampToDays(impala_timestamp); + auto nanoseconds = Load(const_data_ptr_cast(impala_timestamp.value)); + int64_t day_nanoseconds; + if (days_since_epoch < 0) { + // Don't saturate the minimum value by going past it + if (!TryMultiplyOperator::Operation(days_since_epoch + 1, NANOSECONDS_PER_DAY, day_nanoseconds) || + !TryAddOperator::Operation(day_nanoseconds, nanoseconds - NANOSECONDS_PER_DAY, result)) { + // out of range for TIMESTAMP_NS - saturate to -infinity + return timestamp_ns_t::ninfinity().value; + } + return result; + } + if (!TryMultiplyOperator::Operation(days_since_epoch, NANOSECONDS_PER_DAY, day_nanoseconds) || + !TryAddOperator::Operation(day_nanoseconds, nanoseconds, result)) { + // out of range for TIMESTAMP_NS - saturate to +infinity + return timestamp_ns_t::infinity().value; + } + return result; +} + +timestamp_ns_t ImpalaTimestampToTimestampNS(const Int96 &raw_ts) { + timestamp_ns_t result; + result.value = ImpalaTimestampToNanoseconds(raw_ts); + return result; +} + timestamp_t ImpalaTimestampToTimestamp(const Int96 &raw_ts) { auto impala_us = ImpalaTimestampToMicroseconds(raw_ts); return Timestamp::FromEpochMicroSeconds(impala_us); } +date_t ImpalaTimestampToDate(const Int96 &raw_ts) { + return date_t(NumericCast(ImpalaTimestampToDays(raw_ts))); +} + +dtime_ns_t ImpalaTimestampToTimeNs(const Int96 &raw_ts) { + return dtime_ns_t(Load(const_data_ptr_cast(raw_ts.value))); +} + Int96 TimestampToImpalaTimestamp(timestamp_t &ts) { int32_t hour, min, sec, micros; Time::Convert(Timestamp::GetTime(ts), hour, min, sec, micros); @@ -57,6 +96,24 @@ timestamp_t ParquetTimestampMsToTimestamp(const int64_t &raw_ts) { return Timestamp::FromEpochMs(raw_ts); } +timestamp_ns_t ParquetTimestampMsToTimestampNs(const int64_t &raw_ms) { + timestamp_ns_t input; + input.value = raw_ms; + if (!input.IsFinite()) { + return input; + } + return Timestamp::TimestampNsFromEpochMillis(raw_ms); +} + +timestamp_ns_t ParquetTimestampUsToTimestampNs(const int64_t &raw_us) { + timestamp_ns_t input; + input.value = raw_us; + if (!input.IsFinite()) { + return input; + } + return Timestamp::TimestampNsFromEpochMicros(raw_us); +} + timestamp_ns_t ParquetTimestampNsToTimestampNs(const int64_t &raw_ns) { timestamp_ns_t result; result.value = raw_ns; diff --git a/src/duckdb/extension/parquet/read_single_parquet_file.cpp b/src/duckdb/extension/parquet/read_single_parquet_file.cpp index 9447a8aaa..2739185de 100644 --- a/src/duckdb/extension/parquet/read_single_parquet_file.cpp +++ b/src/duckdb/extension/parquet/read_single_parquet_file.cpp @@ -4,6 +4,7 @@ #include "duckdb/common/enum_util.hpp" #include "parquet_reader.hpp" #include "duckdb/common/mutex.hpp" +#include "duckdb/common/once_flag.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/planner/operator/logical_get.hpp" @@ -49,17 +50,16 @@ struct ReadSingleParquetFileData : public TableFunctionData { //! A reader over the metadata of the file, created on first use. The row groups it describes refer to the schema //! it holds, so it is kept for as long as this bind data is ParquetReader &GetMetadataReader(ClientContext &context) const { - lock_guard guard(bind_reader_lock); - if (!metadata_reader) { - metadata_reader = ParquetReader::CreateMetadataReader(context, options, metadata); - } + CallOnce(metadata_reader_initialized, + [&] { metadata_reader = ParquetReader::CreateMetadataReader(context, options, metadata); }); return *metadata_reader; } private: - //! Guards the readers below + //! Guards the transfer of the bind reader mutable mutex bind_reader_lock; mutable shared_ptr bind_reader; + mutable OnceFlag metadata_reader_initialized; mutable shared_ptr metadata_reader; }; @@ -276,7 +276,16 @@ static bool ReadSingleParquetFileClaimBatch(ClientContext &context, TableFunctio auto &lstate = input.local_state->Cast(); // our caller hands out the row groups, so we must not claim the next one ourselves lstate.claimed_externally = true; - return ClaimNextRowGroup(context, gstate, lstate); + // our caller holds the lock of its scan here - the claimed row group is prepared in the prepare_batch callback + lock_guard guard(gstate.lock); + return gstate.reader->TryInitializeScan(context, gstate.state, lstate.state); +} + +static void ReadSingleParquetFilePrepareBatch(ClientContext &context, TableFunctionInput &input) { + auto &gstate = input.global_state->Cast(); + auto &lstate = input.local_state->Cast(); + gstate.reader->PrepareScan(context, gstate.state, lstate.state); + lstate.has_row_group = true; } //! Pre-open the handle the scan reads the file through, so that claiming a row group does not have to open the file @@ -399,8 +408,7 @@ ReadSingleParquetFileStatistics(ClientContext &context, const FunctionData *bind if (column_index >= parquet_data.parquet_names.size()) { return nullptr; } - return ParquetReader::ReadStatistics(context, parquet_data.options, parquet_data.metadata, - parquet_data.parquet_names[column_index]); + return parquet_data.GetMetadataReader(context).ReadStatistics(parquet_data.parquet_names[column_index]); } //! The row groups read from this file. The counts accumulate, so that a scan over several files reports their sum @@ -453,13 +461,8 @@ bool ParquetScanFunction::ProjectionExpressionPushdown(ClientContext &context, return false; } const idx_t idx = column_id.GetPrimaryIndex(); - // Don't do pushdown of strlen to hive and filename columns - for (const auto &partition : bind_data.reader_bind.hive_partitioning_indexes) { - if (partition.index == idx) { - return false; - } - } - if (bind_data.reader_bind.filename_idx.IsValid() && bind_data.reader_bind.filename_idx.GetIndex() == idx) { + // Generated values do not come from the physical column, even when its name and type match. + if (bind_data.IsGeneratedColumn(idx)) { return false; } // We run scalar function pushdown after filter pushdown - a filter pushed into the scan needs the column itself @@ -476,6 +479,12 @@ bool ParquetScanFunction::ProjectionExpressionPushdown(ClientContext &context, if (idx >= children.size() || idx >= bind_data.types.size() || idx >= reader.types.size()) { return false; } + if (!bind_data.file_options.bound_schema.empty() && + (idx >= reader.columns.size() || + !bind_data.MatchesPhysicalColumn(idx, reader.columns[idx].name, reader.types[idx]))) { + // The pushed expression addresses a physical column, before name mapping and conversion. + return false; + } const idx_t column_flat_idx = children[idx].column_index; for (const auto &group : metadata_reader.GetFileMetadata()->row_groups) { D_ASSERT(column_flat_idx < group.columns.size()); @@ -532,6 +541,7 @@ TableFunctionMultiFileSettings ParquetScanFunction::GetMultiFileSettings() { settings.glob_input = FileGlobInput(FileGlobOptions::FALLBACK_GLOB, "parquet"); settings.reader_type = "Parquet"; settings.claim_batch = ReadSingleParquetFileClaimBatch; + settings.prepare_batch = ReadSingleParquetFilePrepareBatch; settings.finish_batch = ReadSingleParquetFileFinishBatch; settings.supports_read_ahead = ReadSingleParquetFileSupportsReadAhead; settings.schedule_io = ReadSingleParquetFileScheduleIO; diff --git a/src/duckdb/extension/parquet/reader/list_column_reader.cpp b/src/duckdb/extension/parquet/reader/list_column_reader.cpp index d50479ff7..be3b792ad 100644 --- a/src/duckdb/extension/parquet/reader/list_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/list_column_reader.cpp @@ -137,7 +137,11 @@ idx_t ListColumnReader::ReadInternal(ColumnReaderInput &input, optional_ptr 0); + if (result_offset == 0) { + throw DataCorruptionException( + "Corrupt Parquet file: a value in list column \"%s\" repeats a list before any list started", + Schema().name); + } OP::HandleRepeat(data, result_offset - 1); continue; } diff --git a/src/duckdb/extension/parquet/reader/string_column_reader.cpp b/src/duckdb/extension/parquet/reader/string_column_reader.cpp index ef02f2777..33f3d727b 100644 --- a/src/duckdb/extension/parquet/reader/string_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/string_column_reader.cpp @@ -13,6 +13,7 @@ #include "duckdb/common/vector/string_vector.hpp" #include "parquet_column_schema.hpp" #include "parquet_types.h" +#include "parquet_timestamp.hpp" namespace duckdb { class Vector; @@ -27,6 +28,10 @@ StringColumnReader::StringColumnReader(const ParquetReader &reader, const Parque if (schema.parquet_type == Type::FIXED_LEN_BYTE_ARRAY) { fixed_width_string_length = schema.type_length; } + if (schema.parquet_type == Type::INT96) { + // INT96 is read as a raw 12-byte blob for int96_as='struct' (which converts it to STRUCT(date, time)) + fixed_width_string_length = sizeof(Int96); + } } bool StringColumnReader::IsValid(const char *str_data, uint32_t str_len, const bool is_varchar) { diff --git a/src/duckdb/extension/parquet/reader/struct_column_reader.cpp b/src/duckdb/extension/parquet/reader/struct_column_reader.cpp index 2a91a45a7..6262dd841 100644 --- a/src/duckdb/extension/parquet/reader/struct_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/struct_column_reader.cpp @@ -132,15 +132,13 @@ void StructColumnReader::RegisterPrefetch(ThriftFileTransport &transport, bool a } } -uint64_t StructColumnReader::TotalCompressedSize() { - uint64_t size = 0; +void StructColumnReader::GatherColumnChunks(unordered_set &column_chunks) { for (auto &child : child_readers) { if (!child) { continue; } - size += child->TotalCompressedSize(); + child->GatherColumnChunks(column_chunks); } - return size; } static bool TypeHasExactRowCount(const LogicalType &type) { diff --git a/src/duckdb/extension/parquet/reader/variant_column_reader.cpp b/src/duckdb/extension/parquet/reader/variant_column_reader.cpp index a4bbc700f..5a2ddb98e 100644 --- a/src/duckdb/extension/parquet/reader/variant_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/variant_column_reader.cpp @@ -8,6 +8,7 @@ #include "reader/variant_column_reader.hpp" #include "duckdb/common/types/variant/parquet_variant_iterator.hpp" #include "column_reader.hpp" +#include "parquet_reader.hpp" #include "duckdb/common/assert.hpp" #include "duckdb/common/constants.hpp" #include "duckdb/common/exception.hpp" @@ -73,6 +74,11 @@ VariantColumnReader::VariantColumnReader(ClientContext &context, const ParquetRe } } + if (child_readers.size() < 2 || !child_readers[0] || !child_readers[1]) { + throw DataCorruptionException("Failed to read file \"%s\": the Variant column must have 'metadata' and 'value' " + "as the first two columns", + reader.GetFileName()); + } if (child_readers[0]->Schema().name == "metadata" && child_readers[1]->Schema().name == "value") { metadata_reader_idx = 0; value_reader_idx = 1; @@ -80,7 +86,9 @@ VariantColumnReader::VariantColumnReader(ClientContext &context, const ParquetRe metadata_reader_idx = 1; value_reader_idx = 0; } else { - throw InternalException("The Variant column must have 'metadata' and 'value' as the first two columns"); + throw DataCorruptionException("Failed to read file \"%s\": the Variant column must have 'metadata' and 'value' " + "as the first two columns", + reader.GetFileName()); } } @@ -214,15 +222,13 @@ void VariantColumnReader::RegisterPrefetch(ThriftFileTransport &transport, bool } } -uint64_t VariantColumnReader::TotalCompressedSize() { - uint64_t size = 0; +void VariantColumnReader::GatherColumnChunks(unordered_set &column_chunks) { for (auto &child : child_readers) { if (!child) { continue; } - size += child->TotalCompressedSize(); + child->GatherColumnChunks(column_chunks); } - return size; } idx_t VariantColumnReader::GroupRowsAvailable() { diff --git a/src/duckdb/extension/parquet/serialize_parquet.cpp b/src/duckdb/extension/parquet/serialize_parquet.cpp index 833ce5b87..4dfe51da1 100644 --- a/src/duckdb/extension/parquet/serialize_parquet.cpp +++ b/src/duckdb/extension/parquet/serialize_parquet.cpp @@ -89,6 +89,7 @@ void ParquetOptionsSerialization::Serialize(Serializer &serializer) const { serializer.WritePropertyWithDefault(107, "can_have_nan", parquet_options.can_have_nan, false); serializer.WritePropertyWithDefault(108, "prefetch_strategy", parquet_options.prefetch_strategy, ParquetPrefetchStrategyOption::AUTO); serializer.WritePropertyWithDefault(109, "utf8_validation", parquet_options.utf8_validation_option, StringColumnReader::Utf8ValidationOption::STRICT_UTF8); + serializer.WritePropertyWithDefault(110, "int96_as", parquet_options.int96_as, ParquetInt96AsOption::TIMESTAMP); } ParquetOptionsSerialization ParquetOptionsSerialization::Deserialize(Deserializer &deserializer) { @@ -103,6 +104,7 @@ ParquetOptionsSerialization ParquetOptionsSerialization::Deserialize(Deserialize deserializer.ReadPropertyWithExplicitDefault(107, "can_have_nan", result.parquet_options.can_have_nan, false); deserializer.ReadPropertyWithExplicitDefault(108, "prefetch_strategy", result.parquet_options.prefetch_strategy, ParquetPrefetchStrategyOption::AUTO); deserializer.ReadPropertyWithExplicitDefault(109, "utf8_validation", result.parquet_options.utf8_validation_option, StringColumnReader::Utf8ValidationOption::STRICT_UTF8); + deserializer.ReadPropertyWithExplicitDefault(110, "int96_as", result.parquet_options.int96_as, ParquetInt96AsOption::TIMESTAMP); return result; } diff --git a/src/duckdb/extension/parquet/writer/primitive_column_writer.cpp b/src/duckdb/extension/parquet/writer/primitive_column_writer.cpp index 315dea0fd..7d95a70d2 100644 --- a/src/duckdb/extension/parquet/writer/primitive_column_writer.cpp +++ b/src/duckdb/extension/parquet/writer/primitive_column_writer.cpp @@ -375,8 +375,8 @@ void PrimitiveColumnWriter::SetParquetStatistics(PrimitiveColumnWriterState &sta if (!state.stats_state) { return; } - auto null_count = MaxRepeat() == 0 ? state.null_count : state.null_count + state.parent_null_count; - column_chunk.meta_data.statistics.null_count = NumericCast(null_count); + // a NULL ancestor counts as a NULL of this column, the children count it once as a parent NULL + column_chunk.meta_data.statistics.null_count = NumericCast(state.null_count + state.parent_null_count); column_chunk.meta_data.statistics.__isset.null_count = true; column_chunk.meta_data.__isset.statistics = true; if (column_chunk.meta_data.type == duckdb_parquet::Type::FLOAT || diff --git a/src/duckdb/extension/parquet/writer/struct_column_writer.cpp b/src/duckdb/extension/parquet/writer/struct_column_writer.cpp index 3207cc3eb..9a8210b3d 100644 --- a/src/duckdb/extension/parquet/writer/struct_column_writer.cpp +++ b/src/duckdb/extension/parquet/writer/struct_column_writer.cpp @@ -122,8 +122,6 @@ void StructColumnWriter::Write(ColumnWriterState &state_p, Vector &vector, idx_t void StructColumnWriter::PrepareWrite(ColumnWriterState &state_p) { auto &state = state_p.Cast(); for (idx_t child_idx = 0; child_idx < child_writers.size(); child_idx++) { - // we add the null count of the struct to the null count of the children - state.child_states[child_idx]->null_count += state_p.null_count; child_writers[child_idx]->PrepareWrite(*state.child_states[child_idx]); } } diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index d155c2d67..68959c2a5 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha46057"; + descriptor->api_version = "v2.0.0-alpha47600"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha46057"; + descriptor->api_version = "v2.0.0-alpha47600"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha46057"; + descriptor->api_version = "v2.0.0-alpha47600"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha46057"; + descriptor->api_version = "v2.0.0-alpha47600"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/catalog/catalog_entry/column_dependency_manager.cpp b/src/duckdb/src/catalog/catalog_entry/column_dependency_manager.cpp index c1741e9f7..c7cefbceb 100644 --- a/src/duckdb/src/catalog/catalog_entry/column_dependency_manager.cpp +++ b/src/duckdb/src/catalog/catalog_entry/column_dependency_manager.cpp @@ -65,6 +65,15 @@ void ColumnDependencyManager::AddGeneratedColumn(LogicalIndex index, const vecto } } +ColumnDependencyManager ColumnDependencyManager::Copy() const { + ColumnDependencyManager result; + result.dependencies_map = dependencies_map; + result.dependents_map = dependents_map; + result.direct_dependencies = direct_dependencies; + result.deleted_columns = deleted_columns; + return result; +} + vector ColumnDependencyManager::RemoveColumn(LogicalIndex index, idx_t column_amount) { // Always add the initial column deleted_columns.insert(index); diff --git a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp index a8cd4fb87..fbccd7aea 100644 --- a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp @@ -873,7 +873,10 @@ unique_ptr DuckTableEntry::RemoveColumn(ClientContext &context, Re if (create_info->columns.empty()) { throw CatalogException("Cannot drop column: table only has one column remaining!"); } - auto adjusted_indices = column_dependency_manager.RemoveColumn(removed_index, columns.LogicalColumnCount()); + // operate on a copy: this is the live catalog entry, and mutating it here races with concurrent ALTERs and leaves + // the dependency map corrupted if a later step throws and the ALTER is rolled back + auto column_dependencies = column_dependency_manager.Copy(); + auto adjusted_indices = column_dependencies.RemoveColumn(removed_index, columns.LogicalColumnCount()); auto binder = Binder::CreateBinder(context); auto bound_constraints = binder->BindConstraints(constraints, name, columns); diff --git a/src/duckdb/src/catalog/catalog_entry/table_function_catalog_entry.cpp b/src/duckdb/src/catalog/catalog_entry/table_function_catalog_entry.cpp index 0c2ce5615..bce39a750 100644 --- a/src/duckdb/src/catalog/catalog_entry/table_function_catalog_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/table_function_catalog_entry.cpp @@ -38,6 +38,9 @@ unique_ptr TableFunctionCatalogEntry::AlterEntry(CatalogTransactio } CreateTableFunctionInfo new_info(std::move(new_set)); new_info.internal = internal; + new_info.descriptions = descriptions; + new_info.descriptions.insert(new_info.descriptions.end(), add_overloads.new_descriptions.begin(), + add_overloads.new_descriptions.end()); return make_uniq(catalog, schema, new_info); } diff --git a/src/duckdb/src/common/adbc/adbc.cpp b/src/duckdb/src/common/adbc/adbc.cpp index 715060399..babfa9002 100644 --- a/src/duckdb/src/common/adbc/adbc.cpp +++ b/src/duckdb/src/common/adbc/adbc.cpp @@ -12,7 +12,9 @@ #include "duckdb/main/query_result_stream.hpp" #include "duckdb/common/exception.hpp" +#include "duckdb/common/mutex.hpp" #include "duckdb/main/connection.hpp" +#include "duckdb/main/valid_checker.hpp" #include "duckdb/common/adbc/options.h" #include "duckdb/common/adbc/single_batch_array_stream.hpp" #include "duckdb/function/table/arrow.hpp" @@ -129,6 +131,8 @@ struct DuckDBAdbcStatementWrapper { ArrowArrayStream ingestion_stream; IngestionMode ingestion_mode = IngestionMode::CREATE; bool temporary_table = false; + //! Metadata calls complete their result at submit, so they snapshot the catalog at the call + bool eager_result = false; uint64_t plan_length; }; @@ -140,11 +144,13 @@ struct DuckDBAdbcStreamWrapper { //! Drained into `materialized` and reset when another statement runs on the connection duckdb::unique_ptr> stream; - //! A result that completed at submission, which no later statement on the connection affects + //! A result the driver completed before handing it out, which no later statement on the connection affects duckdb::unique_ptr result; duckdb::ArrowSchemaWrapper schema; - //! What MaterializeStreams drained, handed out before the error it ran into, if any + //! What draining collected, handed out before the error it ran into, if any duckdb::vector> materialized; + //! Serializes a reader with a drain or a close; per stream, so neither blocks another stream's reader + duckdb::mutex fetch_mutex; idx_t materialized_index = 0; char *last_error = nullptr; AdbcStatusCode status_code = ADBC_STATUS_OK; @@ -197,6 +203,31 @@ static duckdb::unique_ptr FetchArray(DuckDBAdbcStream return nullptr; } +//! Drains the stream into `materialized`; an error ends the drain and is recorded on the wrapper +static void MaterializeStream(DuckDBAdbcStreamWrapper &wrapper) { + const duckdb::lock_guard guard(wrapper.fetch_mutex); + if (!wrapper.stream) { + return; + } + try { + while (!wrapper.last_error) { + duckdb::ErrorData error; + auto array = FetchArray(wrapper, error); + if (error.HasError()) { + SetStreamError(wrapper, error); + break; + } + if (!array) { + break; + } + wrapper.materialized.push_back(std::move(array)); + } + } catch (std::exception &ex) { + SetStreamError(wrapper, duckdb::ErrorData(ex)); + } + wrapper.stream.reset(); +} + static void CopySchema(const ArrowSchema &source, duckdb::ArrowSchemaWrapper &target) { if (target.arrow_schema.release) { target.arrow_schema.release(&target.arrow_schema); @@ -206,10 +237,10 @@ static void CopySchema(const ArrowSchema &source, duckdb::ArrowSchemaWrapper &ta } } -//! A result that cannot stream completes here, so that the next statement on the connection leaves it intact. -//! rows_affected stays -1, which ADBC defines as unknown, unless the statement reports a changed-row count +//! eager completes the result here, for a driver-built query that must reflect the catalog at the call. rows_affected +//! stays -1, which ADBC defines as unknown, unless the statement reports a changed-row count static AdbcStatusCode ExecuteArrow(duckdb::PreparedStatementWrapper &prepared, DuckDBAdbcStreamWrapper &out, - int64_t &rows_affected, struct AdbcError *error) { + bool reads_to_the_end, int64_t &rows_affected, struct AdbcError *error, bool eager) { out.stream.reset(); out.result.reset(); rows_affected = -1; @@ -217,8 +248,9 @@ static AdbcStatusCode ExecuteArrow(duckdb::PreparedStatementWrapper &prepared, D try { duckdb::QueryParameters parameters(duckdb::make_shared_ptr(STANDARD_VECTOR_SIZE)); auto result = prepared.statement->Submit(prepared.values, parameters); - if (!result->HasError() && - result->GetStatementProperties().result_eagerness != duckdb::ResultEagerness::FORCED) { + if (!result->HasError() && !eager && + duckdb::StreamsByDefault(prepared.statement->GetStatementType(), result->GetStatementProperties(), + reads_to_the_end)) { out.stream = duckdb::make_uniq>(std::move(result)); CopySchema(out.stream->FormatState().Schema(), out.schema); return ADBC_STATUS_OK; @@ -247,28 +279,20 @@ static AdbcStatusCode ExecuteArrow(duckdb::PreparedStatementWrapper &prepared, D static AdbcStatusCode QueryInternal(struct AdbcConnection *connection, struct ArrowArrayStream *out, const char *query, struct AdbcError *error) { - AdbcStatement statement; + AdbcStatement statement = {}; auto status = StatementNew(connection, &statement, error); if (status != ADBC_STATUS_OK) { - StatementRelease(&statement, error); - SetError(error, "unable to initialize statement"); return status; } + // A metadata call promises a point-in-time snapshot, so its result must complete before the call returns + static_cast(statement.private_data)->eager_result = true; status = StatementSetSqlQuery(&statement, query, error); - if (status != ADBC_STATUS_OK) { - StatementRelease(&statement, error); - SetError(error, "unable to initialize statement"); - return status; - } - status = StatementExecuteQuery(&statement, out, nullptr, error); - if (status != ADBC_STATUS_OK) { - StatementRelease(&statement, error); - SetError(error, "unable to initialize statement"); - return status; + if (status == ADBC_STATUS_OK) { + status = StatementExecuteQuery(&statement, out, nullptr, error); } StatementRelease(&statement, error); - return ADBC_STATUS_OK; + return status; } struct DuckDBAdbcDatabaseWrapper { @@ -578,7 +602,11 @@ AdbcStatusCode ConnectionNew(struct AdbcConnection *connection, struct AdbcError return ADBC_STATUS_OK; } -static AdbcStatusCode ExecuteQuery(duckdb::Connection *conn, const char *query, struct AdbcError *error) { +static AdbcStatusCode ExecuteQuery(duckdb::DuckDBAdbcConnectionWrapper &conn_wrapper, const char *query, + struct AdbcError *error) { + // The connection refuses a statement while one of its streams is open + conn_wrapper.MaterializeStreams(); + auto conn = reinterpret_cast(conn_wrapper.connection); auto res = conn->Query(query); if (res->HasError()) { auto error_message = "Failed to execute query \"" + std::string(query) + "\": " + res->GetError(); @@ -588,14 +616,34 @@ static AdbcStatusCode ExecuteQuery(duckdb::Connection *conn, const char *query, return ADBC_STATUS_OK; } -static AdbcStatusCode InternalSetOption(duckdb::Connection &conn, std::unordered_map &options, - struct AdbcError *error) { +//! The engine turns a COMMIT of a transaction that an earlier error invalidated into a rollback that reports success, +//! so that case is reported here. Either way the transaction has ended when this returns. +static AdbcStatusCode CommitTransaction(duckdb::DuckDBAdbcConnectionWrapper &conn_wrapper, struct AdbcError *error) { + // Reading an open stream into memory can fail, which invalidates the transaction + conn_wrapper.MaterializeStreams(); + auto &context = *reinterpret_cast(conn_wrapper.connection)->context; + std::string invalidated; + if (context.transaction.HasActiveTransaction() && + duckdb::ValidChecker::IsInvalidated(context.ActiveTransaction())) { + invalidated = duckdb::ValidChecker::InvalidatedMessage(context.ActiveTransaction()); + } + auto status = ExecuteQuery(conn_wrapper, "COMMIT", error); + if (status == ADBC_STATUS_OK && !invalidated.empty()) { + SetError(error, "Failed to commit, the transaction was rolled back after an earlier error: " + invalidated); + return ADBC_STATUS_INVALID_STATE; + } + return status; +} + +static AdbcStatusCode InternalSetOption(duckdb::DuckDBAdbcConnectionWrapper &conn_wrapper, struct AdbcError *error) { + auto &conn = *reinterpret_cast(conn_wrapper.connection); + auto &options = conn_wrapper.options; // If we got here, the options have already been validated and are acceptable for (auto &option : options) { if (strcmp(option.first.c_str(), ADBC_CONNECTION_OPTION_AUTOCOMMIT) == 0) { if (strcmp(option.second.c_str(), ADBC_OPTION_VALUE_ENABLED) == 0) { if (conn.HasActiveTransaction()) { - AdbcStatusCode status = ExecuteQuery(&conn, "COMMIT", error); + AdbcStatusCode status = CommitTransaction(conn_wrapper, error); if (status != ADBC_STATUS_OK) { options.clear(); return status; @@ -603,7 +651,7 @@ static AdbcStatusCode InternalSetOption(duckdb::Connection &conn, std::unordered } } else if (strcmp(option.second.c_str(), ADBC_OPTION_VALUE_DISABLED) == 0) { if (!conn.HasActiveTransaction()) { - AdbcStatusCode status = ExecuteQuery(&conn, "START TRANSACTION", error); + AdbcStatusCode status = ExecuteQuery(conn_wrapper, "START TRANSACTION", error); if (status != ADBC_STATUS_OK) { options.clear(); return status; @@ -623,9 +671,8 @@ static AdbcStatusCode ConnectionSetOptionCurrentValue(duckdb::DuckDBAdbcConnecti SetError(error, "Connection is not initialized"); return ADBC_STATUS_INVALID_STATE; } - auto conn = reinterpret_cast(conn_wrapper->connection); std::string query = sql_prefix + duckdb::SQLIdentifier(value); - return ExecuteQuery(conn, query.c_str(), error); + return ExecuteQuery(*conn_wrapper, query.c_str(), error); } AdbcStatusCode ConnectionSetOption(struct AdbcConnection *connection, const char *key, const char *value, @@ -655,8 +702,7 @@ AdbcStatusCode ConnectionSetOption(struct AdbcConnection *connection, const char // If the connection has not yet been initialized, we just return here. return ADBC_STATUS_OK; } - auto conn = reinterpret_cast(conn_wrapper->connection); - return InternalSetOption(*conn, conn_wrapper->options, error); + return InternalSetOption(*conn_wrapper, error); } if (strcmp(key, ADBC_CONNECTION_OPTION_CURRENT_CATALOG) == 0) { return ConnectionSetOptionCurrentValue(conn_wrapper, "USE ", value, error); @@ -861,11 +907,13 @@ AdbcStatusCode ConnectionCommit(struct AdbcConnection *connection, struct AdbcEr return ADBC_STATUS_INVALID_STATE; } - AdbcStatusCode status = ExecuteQuery(conn, "COMMIT", error); - if (status != ADBC_STATUS_OK) { + AdbcStatusCode status = CommitTransaction(*conn_wrapper, error); + if (status != ADBC_STATUS_OK && status != ADBC_STATUS_INVALID_STATE) { return status; } - return ExecuteQuery(conn, "START TRANSACTION", error); + // The transaction ended, committed or rolled back, so manual commit mode starts the next one + auto restarted = ExecuteQuery(*conn_wrapper, "START TRANSACTION", error); + return status == ADBC_STATUS_OK ? restarted : status; } AdbcStatusCode ConnectionRollback(struct AdbcConnection *connection, struct AdbcError *error) { @@ -880,11 +928,13 @@ AdbcStatusCode ConnectionRollback(struct AdbcConnection *connection, struct Adbc return ADBC_STATUS_INVALID_STATE; } - AdbcStatusCode status = ExecuteQuery(conn, "ROLLBACK", error); + // Reading the open streams into memory first would only keep rows of a transaction that is rolled back + conn_wrapper->CloseStreams("the stream was closed because its transaction was rolled back"); + AdbcStatusCode status = ExecuteQuery(*conn_wrapper, "ROLLBACK", error); if (status != ADBC_STATUS_OK) { return status; } - return ExecuteQuery(conn, "START TRANSACTION", error); + return ExecuteQuery(*conn_wrapper, "START TRANSACTION", error); } AdbcStatusCode ConnectionCancel(struct AdbcConnection *connection, struct AdbcError *error) { @@ -1444,8 +1494,7 @@ AdbcStatusCode ConnectionInit(struct AdbcConnection *connection, struct AdbcData return adbc_status; } // We might have options to set - auto conn = reinterpret_cast(conn_wrapper->connection); - return InternalSetOption(*conn, conn_wrapper->options, error); + return InternalSetOption(*conn_wrapper, error); } AdbcStatusCode ConnectionRelease(struct AdbcConnection *connection, struct AdbcError *error) { @@ -1482,6 +1531,8 @@ static int get_next(struct ArrowArrayStream *stream, struct ArrowArray *out) { } out->release = nullptr; auto &result_wrapper = *static_cast(stream->private_data); + // A statement on the same connection drains or closes this stream from its own thread; serialize with it + const duckdb::lock_guard guard(result_wrapper.fetch_mutex); if (result_wrapper.materialized_index < result_wrapper.materialized.size()) { auto array = std::move(result_wrapper.materialized[result_wrapper.materialized_index++]); array->MoveTo(*out); @@ -1522,7 +1573,8 @@ const char *get_last_error(struct ArrowArrayStream *stream) { return nullptr; } auto result_wrapper = reinterpret_cast(stream->private_data); - return result_wrapper ? result_wrapper->last_error : nullptr; + const duckdb::lock_guard guard(result_wrapper->fetch_mutex); + return result_wrapper->last_error; } const AdbcError *ErrorFromArrayStream(struct ArrowArrayStream *stream, AdbcStatusCode *status) { @@ -1534,6 +1586,7 @@ const AdbcError *ErrorFromArrayStream(struct ArrowArrayStream *stream, AdbcStatu return nullptr; } auto result_wrapper = reinterpret_cast(stream->private_data); + const duckdb::lock_guard guard(result_wrapper->fetch_mutex); if (!result_wrapper->last_error) { return nullptr; } @@ -1845,6 +1898,7 @@ AdbcStatusCode StatementNew(struct AdbcConnection *connection, struct AdbcStatem statement_wrapper->target_catalog = nullptr; statement_wrapper->db_schema = nullptr; statement_wrapper->temporary_table = false; + statement_wrapper->eager_result = false; statement_wrapper->ingestion_mode = IngestionMode::CREATE; return ADBC_STATUS_OK; @@ -2050,8 +2104,7 @@ AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct Arr } // Materialize any active streams on this connection before executing a new query. - // Without materialization, executing a new query would silently invalidate any existing streaming results on the - // same connection. + // Without materialization, the connection would refuse the new query while a streaming result is open. if (wrapper->conn_wrapper) { wrapper->conn_wrapper->MaterializeStreams(); wrapper->conn_wrapper->cancel_requested = false; @@ -2156,11 +2209,12 @@ AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct Arr return ADBC_STATUS_INVALID_ARGUMENT; } } - auto status = ExecuteArrow(prepared, *stream_wrapper, affected, error); + // Only the last row's result is handed out, and which row is last is not known without consuming the + // next one, so no run counts on being read to its end + auto status = ExecuteArrow(prepared, *stream_wrapper, false, affected, error, wrapper->eager_result); if (status != ADBC_STATUS_OK) { return status; } - // Recreate wrappers for next iteration arrow_array_wrapper = duckdb::ArrowArrayWrapper(); stream.get_next(&stream, &arrow_array_wrapper.arrow_array); } @@ -2169,7 +2223,7 @@ AdbcStatusCode StatementExecuteQuery(struct AdbcStatement *statement, struct Arr return ADBC_STATUS_INVALID_ARGUMENT; } } else { - auto status = ExecuteArrow(prepared, *stream_wrapper, affected, error); + auto status = ExecuteArrow(prepared, *stream_wrapper, out != nullptr, affected, error, wrapper->eager_result); if (status != ADBC_STATUS_OK) { return status; } @@ -3285,7 +3339,7 @@ AdbcStatusCode ConnectionGetStatistics(struct AdbcConnection *connection, const SetError(error, "Connection is not initialized"); return ADBC_STATUS_INVALID_STATE; } - // Running a query on this connection invalidates any open streaming results. + // The connection refuses a query while a streaming result is open. conn_wrapper->MaterializeStreams(); auto conn = reinterpret_cast(conn_wrapper->connection); @@ -3438,25 +3492,26 @@ void duckdb::DuckDBAdbcConnectionWrapper::UnregisterStream(duckdb_adbc::DuckDBAd void duckdb::DuckDBAdbcConnectionWrapper::MaterializeStreams() { const duckdb::lock_guard guard(stream_mutex); for (auto *result_wrapper : active_streams) { - if (!result_wrapper || !result_wrapper->stream) { + if (!result_wrapper) { continue; } - // An error found while draining is reported once the arrays read before it are handed out - try { - while (!result_wrapper->last_error) { - duckdb::ErrorData error; - auto array = duckdb_adbc::FetchArray(*result_wrapper, error); - if (error.HasError()) { - duckdb_adbc::SetStreamError(*result_wrapper, error); - break; - } - if (!array) { - break; - } - result_wrapper->materialized.push_back(std::move(array)); - } - } catch (std::exception &ex) { - duckdb_adbc::SetStreamError(*result_wrapper, duckdb::ErrorData(ex)); + duckdb_adbc::MaterializeStream(*result_wrapper); + } +} + +void duckdb::DuckDBAdbcConnectionWrapper::CloseStreams(const char *reason) { + const duckdb::lock_guard guard(stream_mutex); + for (auto *result_wrapper : active_streams) { + if (!result_wrapper) { + continue; + } + const duckdb::lock_guard fetch_guard(result_wrapper->fetch_mutex); + if (!result_wrapper->stream) { + continue; + } + // A reader may still hold the pointer get_last_error returned, so an error is never replaced + if (!result_wrapper->last_error) { + duckdb_adbc::SetStreamError(*result_wrapper, duckdb::ErrorData(duckdb::ExceptionType::TRANSACTION, reason)); } result_wrapper->stream.reset(); } @@ -3620,6 +3675,8 @@ static const char *DuckDBErrorTypeToString(duckdb_error_type type) { return "DataCorruption"; case DUCKDB_INVALID_CONFIGURATION: return "InvalidConfiguration"; + case DUCKDB_ERROR_RESOURCE_IN_USE: + return "ResourceInUse"; default: return "Unknown"; } diff --git a/src/duckdb/src/common/compressed_file_system.cpp b/src/duckdb/src/common/compressed_file_system.cpp index 076859546..d45aa3867 100644 --- a/src/duckdb/src/common/compressed_file_system.cpp +++ b/src/duckdb/src/common/compressed_file_system.cpp @@ -5,6 +5,9 @@ namespace duckdb { +//! Compressed data of remote files is read in chunks of this size, so they take few requests +static constexpr idx_t REMOTE_READ_IN_BUF_SIZE = 16777216; + StreamWrapper::~StreamWrapper() { } @@ -66,11 +69,17 @@ void CompressedFile::Initialize(QueryContext context, bool write) { this->context = context; this->write = write; stream_data.in_buf_size = compressed_fs.InBufferSize(); + if (!write && FileSystem::IsRemoteFile(child_handle->path)) { + stream_data.in_buf_size = MaxValue( + stream_data.in_buf_size, MinValue(REMOTE_READ_IN_BUF_SIZE, child_handle->GetFileSize())); + } stream_data.out_buf_size = compressed_fs.OutBufferSize(); - stream_data.in_buff = make_unsafe_uniq_array(stream_data.in_buf_size); + auto &allocator = + context.Valid() ? BufferAllocator::Get(*context.GetClientContext()) : Allocator::DefaultAllocator(); + stream_data.in_buff = allocator.Allocate(stream_data.in_buf_size); stream_data.in_buff_start = stream_data.in_buff.get(); stream_data.in_buff_end = stream_data.in_buff.get(); - stream_data.out_buff = make_unsafe_uniq_array(stream_data.out_buf_size); + stream_data.out_buff = allocator.Allocate(stream_data.out_buf_size); stream_data.out_buff_start = stream_data.out_buff.get(); stream_data.out_buff_end = stream_data.out_buff.get(); @@ -161,8 +170,8 @@ void CompressedFile::Clear() { } void CompressedFile::ResetStreamData() { - stream_data.in_buff.reset(); - stream_data.out_buff.reset(); + stream_data.in_buff.Reset(); + stream_data.out_buff.Reset(); stream_data.out_buff_start = nullptr; stream_data.out_buff_end = nullptr; stream_data.in_buff_start = nullptr; diff --git a/src/duckdb/src/common/csv_writer.cpp b/src/duckdb/src/common/csv_writer.cpp index 7ca1f8804..56bef0a57 100644 --- a/src/duckdb/src/common/csv_writer.cpp +++ b/src/duckdb/src/common/csv_writer.cpp @@ -18,7 +18,7 @@ CSVWriterState::CSVWriterState() } CSVWriterState::CSVWriterState(ClientContext &context, idx_t flush_size_p) - : flush_size(flush_size_p), stream(make_uniq(Allocator::Get(context), flush_size)) { + : flush_size(flush_size_p), stream(make_uniq(BufferAllocator::Get(context), flush_size)) { } CSVWriterState::CSVWriterState(DatabaseInstance &db, idx_t flush_size_p) diff --git a/src/duckdb/src/common/enum_util.cpp b/src/duckdb/src/common/enum_util.cpp index 0934e0e89..588815540 100644 --- a/src/duckdb/src/common/enum_util.cpp +++ b/src/duckdb/src/common/enum_util.cpp @@ -26,6 +26,7 @@ #include "duckdb/common/enums/checkpoint_abort.hpp" #include "duckdb/common/enums/checkpoint_on_detach.hpp" #include "duckdb/common/enums/compression_type.hpp" +#include "duckdb/common/enums/connection_type.hpp" #include "duckdb/common/enums/copy_overwrite_mode.hpp" #include "duckdb/common/enums/cte_materialize.hpp" #include "duckdb/common/enums/date_part_specifier.hpp" @@ -69,7 +70,6 @@ #include "duckdb/common/enums/query_result_state.hpp" #include "duckdb/common/enums/regex_match_operator_semantics.hpp" #include "duckdb/common/enums/relation_type.hpp" -#include "duckdb/common/enums/result_eagerness.hpp" #include "duckdb/common/enums/result_lifetime.hpp" #include "duckdb/common/enums/result_ordering.hpp" #include "duckdb/common/enums/row_group_append_mode.hpp" @@ -208,6 +208,7 @@ #include "duckdb/parser/peg/ast/expression_chain.hpp" #include "duckdb/parser/peg/matcher.hpp" #include "duckdb/parser/peg/matcher/operator_matcher.hpp" +#include "duckdb/parser/peg/parsed_grammar.hpp" #include "duckdb/parser/peg/sql_formatter.hpp" #include "duckdb/parser/peg/transformer/parse_result.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" @@ -1487,6 +1488,24 @@ ConflictManagerMode EnumUtil::FromString(const char *value) return static_cast(StringUtil::StringToEnum(GetConflictManagerModeValues(), 2, "ConflictManagerMode", value)); } +const StringUtil::EnumStringLiteral *GetConnectionTypeValues() { + static constexpr StringUtil::EnumStringLiteral values[] { + { static_cast(ConnectionType::USER), "USER" }, + { static_cast(ConnectionType::INTERNAL), "INTERNAL" } + }; + return values; +} + +template<> +const char* EnumUtil::ToChars(ConnectionType value) { + return StringUtil::EnumToString(GetConnectionTypeValues(), 2, "ConnectionType", static_cast(value)); +} + +template<> +ConnectionType EnumUtil::FromString(const char *value) { + return static_cast(StringUtil::StringToEnum(GetConnectionTypeValues(), 2, "ConnectionType", value)); +} + const StringUtil::EnumStringLiteral *GetConstraintTypeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(ConstraintType::INVALID), "INVALID" }, @@ -2236,6 +2255,25 @@ ExpressionClass EnumUtil::FromString(const char *value) { return static_cast(StringUtil::StringToEnum(GetExpressionClassValues(), 40, "ExpressionClass", value)); } +const StringUtil::EnumStringLiteral *GetExpressionDepthKindValues() { + static constexpr StringUtil::EnumStringLiteral values[] { + { static_cast(ExpressionDepthKind::NONE), "NONE" }, + { static_cast(ExpressionDepthKind::NESTING), "NESTING" }, + { static_cast(ExpressionDepthKind::CHAIN), "CHAIN" } + }; + return values; +} + +template<> +const char* EnumUtil::ToChars(ExpressionDepthKind value) { + return StringUtil::EnumToString(GetExpressionDepthKindValues(), 3, "ExpressionDepthKind", static_cast(value)); +} + +template<> +ExpressionDepthKind EnumUtil::FromString(const char *value) { + return static_cast(StringUtil::StringToEnum(GetExpressionDepthKindValues(), 3, "ExpressionDepthKind", value)); +} + const StringUtil::EnumStringLiteral *GetExpressionTailTypeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(ExpressionTailType::IS_TEST), "IS_TEST" }, @@ -5434,24 +5472,6 @@ RequestType EnumUtil::FromString(const char *value) { return static_cast(StringUtil::StringToEnum(GetRequestTypeValues(), 6, "RequestType", value)); } -const StringUtil::EnumStringLiteral *GetResultEagernessValues() { - static constexpr StringUtil::EnumStringLiteral values[] { - { static_cast(ResultEagerness::FORCED), "FORCED" }, - { static_cast(ResultEagerness::AUTO), "AUTO" } - }; - return values; -} - -template<> -const char* EnumUtil::ToChars(ResultEagerness value) { - return StringUtil::EnumToString(GetResultEagernessValues(), 2, "ResultEagerness", static_cast(value)); -} - -template<> -ResultEagerness EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetResultEagernessValues(), 2, "ResultEagerness", value)); -} - const StringUtil::EnumStringLiteral *GetResultLifetimeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(ResultLifetime::UNDECIDED), "UNDECIDED" }, diff --git a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp index 3dbd80db0..589923fcd 100644 --- a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp @@ -476,6 +476,18 @@ static ColumnMapResult MapColumnStruct(ClientContext &context, const MultiFileCo auto child_mapping = MapColumn(context, child_column, child_index, local_column.children, *nested_mapper); if (child_mapping.column_index) { + auto &child_column_index = *child_mapping.column_index; + if (child_mapping.local_column && child_mapping.local_column->type.id() == LogicalTypeId::STRUCT && + !child_column_index.IsPushdownExtract()) { + // the extracted field is read with the type it has in this file, and then remapped to the global type + // (the fields of a nested struct can differ between files, e.g. with union_by_name) + child_column_index.SetType(child_mapping.local_column->type); + if (child_mapping.column_map.type().id() == LogicalTypeId::TUPLE) { + // the mapping of a nested struct is (local name, mapping) - we extract the field directly + auto map_entry = StructValue::GetChildren(child_mapping.column_map)[1]; + child_mapping.column_map = std::move(map_entry); + } + } vector single_child; single_child.push_back(std::move(*child_mapping.column_index)); child_mapping.column_index = make_uniq(local_id.GetIndex(), single_child); diff --git a/src/duckdb/src/common/multi_file/multi_file_reader.cpp b/src/duckdb/src/common/multi_file/multi_file_reader.cpp index 0799f025f..58641a287 100644 --- a/src/duckdb/src/common/multi_file/multi_file_reader.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_reader.cpp @@ -74,6 +74,37 @@ unique_ptr MultiFileBindData::Copy() const { return std::move(result); } +bool MultiFileBindData::IsGeneratedColumn(idx_t column_idx) const { + if ((reader_bind.filename_idx.IsValid() && reader_bind.filename_idx.GetIndex() == column_idx) || + (reader_bind.file_row_number_idx.IsValid() && reader_bind.file_row_number_idx.GetIndex() == column_idx)) { + return true; + } + for (const auto &partition : reader_bind.hive_partitioning_indexes) { + if (partition.index == column_idx) { + return true; + } + } + return false; +} + +bool MultiFileBindData::MatchesPhysicalColumn(idx_t column_idx, const Identifier &physical_name, + const LogicalType &physical_type) const { + return column_idx < names.size() && column_idx < types.size() && !IsGeneratedColumn(column_idx) && + names[column_idx] == physical_name && types[column_idx].EqualsIncludingCollation(physical_type); +} + +bool MultiFileBindData::HasIdentityMapping(const vector &physical_columns) const { + if (physical_columns.size() != names.size() || physical_columns.size() != types.size()) { + return false; + } + for (idx_t i = 0; i < physical_columns.size(); i++) { + if (!MatchesPhysicalColumn(i, physical_columns[i].name, physical_columns[i].type)) { + return false; + } + } + return true; +} + unique_ptr MultiFileReader::CreateDefault(const string &function_name) { auto res = make_uniq(); res->function_name = Identifier(function_name); @@ -381,6 +412,77 @@ vector MultiFileReader::ParseSchemaOption(ClientConte return ParseSchemaMap(context, schema_value, MapType::KeyType(schema_value.type()), true); } +void MultiFileOptions::SetBoundSchema(const Value &value, ClientContext &context) { + if (value.IsNull() || value.type().id() != LogicalTypeId::STRUCT) { + throw BinderException("'bound_schema' expects a non-NULL STRUCT of column names to type strings"); + } + auto &values = StructValue::GetChildren(value); + if (values.empty()) { + throw BinderException("'bound_schema' cannot be empty"); + } + for (idx_t i = 0; i < values.size(); i++) { + if (values[i].IsNull() || values[i].type().id() != LogicalTypeId::VARCHAR) { + throw BinderException("'bound_schema' types must be non-NULL VARCHAR values"); + } + auto type = TransformStringToLogicalType(StringValue::Get(values[i]), context); + bound_schema.push_back( + MultiFileColumnDefinition::CreateNested(StructType::GetChildName(value.type(), i), type)); + } +} + +void MultiFileOptions::ValidateBoundSchema() const { + if (bound_schema.empty()) { + return; + } + if (!schema.empty()) { + throw BinderException("'bound_schema' cannot be combined with 'schema'"); + } + if (union_by_name || maximum_sample_files != 1) { + throw BinderException("'bound_schema' does not support union_by_name or multi-file schema sampling"); + } + if (auto_detect_hive_partitioning) { + throw BinderException("'bound_schema' requires explicit hive_partitioning=true or false"); + } + if (hive_partitioning && hive_types_autocast) { + throw BinderException("'bound_schema' requires hive_types_autocast=false and complete hive_types"); + } + if (!hive_partitioning && !hive_types_schema.empty()) { + throw BinderException("'bound_schema' cannot combine hive_types with hive_partitioning=false"); + } + case_insensitive_map_t columns; + for (auto &column : bound_schema) { + if (!column.type.IsComplete() || !columns.emplace(column.name.GetIdentifierName(), column.type).second) { + throw BinderException("'bound_schema' requires complete types and distinct column names"); + } + } + auto require_type = [&](const string &name, const LogicalType &type) { + auto entry = columns.find(name); + if (entry == columns.end() || !entry->second.EqualsIncludingCollation(type)) { + throw BinderException("'bound_schema' must include column \"%s\" with type %s", name, type.ToString()); + } + }; + if (filename) { + require_type(filename_column, LogicalType::VARCHAR); + } + if (file_row_number) { + require_type("file_row_number", LogicalType::BIGINT); + if (filename && StringUtil::CIEquals(filename_column, "file_row_number")) { + throw BinderException("'bound_schema' cannot assign filename and file_row_number to the same column"); + } + } + for (auto &partition : hive_types_schema) { + require_type(partition.first, partition.second); + if ((filename && StringUtil::CIEquals(filename_column, partition.first)) || + (file_row_number && StringUtil::CIEquals("file_row_number", partition.first))) { + throw BinderException("'bound_schema' cannot assign generated and Hive roles to column \"%s\"", + partition.first); + } + } + if (allow_empty) { + throw BinderException("'bound_schema' does not support allow_empty"); + } +} + bool MultiFileReader::ParseCopyOption(const Identifier &key, const vector &values, MultiFileOptions &options) { if (key != "file_row_number") { return false; @@ -454,6 +556,9 @@ bool MultiFileReader::ParseOption(const Identifier &key, const Value &val, Multi } // verify that all the children of the struct value are VARCHAR auto &children = StructValue::GetChildren(val); + if (children.empty()) { + throw InvalidInputException("'hive_types' must specify the type of at least one column"); + } for (idx_t i = 0; i < children.size(); i++) { const Value &child = children[i]; if (child.type().id() != LogicalType::VARCHAR) { @@ -466,6 +571,8 @@ bool MultiFileReader::ParseOption(const Identifier &key, const Value &val, Multi options.hive_types_schema[name] = transformed_type; } D_ASSERT(!options.hive_types_schema.empty()); + } else if (key == "bound_schema") { + options.SetBoundSchema(val, context); } else if (key == "schema") { options.schema = ParseSchemaOption(context, val); if (options.schema.empty()) { @@ -529,27 +636,46 @@ bool MultiFileReader::Bind(MultiFileOptions &options, MultiFileList &files, vect void MultiFileReader::BindOptions(MultiFileOptions &options, MultiFileList &files, vector &return_types, vector &names, MultiFileReaderBindData &bind_data) { + const bool has_bound_schema = !options.bound_schema.empty(); + if (has_bound_schema) { + // Roles bind directly to their final declared positions; the physical reader's schema is unchanged. + names.clear(); + return_types.clear(); + for (auto &column : options.bound_schema) { + names.push_back(column.name); + return_types.push_back(column.type); + } + } + auto bind_generated_column = [&](const Identifier &name, const LogicalType &type) -> idx_t { + if (has_bound_schema) { + auto index = StringUtil::CIFind(names, name); + if (index == DConstants::INVALID_INDEX || !return_types[index].EqualsIncludingCollation(type)) { + throw BinderException("'bound_schema' column \"%s\" does not match the resolved output", name); + } + return index; + } + auto index = names.size(); + names.push_back(name); + return_types.push_back(type); + return index; + }; // Add the file_row_number column - it is read from the row number virtual column of the reader if (options.file_row_number) { - if (StringUtil::CIFind(names, "file_row_number") != DConstants::INVALID_INDEX) { + if (!has_bound_schema && StringUtil::CIFind(names, "file_row_number") != DConstants::INVALID_INDEX) { throw BinderException("Option file_row_number adds column \"file_row_number\", but a column with this " "name is also in the file"); } - bind_data.file_row_number_idx = names.size(); - return_types.emplace_back(LogicalType::BIGINT); - names.emplace_back("file_row_number"); + bind_data.file_row_number_idx = bind_generated_column("file_row_number", LogicalType::BIGINT); } // Add generated constant column for filename if (options.filename) { - if (std::find(names.begin(), names.end(), options.filename_column) != names.end()) { + if (!has_bound_schema && std::find(names.begin(), names.end(), options.filename_column) != names.end()) { throw BinderException("Option filename adds column \"%s\", but a column with this name is also in the " "file. Try setting a different name: filename=''", options.filename_column); } - bind_data.filename_idx = names.size(); - return_types.emplace_back(LogicalType::VARCHAR); - names.emplace_back(options.filename_column); + bind_data.filename_idx = bind_generated_column(Identifier(options.filename_column), LogicalType::VARCHAR); } // Add generated constant columns from hive partitioning scheme @@ -579,12 +705,20 @@ void MultiFileReader::BindOptions(MultiFileOptions &options, MultiFileList &file } } + if (has_bound_schema && partitions.size() != options.hive_types_schema.size()) { + throw BinderException("'bound_schema' requires hive_types for every Hive partition column"); + } if (!options.hive_types_schema.empty()) { // verify that all hive_types are existing partitions options.VerifyHiveTypesArePartitions(partitions); } for (auto &part : partitions) { + if (has_bound_schema) { + auto index = bind_generated_column(Identifier(part.first), options.GetHiveLogicalType(part.first)); + bind_data.hive_partitioning_indexes.emplace_back(part.first, index); + continue; + } idx_t hive_partitioning_index; auto lookup = std::find_if(names.begin(), names.end(), [&](const Identifier &col_name) { return col_name == part.first; }); @@ -1483,7 +1617,7 @@ unique_ptr MultiFileOptions::GetColumnStatistics(const Identifie } bool MultiFileOptions::AnySet() const { - return filename || hive_partitioning || union_by_name || !schema.empty(); + return filename || hive_partitioning || union_by_name || !schema.empty() || !bound_schema.empty(); } Value MultiFileOptions::GetHivePartitionValue(const string &value, const string &key, ClientContext &context) const { diff --git a/src/duckdb/src/common/multi_file/table_function_multi_file.cpp b/src/duckdb/src/common/multi_file/table_function_multi_file.cpp index 500215099..4e224291a 100644 --- a/src/duckdb/src/common/multi_file/table_function_multi_file.cpp +++ b/src/duckdb/src/common/multi_file/table_function_multi_file.cpp @@ -277,13 +277,14 @@ void TableFunctionFileReader::SetScanState(GlobalTableFunctionState &gstate) { } void TableFunctionFileReader::InitializeFunctionState(ClientContext &context) { - lock_guard guard(lock); - if (global_state || !function.init_global) { - return; - } - TableFunctionFileInitInput file_input; - auto init_input = GetInitInput(file_input); - global_state = function.init_global(context, init_input); + CallOnce(initialize_once, [&] { + if (!function.init_global) { + return; + } + TableFunctionFileInitInput file_input; + auto init_input = GetInitInput(file_input); + global_state = function.init_global(context, init_input); + }); } void TableFunctionFileReader::PrepareReader(ClientContext &context, GlobalTableFunctionState &gstate) { @@ -337,6 +338,16 @@ bool TableFunctionFileReader::TryInitializeScan(ClientContext &context, GlobalTa return true; } +void TableFunctionFileReader::PrepareScan(ClientContext &context, GlobalTableFunctionState &, + LocalTableFunctionState &lstate_p) { + if (!settings.prepare_batch) { + return; + } + auto &lstate = lstate_p.Cast(); + TableFunctionInput input(bind_data.get(), lstate.local_state.get(), global_state.get()); + settings.prepare_batch(context, input); +} + AsyncResult TableFunctionFileReader::ScheduleIO(ClientContext &context, GlobalTableFunctionState &gstate, LocalTableFunctionState &lstate_p) { if (!settings.schedule_io) { diff --git a/src/duckdb/src/common/operator/cast_operators.cpp b/src/duckdb/src/common/operator/cast_operators.cpp index 1a78e8b0e..c4282dc0c 100644 --- a/src/duckdb/src/common/operator/cast_operators.cpp +++ b/src/duckdb/src/common/operator/cast_operators.cpp @@ -1134,6 +1134,14 @@ static bool TryCastTimestampBase(const SRC &input, DST &result, bool strict = tr ++result.value; } result.value /= 2; + // rounding can go past the largest (or smallest) timestamp - the result must still be a valid timestamp + if (DST::PRECISION < Interval::MICROS_PER_SEC) { + int64_t micros; + if (!TryMultiplyOperator::Operation(result.value, Interval::MICROS_PER_SEC / DST::PRECISION, micros) || + !timestamp_t(micros).IsFinite()) { + return false; + } + } return true; } } diff --git a/src/duckdb/src/common/optional_idx.cpp b/src/duckdb/src/common/optional_idx.cpp index e8efd4120..ea701d7de 100644 --- a/src/duckdb/src/common/optional_idx.cpp +++ b/src/duckdb/src/common/optional_idx.cpp @@ -2,10 +2,6 @@ namespace duckdb { -void optional_idx::ThrowInvalidInitialization() { - throw InternalException("optional_idx cannot be initialized with an invalid index"); -} - void optional_idx::ThrowNotSet() { throw InternalException("Attempting to get the index of an optional_idx that is not set"); } diff --git a/src/duckdb/src/common/serializer/async_memory_governor.cpp b/src/duckdb/src/common/serializer/async_memory_governor.cpp index 601cb4faf..dc3f320d1 100644 --- a/src/duckdb/src/common/serializer/async_memory_governor.cpp +++ b/src/duckdb/src/common/serializer/async_memory_governor.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/helper.hpp" #include "duckdb/main/client_context.hpp" #include "duckdb/parallel/task_scheduler.hpp" +#include "duckdb/storage/buffer_manager.hpp" #include "duckdb/storage/temporary_memory_manager.hpp" namespace duckdb { @@ -12,9 +13,12 @@ ManagedAsyncMemoryGovernor::ManagedAsyncMemoryGovernor(ClientContext &client_con auto &scheduler = TaskScheduler::GetScheduler(client_context); auto regular_threads = MaxValue(NumericCast(scheduler.NumberOfThreads()), 1); auto async_threads = NumericCast(scheduler.NumberOfAsyncThreads()); - max_pending_bytes = ManagedAsyncMemoryConfig::MAX_PENDING_BYTES_PER_THREAD * regular_threads; + auto max_memory = BufferManager::GetBufferManager(client_context).GetMaxMemory(); + max_pending_bytes = MinValue(ManagedAsyncMemoryConfig::MAX_PENDING_BYTES_PER_THREAD * regular_threads, + max_memory / ManagedAsyncMemoryConfig::MAX_PENDING_MEMORY_LIMIT_DIVISOR); min_pending_bytes = - MinValue(max_pending_bytes, ManagedAsyncMemoryConfig::MIN_PENDING_BYTES_PER_THREAD * regular_threads); + MinValue(max_pending_bytes, MinValue(ManagedAsyncMemoryConfig::MIN_PENDING_BYTES_PER_THREAD * regular_threads, + max_memory / ManagedAsyncMemoryConfig::MIN_PENDING_MEMORY_LIMIT_DIVISOR)); // A reservation is only useful when drain tasks run asynchronously; synchronous draining bounds itself. if (async_threads > 0 && max_pending_bytes > 0) { memory_state = TemporaryMemoryManager::Get(client_context).Register(client_context); diff --git a/src/duckdb/src/common/serializer/async_write_queue.cpp b/src/duckdb/src/common/serializer/async_write_queue.cpp index ffad1a291..363b372f4 100644 --- a/src/duckdb/src/common/serializer/async_write_queue.cpp +++ b/src/duckdb/src/common/serializer/async_write_queue.cpp @@ -73,20 +73,16 @@ class AsyncWriteQueueTask : public BaseExecutorTask { AsyncWriteQueueTask(AsyncWriteQueue &queue_p, TaskExecutor &executor) : BaseExecutorTask(executor), queue(queue_p) { } - ~AsyncWriteQueueTask() override { - if (!started) { - queue.CancelScheduledTask(); - } - } - void ExecuteTask() override { - started = true; queue.DrainRequests(); } + void Cancel() override { + queue.CancelScheduledTask(); + } + private: AsyncWriteQueue &queue; - bool started = false; }; AsyncWriteQueue::AsyncWriteQueue(ClientContext &client_context_p, AsyncWriteTarget &target_p) @@ -204,7 +200,9 @@ void AsyncWriteQueue::ScheduleTasksInternal(bool force) { auto selected_bytes = SelectPendingRequestBytes(scheduled_pending_bytes + scheduled_bytes); auto task_budget = TaskByteBudget(); auto has_active_task = active_tasks + schedule_count > 0; - if (!force && has_active_task && selected_bytes < task_budget) { + auto unscheduled_bytes = pending_bytes - scheduled_pending_bytes - scheduled_bytes; + // A small leading request must not block a larger request behind it. + if (!force && has_active_task && unscheduled_bytes < task_budget) { break; } schedule_count++; @@ -1322,10 +1320,8 @@ bool ManagedAsyncWriteStreamQueue::TakePendingWriteRequest(AsyncWriteRequest &re } idx_t selected_bytes = 0; + // The queued-byte threshold was checked above; a small prefix may precede a large zero-copy payload. auto end = SelectPhysicalWriteEnd(0, selected_bytes); - if (policy == SchedulePolicy::THRESHOLD && selected_bytes < first_task_schedule_threshold) { - return false; - } auto write_offset = pending_writes.front().offset; unique_ptr payload; diff --git a/src/duckdb/src/common/sort/sort.cpp b/src/duckdb/src/common/sort/sort.cpp index 786f0b697..234e0d94f 100644 --- a/src/duckdb/src/common/sort/sort.cpp +++ b/src/duckdb/src/common/sort/sort.cpp @@ -5,6 +5,7 @@ #include "duckdb/common/sorting/sort_key.hpp" #include "duckdb/common/sorting/sorted_run.hpp" #include "duckdb/common/sorting/sorted_run_merger.hpp" +#include "duckdb/common/type_visitor.hpp" #include "duckdb/common/types/batched_data_collection.hpp" #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/function/create_sort_key.hpp" @@ -73,14 +74,20 @@ Sort::Sort(ClientContext &client_context_p, const vector &orde vector payload_types; for (idx_t output_col_idx = 0; output_col_idx < projection_map.size(); output_col_idx++) { const auto &input_col_idx = projection_map[output_col_idx]; + const auto &input_col_type = input_types[input_col_idx]; const auto it = input_column_to_key.find(input_col_idx); - if (it != input_column_to_key.end()) { + //! The sort key encoding for FLOAT/DOUBLE is not injective around zero (-0.0 and 0.0 encode + //! identically, since they must compare equal), so re-decoding the key would turn -0.0 into 0.0. + //! Route such columns through the payload instead, where the original bits are preserved. + const bool key_encoding_is_lossy = TypeVisitor::Contains(input_col_type, LogicalTypeId::FLOAT) || + TypeVisitor::Contains(input_col_type, LogicalTypeId::DOUBLE); + if (it != input_column_to_key.end() && !key_encoding_is_lossy) { // Projected column also appears as a key, just reference it output_projection_columns.push_back({false, it->second, output_col_idx}); } else { // Projected column does not appear as a key, add to payload layout output_projection_columns.push_back({true, payload_types.size(), output_col_idx}); - payload_types.push_back(input_types[input_col_idx]); + payload_types.push_back(input_col_type); input_projection_map.push_back(input_col_idx); } } diff --git a/src/duckdb/src/common/string_util.cpp b/src/duckdb/src/common/string_util.cpp index e560b5e65..1b1ab0fef 100644 --- a/src/duckdb/src/common/string_util.cpp +++ b/src/duckdb/src/common/string_util.cpp @@ -526,18 +526,17 @@ bool StringUtil::CIEndsWith(const string &str, const string &suffix) { bool StringUtil::CILessThan(const string &s1, const string &s2) { const auto charmap = ASCII_TO_UPPER_MAP; - unsigned char u1 {}, u2 {}; - - idx_t length = MinValue(s1.length(), s2.length()); - length += s1.length() != s2.length(); + // compare the common prefix byte-by-byte (treating an embedded NUL as a regular byte) + const idx_t length = MinValue(s1.length(), s2.length()); for (idx_t i = 0; i < length; i++) { - u1 = (unsigned char)s1[i]; - u2 = (unsigned char)s2[i]; - if (charmap[u1] != charmap[u2]) { - break; + const auto u1 = charmap[(unsigned char)s1[i]]; + const auto u2 = charmap[(unsigned char)s2[i]]; + if (u1 != u2) { + return u1 < u2; } } - return (charmap[u1] - charmap[u2]) < 0; + // the prefixes are equal - the shorter string sorts first + return s1.length() < s2.length(); } idx_t StringUtil::CIFind(const vector &vector, const Identifier &search_string) { diff --git a/src/duckdb/src/common/types.cpp b/src/duckdb/src/common/types.cpp index 63726d352..4aab4a9a4 100644 --- a/src/duckdb/src/common/types.cpp +++ b/src/duckdb/src/common/types.cpp @@ -530,150 +530,177 @@ static string TypeModifierListToString(const vector &mod_li } string LogicalType::ToString() const { - if (id() != LogicalTypeId::UNBOUND) { - auto alias = GetAlias(); - if (!alias.empty()) { - if (HasExtensionInfo()) { - auto &ext_info = *GetExtensionInfo(); - alias += TypeModifierListToString(ext_info.modifiers); - } - return alias; - } - } - switch (id()) { - case LogicalTypeId::TUPLE: { - if (!HasParameters()) { - return "TUPLE"; - } - auto &child_types = StructType::GetChildTypes(*this); - if (child_types.empty()) { - return "TUPLE"; + // nested types are rendered with an explicit stack to avoid unbounded recursion + struct StackEntry { + optional_ptr type; + string text; + }; + string result; + vector stack; + auto push_text = [&](string text) { + stack.push_back(StackEntry {nullptr, std::move(text)}); + }; + auto push_type = [&](const LogicalType &type) { + stack.push_back(StackEntry {&type, string()}); + }; + push_type(*this); + + while (!stack.empty()) { + auto entry = std::move(stack.back()); + stack.pop_back(); + if (!entry.type) { + result += entry.text; + continue; } - string ret = "TUPLE("; - for (size_t i = 0; i < child_types.size(); i++) { - ret += child_types[i].second.ToString(); - if (i < child_types.size() - 1) { - ret += ", "; + auto &type = *entry.type; + if (type.id() != LogicalTypeId::UNBOUND) { + auto alias = type.GetAlias(); + if (!alias.empty()) { + if (type.HasExtensionInfo()) { + auto &ext_info = *type.GetExtensionInfo(); + alias += TypeModifierListToString(ext_info.modifiers); + } + result += alias; + continue; } } - ret += ")"; - return ret; - } - case LogicalTypeId::STRUCT: { - if (!HasParameters()) { - return "STRUCT"; - } - auto &child_types = StructType::GetChildTypes(*this); - if (child_types.empty()) { - return "STRUCT"; - } - - string ret = "STRUCT("; - for (size_t i = 0; i < child_types.size(); i++) { - ret += StringUtil::Format("%s %s", SQLIdentifier(child_types[i].first), child_types[i].second); - if (i < child_types.size() - 1) { - ret += ", "; + switch (type.id()) { + case LogicalTypeId::TUPLE: + case LogicalTypeId::STRUCT: { + string name = type.id() == LogicalTypeId::TUPLE ? "TUPLE" : "STRUCT"; + if (!type.HasParameters() || StructType::GetChildTypes(type).empty()) { + result += name; + break; } + auto &child_types = StructType::GetChildTypes(type); + auto print_names = type.id() == LogicalTypeId::STRUCT; + push_text(")"); + for (idx_t i = child_types.size(); i > 0; i--) { + auto &child = child_types[i - 1]; + push_type(child.second); + if (print_names) { + push_text(SQLIdentifier(child.first) + " "); + } + if (i > 1) { + push_text(", "); + } + } + push_text(name + "("); + break; } - ret += ")"; - return ret; - } - case LogicalTypeId::LIST: { - if (!HasParameters()) { - return "LIST"; - } - return ListType::GetChildType(*this).ToString() + "[]"; - } - case LogicalTypeId::MAP: { - if (!HasParameters()) { - return "MAP"; - } - auto &key_type = MapType::KeyType(*this); - auto &value_type = MapType::ValueType(*this); - return "MAP(" + key_type.ToString() + ", " + value_type.ToString() + ")"; - } - case LogicalTypeId::UNION: { - if (!HasParameters()) { - return "UNION"; + case LogicalTypeId::LIST: { + if (!type.HasParameters()) { + result += "LIST"; + break; + } + push_text("[]"); + push_type(ListType::GetChildType(type)); + break; } - string ret = "UNION("; - size_t count = UnionType::GetMemberCount(*this); - for (size_t i = 0; i < count; i++) { - auto member_name = UnionType::GetMemberName(*this, i); - auto member_type = UnionType::GetMemberType(*this, i).ToString(); - ret += StringUtil::Format("%s %s", SQLIdentifier(member_name), member_type); - if (i < count - 1) { - ret += ", "; + case LogicalTypeId::ARRAY: { + if (!type.HasParameters()) { + result += "ARRAY"; + break; } + auto size = ArrayType::GetSize(type); + push_text(size == 0 ? "[ANY]" : "[" + to_string(size) + "]"); + push_type(ArrayType::GetChildType(type)); + break; } - ret += ")"; - return ret; - } - case LogicalTypeId::ARRAY: { - if (!HasParameters()) { - return "ARRAY"; + case LogicalTypeId::MAP: { + if (!type.HasParameters()) { + result += "MAP"; + break; + } + push_text(")"); + push_type(MapType::ValueType(type)); + push_text(", "); + push_type(MapType::KeyType(type)); + push_text("MAP("); + break; } - auto size = ArrayType::GetSize(*this); - if (size == 0) { - return ArrayType::GetChildType(*this).ToString() + "[ANY]"; - } else { - return ArrayType::GetChildType(*this).ToString() + "[" + to_string(size) + "]"; + case LogicalTypeId::UNION: { + if (!type.HasParameters()) { + result += "UNION"; + break; + } + auto count = UnionType::GetMemberCount(type); + push_text(")"); + for (idx_t i = count; i > 0; i--) { + push_type(UnionType::GetMemberType(type, i - 1)); + push_text(SQLIdentifier(UnionType::GetMemberName(type, i - 1)) + " "); + if (i > 1) { + push_text(", "); + } + } + push_text("UNION("); + break; } - } - case LogicalTypeId::DECIMAL: { - if (!HasParameters()) { - return "DECIMAL"; + case LogicalTypeId::DECIMAL: { + if (!type.HasParameters()) { + result += "DECIMAL"; + break; + } + auto width = DecimalType::GetWidth(type); + auto scale = DecimalType::GetScale(type); + if (width == 0) { + result += "DECIMAL"; + break; + } + result += StringUtil::Format("DECIMAL(%d,%d)", width, scale); + break; } - auto width = DecimalType::GetWidth(*this); - auto scale = DecimalType::GetScale(*this); - if (width == 0) { - return "DECIMAL"; + case LogicalTypeId::ENUM: { + result += "ENUM("; + for (idx_t i = 0; i < EnumType::GetSize(type); i++) { + if (i > 0) { + result += ", "; + } + result += SQLString(EnumType::GetString(type, i).GetString()); + } + result += ")"; + break; } - return StringUtil::Format("DECIMAL(%d,%d)", width, scale); - } - case LogicalTypeId::ENUM: { - string ret = "ENUM("; - for (idx_t i = 0; i < EnumType::GetSize(*this); i++) { - if (i > 0) { - ret += ", "; + case LogicalTypeId::UNBOUND: { + if (!type.HasParameters()) { + result += "UNBOUND"; + break; + } + auto &expr = UnboundType::GetTypeExpression(type); + if (expr->GetExpressionType() != ExpressionType::TYPE) { + result += "(" + expr->ToString() + ")"; + } else { + result += expr->ToString(); } - ret += SQLString(EnumType::GetString(*this, i).GetString()); + break; } - ret += ")"; - return ret; - } - case LogicalTypeId::UNBOUND: { - if (!HasParameters()) { - return "UNBOUND"; + case LogicalTypeId::SQLNULL: { + result += "\"NULL\""; + break; } - - auto &expr = UnboundType::GetTypeExpression(*this); - if (expr->GetExpressionType() != ExpressionType::TYPE) { - return "(" + expr->ToString() + ")"; - } else { - return expr->ToString(); + case LogicalTypeId::TEMPLATE: { + if (!type.HasParameters()) { + result += "T"; + break; + } + result += TemplateType::GetName(type); + break; } - } - case LogicalTypeId::SQLNULL: { - return "\"NULL\""; - } - case LogicalTypeId::TEMPLATE: { - if (!HasParameters()) { - return "T"; + case LogicalTypeId::GEOMETRY: { + if (!GeoType::HasCRS(type)) { + result += "GEOMETRY"; + break; + } + auto &crs = GeoType::GetCRS(type); + result += StringUtil::Format("GEOMETRY(%s)", SQLString(crs.GetDefinition())); + break; } - return TemplateType::GetName(*this); - } - case LogicalTypeId::GEOMETRY: { - if (!GeoType::HasCRS(*this)) { - return "GEOMETRY"; + default: + result += EnumUtil::ToString(type.id()); + break; } - auto &crs = GeoType::GetCRS(*this); - auto crs_text = SQLString(crs.GetDefinition()); - return StringUtil::Format("GEOMETRY(%s)", crs_text); - } - default: - return EnumUtil::ToString(id()); } + return result; } LogicalTypeId TransformStringToLogicalTypeId(const string &str) { @@ -1899,13 +1926,13 @@ LogicalType ArrayType::ConvertToList(const LogicalType &type) { LogicalType LogicalType::ARRAY(const LogicalType &child, optional_idx size) { if (!size.IsValid()) { // Create an incomplete ARRAY type, used for binding - auto info = make_uniq(child, 0); + auto info = make_uniq(child, uint32_t(0)); return LogicalType(LogicalTypeId::ARRAY, std::move(info)); } else { auto array_size = size.GetIndex(); D_ASSERT(array_size > 0); D_ASSERT(array_size <= ArrayType::MAX_ARRAY_SIZE); - auto info = make_uniq(child, array_size); + auto info = make_uniq(child, NumericCast(array_size)); return LogicalType(LogicalTypeId::ARRAY, std::move(info)); } } diff --git a/src/duckdb/src/common/types/bignum.cpp b/src/duckdb/src/common/types/bignum.cpp index 3bb960851..9f4da3623 100644 --- a/src/duckdb/src/common/types/bignum.cpp +++ b/src/duckdb/src/common/types/bignum.cpp @@ -242,49 +242,48 @@ string Bignum::EncodeBignum(const string_t &value, idx_t start_pos, idx_t end_po auto int_value_char = value.GetData(); idx_t actual_size = end_pos - start_pos; + // convert the decimal digits to base 2**32 digits (least significant first), DECIMAL_SHIFT digits at a time + vector digits; + digits.reserve(actual_size / DECIMAL_SHIFT + 1); + idx_t chunk_size = actual_size % DECIMAL_SHIFT; + if (chunk_size == 0) { + chunk_size = DECIMAL_SHIFT; + } + digit_t chunk_base = 1; + for (idx_t i = 0; i < chunk_size; i++) { + chunk_base *= 10; + } + for (idx_t pos = start_pos; pos < end_pos; pos += chunk_size, chunk_size = DECIMAL_SHIFT) { + digit_t carry = 0; + for (idx_t i = pos; i < pos + chunk_size; i++) { + carry = carry * 10 + static_cast(int_value_char[i] - '0'); + } + // digits = digits * 10**chunk_size + chunk + for (auto &digit : digits) { + twodigit_t tmp = static_cast(digit) * chunk_base + carry; + digit = static_cast(tmp); + carry = static_cast(tmp >> DIGIT_BITS); + } + if (carry) { + digits.push_back(carry); + } + chunk_base = DECIMAL_BASE; + } + // we initialize result with space for our header string result(BIGNUM_HEADER_SIZE, '0'); - unsafe_vector digits; - - // The max number a uint64_t can represent is 18.446.744.073.709.551.615 - // That has 20 digits - // In the worst case a remainder of a division will be 255, which is 3 digits - // Since the max value is 184, we need to take one more digit out - // Hence we end up with a max of 16 digits supported. - constexpr uint8_t max_digits = 16; - const idx_t number_of_digits = static_cast(std::ceil(static_cast(actual_size) / max_digits)); - - // lets convert the string to a uint64_t vector - idx_t cur_end = end_pos; - for (idx_t i = 0; i < number_of_digits; i++) { - idx_t cur_start = static_cast(start_pos) > static_cast(cur_end - max_digits) - ? start_pos - : cur_end - max_digits; - std::string current_number(int_value_char + cur_start, cur_end - cur_start); - digits.push_back(std::stoull(current_number)); - // move cur_end to more digits down the road - cur_end = cur_end - max_digits; - } - - // Now that we have our uint64_t vector, lets start our division process to figure out the new number and remainder - while (!digits.empty()) { - idx_t digit_idx = digits.size() - 1; - uint8_t remainder = 0; - idx_t digits_size = digits.size(); - for (idx_t i = 0; i < digits_size; i++) { - digits[digit_idx] += static_cast(remainder * pow(10, max_digits)); - remainder = static_cast(digits[digit_idx] % 256); - digits[digit_idx] /= 256; - if (digits[digit_idx] == 0 && digit_idx == digits.size() - 1) { - // we can cap this - digits.pop_back(); + result.reserve(BIGNUM_HEADER_SIZE + digits.size() * DIGIT_BYTES); + // write the bytes least significant first, these are reversed afterwards + for (idx_t digit_idx = 0; digit_idx < digits.size(); digit_idx++) { + auto digit = digits[digit_idx]; + for (idx_t byte_idx = 0; byte_idx < DIGIT_BYTES; byte_idx++) { + if (digit_idx + 1 == digits.size() && digit == 0) { + // skip leading zero bytes of the most significant digit + break; } - digit_idx--; - } - if (is_negative) { - result.push_back(static_cast(~remainder)); - } else { - result.push_back(static_cast(remainder)); + auto byte = static_cast(digit & 0xFF); + result.push_back(static_cast(is_negative ? ~byte : byte)); + digit >>= 8; } } std::reverse(result.begin() + BIGNUM_HEADER_SIZE, result.end()); @@ -329,35 +328,177 @@ string Bignum::FromByteArray(uint8_t *data, idx_t size, bool is_negative) { return result; } -// typos:ignore-next-line -// Following CPython and Knuth (TAOCP, Volume 2 (3rd edn), section 4.4, Method 1b). +//! Below this number of limbs, numbers are multiplied with the schoolbook algorithm +static constexpr idx_t KARATSUBA_THRESHOLD = 48; +//! Below this number of binary limbs, numbers are converted to decimal with the schoolbook algorithm +static constexpr idx_t CONVERSION_THRESHOLD = 64; + +static void TrimLimbs(vector &limbs) { + while (!limbs.empty() && limbs.back() == 0) { + limbs.pop_back(); + } +} + +// Adds the decimal limbs of b, shifted by "shift" limbs, to a +static void AddDecimalLimbs(vector &a, const digit_t *b, idx_t b_size, idx_t shift) { + while (b_size > 0 && b[b_size - 1] == 0) { + b_size--; + } + if (a.size() < shift + b_size) { + a.resize(shift + b_size, 0); + } + digit_t carry = 0; + idx_t i = 0; + for (; i < b_size; i++) { + digit_t sum = a[shift + i] + b[i] + carry; + carry = sum >= Bignum::DECIMAL_BASE; + a[shift + i] = carry ? sum - Bignum::DECIMAL_BASE : sum; + } + for (idx_t idx = shift + i; carry; idx++) { + if (idx == a.size()) { + a.push_back(0); + } + digit_t sum = a[idx] + carry; + carry = sum >= Bignum::DECIMAL_BASE; + a[idx] = carry ? sum - Bignum::DECIMAL_BASE : sum; + } +} + +// Subtracts the decimal limbs of b from a, where a >= b +static void SubtractDecimalLimbs(vector &a, const vector &b) { + digit_t borrow = 0; + idx_t i = 0; + for (; i < b.size(); i++) { + if (i >= a.size()) { + D_ASSERT(b[i] == 0); + continue; + } + digit_t subtrahend = b[i] + borrow; + borrow = a[i] < subtrahend; + a[i] = borrow ? a[i] + Bignum::DECIMAL_BASE - subtrahend : a[i] - subtrahend; + } + for (; borrow; i++) { + D_ASSERT(i < a.size()); + borrow = a[i] == 0; + a[i] = borrow ? Bignum::DECIMAL_BASE - 1 : a[i] - 1; + } +} + +// Multiplies two numbers in decimal limbs using Karatsuba multiplication +static vector MultiplyDecimalLimbs(const digit_t *a, idx_t a_size, const digit_t *b, idx_t b_size) { + if (a_size < b_size) { + std::swap(a, b); + std::swap(a_size, b_size); + } + vector result; + if (b_size == 0) { + return result; + } + result.resize(a_size + b_size, 0); + if (b_size < KARATSUBA_THRESHOLD) { + for (idx_t i = 0; i < b_size; i++) { + twodigit_t carry = 0; + for (idx_t j = 0; j < a_size; j++) { + twodigit_t current = UnsafeNumericCast(b[i]) * a[j] + result[i + j] + carry; + carry = current / Bignum::DECIMAL_BASE; + result[i + j] = static_cast(current - carry * Bignum::DECIMAL_BASE); + } + result[i + a_size] = static_cast(carry); + } + TrimLimbs(result); + return result; + } + if (2 * b_size <= a_size) { + // unbalanced: multiply b with chunks of a + for (idx_t offset = 0; offset < a_size; offset += b_size) { + auto part = MultiplyDecimalLimbs(a + offset, MinValue(b_size, a_size - offset), b, b_size); + AddDecimalLimbs(result, part.data(), part.size(), offset); + } + TrimLimbs(result); + return result; + } + // a = a1 * BASE^m + a0, b = b1 * BASE^m + b0 + idx_t m = a_size / 2; + auto z0 = MultiplyDecimalLimbs(a, m, b, m); + auto z2 = MultiplyDecimalLimbs(a + m, a_size - m, b + m, b_size - m); + vector a_sum(a, a + m); + AddDecimalLimbs(a_sum, a + m, a_size - m, 0); + vector b_sum(b, b + m); + AddDecimalLimbs(b_sum, b + m, b_size - m, 0); + // z1 = (a0 + a1) * (b0 + b1) - z0 - z2 + auto z1 = MultiplyDecimalLimbs(a_sum.data(), a_sum.size(), b_sum.data(), b_sum.size()); + SubtractDecimalLimbs(z1, z0); + SubtractDecimalLimbs(z1, z2); + AddDecimalLimbs(result, z0.data(), z0.size(), 0); + AddDecimalLimbs(result, z1.data(), z1.size(), m); + AddDecimalLimbs(result, z2.data(), z2.size(), 2 * m); + TrimLimbs(result); + return result; +} + +// Converts binary limbs (base 2^32) to decimal limbs (base 10^9), both little-endian +// powers[k] holds 2^(32 * 2^k) in decimal limbs +static vector BinaryToDecimalLimbs(const digit_t *binary, idx_t size, vector> &powers) { + vector digits; + if (size <= CONVERSION_THRESHOLD) { + // typos:ignore-next-line + // Following CPython and Knuth (TAOCP, Volume 2 (3rd edn), section 4.4, Method 1b). + for (idx_t i = size; i > 0; i--) { + digit_t hi = binary[i - 1]; + for (idx_t j = 0; j < digits.size(); j++) { + twodigit_t tmp = UnsafeNumericCast(digits[j]) << Bignum::DIGIT_BITS | hi; + hi = static_cast(tmp / UnsafeNumericCast(Bignum::DECIMAL_BASE)); + digits[j] = static_cast(tmp - UnsafeNumericCast(Bignum::DECIMAL_BASE * hi)); + } + while (hi) { + digits.push_back(hi % Bignum::DECIMAL_BASE); + hi /= Bignum::DECIMAL_BASE; + } + } + return digits; + } + // split the number into high * 2^(32 * m) + low, where m is a power of two + idx_t k = 0; + idx_t m = 1; + while (2 * m < size) { + m *= 2; + k++; + } + auto low = BinaryToDecimalLimbs(binary, m, powers); + auto high = BinaryToDecimalLimbs(binary + m, size - m, powers); + while (powers.size() <= k) { + auto &last = powers.back(); + powers.push_back(MultiplyDecimalLimbs(last.data(), last.size(), last.data(), last.size())); + } + auto &power = powers[k]; + digits = MultiplyDecimalLimbs(high.data(), high.size(), power.data(), power.size()); + AddDecimalLimbs(digits, low.data(), low.size(), 0); + TrimLimbs(digits); + return digits; +} + string Bignum::BignumToVarchar(const bignum_t &blob) { string decimal_string; vector byte_array; bool is_negative; GetByteArray(byte_array, is_negative, blob.data); - vector digits; // Rounding byte_array to digit_bytes multiple size, so that we can process every digit_bytes bytes // at a time without if check in the for loop idx_t padding_size = (-byte_array.size()) & (DIGIT_BYTES - 1); byte_array.insert(byte_array.begin(), padding_size, 0); - for (idx_t i = 0; i < byte_array.size(); i += DIGIT_BYTES) { - digit_t hi = 0; + idx_t limb_count = byte_array.size() / DIGIT_BYTES; + vector binary(limb_count); + for (idx_t i = 0; i < limb_count; i++) { + digit_t limb = 0; for (idx_t j = 0; j < DIGIT_BYTES; j++) { - hi |= UnsafeNumericCast(byte_array[i + j]) << (8 * (DIGIT_BYTES - j - 1)); - } - - for (idx_t j = 0; j < digits.size(); j++) { - twodigit_t tmp = UnsafeNumericCast(digits[j]) << DIGIT_BITS | hi; - hi = static_cast(tmp / UnsafeNumericCast(DECIMAL_BASE)); - digits[j] = static_cast(tmp - UnsafeNumericCast(DECIMAL_BASE * hi)); - } - - while (hi) { - digits.push_back(hi % DECIMAL_BASE); - hi /= DECIMAL_BASE; + limb |= UnsafeNumericCast(byte_array[i * DIGIT_BYTES + j]) << (8 * (DIGIT_BYTES - j - 1)); } + binary[limb_count - i - 1] = limb; } + TrimLimbs(binary); + // 2^32 in decimal limbs + vector> powers {{294967296, 4}}; + auto digits = BinaryToDecimalLimbs(binary.data(), binary.size(), powers); if (digits.empty()) { digits.push_back(0); diff --git a/src/duckdb/src/common/types/column/column_data_allocator.cpp b/src/duckdb/src/common/types/column/column_data_allocator.cpp index 3560e9240..377a66c3f 100644 --- a/src/duckdb/src/common/types/column/column_data_allocator.cpp +++ b/src/duckdb/src/common/types/column/column_data_allocator.cpp @@ -80,14 +80,15 @@ ColumnDataAllocator::~ColumnDataAllocator() { BufferHandle ColumnDataAllocator::Pin(uint32_t block_id) { D_ASSERT(type == ColumnDataAllocatorType::BUFFER_MANAGER_ALLOCATOR || type == ColumnDataAllocatorType::HYBRID); + if (!shared) { + return blocks[block_id].Pin(*alloc.buffer_manager); + } shared_ptr handle; - if (shared) { + { // we only need to grab the lock when accessing the vector, because vector access is not thread-safe: // the vector can be resized by another thread while we try to access it lock_guard guard(lock); handle = blocks[block_id].GetHandle(); - } else { - handle = blocks[block_id].GetHandle(); } return alloc.buffer_manager->Pin(handle); } @@ -152,8 +153,7 @@ void ColumnDataAllocator::AllocateBuffer(idx_t size, uint32_t &block_id, uint32_ block_id = NumericCast(blocks.size() - 1); if (chunk_state && chunk_state->handles.find(block_id) == chunk_state->handles.end()) { // not guaranteed to be pinned already by this thread (if shared allocator) - auto handle = blocks[block_id].GetHandle(); - chunk_state->handles[block_id] = alloc.buffer_manager->Pin(handle); + chunk_state->handles[block_id] = block.Pin(*alloc.buffer_manager); } offset = block.size; block.size += size; @@ -255,10 +255,13 @@ void ColumnDataAllocator::UnswizzlePointers(ChunkManagementState &state, Vector SwizzleMetaData &swizzle_segment, const VectorMetaData &string_heap_segment, const idx_t &v_offset, const bool &copied) { D_ASSERT(result.GetType().InternalType() == PhysicalType::VARCHAR); - lock_guard guard(lock); - const auto old_base_ptr = char_ptr_cast(swizzle_segment.ptr); const auto new_base_ptr = char_ptr_cast(GetDataPointer(state, string_heap_segment.block_id, string_heap_segment.offset)); + if (!copied && swizzle_segment.ptr.load(std::memory_order_acquire) == data_ptr_cast(new_base_ptr)) { + return; + } + lock_guard guard(lock); + const auto old_base_ptr = char_ptr_cast(swizzle_segment.ptr.load(std::memory_order_relaxed)); if (old_base_ptr == new_base_ptr) { return; // pointers are still valid } @@ -286,7 +289,7 @@ void ColumnDataAllocator::UnswizzlePointers(ChunkManagementState &state, Vector if (!copied) { // if the data was not copied, we modified data on the blocks. store the new base ptr - swizzle_segment.ptr = data_ptr_cast(new_base_ptr); + swizzle_segment.ptr.store(data_ptr_cast(new_base_ptr), std::memory_order_release); } } @@ -360,6 +363,14 @@ shared_ptr BlockMetaData::GetHandle() const { return res; } +BufferHandle BlockMetaData::Pin(BufferManager &buffer_manager) { + if (handle) { + return buffer_manager.Pin(handle); + } + auto owned_handle = GetHandle(); + return buffer_manager.Pin(owned_handle); +} + void BlockMetaData::SetHandle(ManagedResultSet &managed_result_set, shared_ptr handle_p) { if (managed_result_set.IsValid()) { managed_result_set.GetHandles().emplace_back(handle_p); diff --git a/src/duckdb/src/common/types/column/column_data_consumer.cpp b/src/duckdb/src/common/types/column/column_data_consumer.cpp index fa20f1d5b..7cdab1ecb 100644 --- a/src/duckdb/src/common/types/column/column_data_consumer.cpp +++ b/src/duckdb/src/common/types/column/column_data_consumer.cpp @@ -38,7 +38,6 @@ bool ColumnDataConsumer::AssignChunk(ColumnDataConsumerScanState &state) { lock_guard guard(lock); if (current_chunk_index == chunk_count) { // All chunks have been assigned - state.current_chunk_state.handles.clear(); state.chunk_index = DConstants::INVALID_INDEX; return false; } diff --git a/src/duckdb/src/common/types/date.cpp b/src/duckdb/src/common/types/date.cpp index 4520ad4af..61e3965e4 100644 --- a/src/duckdb/src/common/types/date.cpp +++ b/src/duckdb/src/common/types/date.cpp @@ -615,7 +615,16 @@ int32_t Date::ExtractWeekNumberRegular(date_t date, bool monday_first) { // Returns the date of the monday of the current week. date_t Date::GetMondayOfCurrentWeek(date_t date) { int32_t dotw = Date::ExtractISODayOfTheWeek(date); - return date - (dotw - 1); + return Date::SubtractDays(date, dotw - 1); +} + +date_t Date::SubtractDays(date_t date, int64_t days) { + static const auto min_days = Date::FromDate(DATE_MIN_YEAR, DATE_MIN_MONTH, DATE_MIN_DAY).days; + const auto result = int64_t(date.days) - days; + if (result < min_days || result > NumericLimits::Maximum() - 1) { + throw OutOfRangeException("Date out of range: subtracting %d days from %s", days, Date::ToString(date)); + } + return date_t(int32_t(result)); } } // namespace duckdb diff --git a/src/duckdb/src/common/types/row/tuple_data_collection.cpp b/src/duckdb/src/common/types/row/tuple_data_collection.cpp index e6a72a3a9..3a34bcf12 100644 --- a/src/duckdb/src/common/types/row/tuple_data_collection.cpp +++ b/src/duckdb/src/common/types/row/tuple_data_collection.cpp @@ -60,8 +60,7 @@ void TupleDataCollection::Initialize() { } else { scatter_functions.reserve(layout.ColumnCount()); gather_functions.reserve(layout.ColumnCount()); - for (idx_t col_idx = 0; col_idx < layout.ColumnCount(); col_idx++) { - auto &type = layout.GetTypes()[col_idx]; + for (auto &type : layout.GetTypes()) { scatter_functions.emplace_back(GetScatterFunction(type)); gather_functions.emplace_back(GetGatherFunction(type)); } diff --git a/src/duckdb/src/common/types/type_manager.cpp b/src/duckdb/src/common/types/type_manager.cpp index fc754e892..145e4fdce 100644 --- a/src/duckdb/src/common/types/type_manager.cpp +++ b/src/duckdb/src/common/types/type_manager.cpp @@ -1,4 +1,6 @@ #include "duckdb/common/types/type_manager.hpp" + +#include "duckdb/common/type_visitor.hpp" #include "duckdb/function/cast/cast_function_set.hpp" #include "duckdb/parser/parser.hpp" #include "duckdb/planner/binder.hpp" @@ -102,6 +104,14 @@ LogicalType TypeManager::ParseLogicalType(const string &type_str, ClientContext return parse_function(type_str, context); } +LogicalType TypeManager::ParseLogicalType(const string &type_str) const { + auto type = UnboundType::TryParseAndDefaultBind(type_str); + if (TypeVisitor::Contains(type, LogicalTypeId::UNBOUND)) { + throw InvalidInputException("Type \"%s\" is not a built-in type", type_str); + } + return type; +} + TypeManager &TypeManager::Get(DatabaseInstance &db) { return DBConfig::GetConfig(db).GetTypeManager(); } diff --git a/src/duckdb/src/common/types/variant/parquet_variant_encoding.cpp b/src/duckdb/src/common/types/variant/parquet_variant_encoding.cpp index 8a4eb21be..6bde981f2 100644 --- a/src/duckdb/src/common/types/variant/parquet_variant_encoding.cpp +++ b/src/duckdb/src/common/types/variant/parquet_variant_encoding.cpp @@ -115,7 +115,7 @@ static void CreateMetadata(UnifiedVariantVectorData &variant, Vector &metadata, byte_length); D_ASSERT(offset_ptr + (NumericCast(dictionary_count + 1) * byte_length) == string_ptr); D_ASSERT(string_ptr + total_offset == metadata_blob_data + total_length); - metadata_blob.SetSizeAndFinalize(total_length, total_length); + metadata_blob.SetSizeAndFinalize(NumericCast(total_length), total_length); #ifdef DEBUG auto decoded_metadata = VariantMetadata(metadata_blob); @@ -265,12 +265,12 @@ static idx_t AnalyzeValueData(const UnifiedVariantVectorData &variant, idx_t row auto i = child_indices[entry]; auto keys_index = variant.GetKeysIndex(row, i + nested_data.children_idx); auto values_index = variant.GetValuesIndex(row, i + nested_data.children_idx); - offsets[offset_size + entry] = total_offset; + offsets[offset_size + entry] = NumericCast(total_offset); total_offset += AnalyzeValueData(variant, row, values_index, offsets, nullptr); highest_keys_index = MaxValue(highest_keys_index, keys_index); } - offsets[offset_size + num_elements] = total_offset; + offsets[offset_size + num_elements] = NumericCast(total_offset); //! Calculate the sizes for the objects value data auto field_id_size = CalculateByteLength(highest_keys_index); @@ -295,11 +295,11 @@ static idx_t AnalyzeValueData(const UnifiedVariantVectorData &variant, idx_t row offsets.resize(offset_size + nested_data.child_count + 1); for (idx_t i = 0; i < nested_data.child_count; i++) { auto values_index = variant.GetValuesIndex(row, i + nested_data.children_idx); - offsets[offset_size + i] = total_offset; + offsets[offset_size + i] = NumericCast(total_offset); total_offset += AnalyzeValueData(variant, row, values_index, offsets, nullptr); } - offsets[offset_size + nested_data.child_count] = total_offset; + offsets[offset_size + nested_data.child_count] = NumericCast(total_offset); auto field_offset_size = CalculateByteLength(total_offset); auto num_elements = nested_data.child_count; @@ -477,7 +477,7 @@ static void WritePrimitiveValueData(const UnifiedVariantVectorData &variant, idx } else { WritePrimitiveTypeHeader(value_data); } - Store(string_size, value_data); + Store(NumericCast(string_size), value_data); value_data += sizeof(uint32_t); } else { uint8_t value_header = 0; @@ -704,7 +704,7 @@ static void WriteValueData(const UnifiedVariantVectorData &variant, idx_t row, u value_data += field_offset_size; auto start_ptr = children_ptr; WriteValueData(variant, row, values_index, children_ptr, offsets, offset_index, nullptr); - total_offset += (children_ptr - start_ptr); + total_offset += NumericCast(children_ptr - start_ptr); } memcpy(value_data, reinterpret_cast(&total_offset), field_offset_size); value_data += field_offset_size; @@ -759,7 +759,7 @@ static void WriteValueData(const UnifiedVariantVectorData &variant, idx_t row, u value_data += field_offset_size; auto start_ptr = children_ptr; WriteValueData(variant, row, values_index, children_ptr, offsets, offset_index, nullptr); - total_offset += (children_ptr - start_ptr); + total_offset += NumericCast(children_ptr - start_ptr); } memcpy(value_data, reinterpret_cast(&total_offset), field_offset_size); value_data += field_offset_size; @@ -778,19 +778,19 @@ static void CreateValues(UnifiedVariantVectorData &variant, Vector &value, optio auto value_data = FlatVector::GetDataMutable(value); for (idx_t i = 0; i < count; i++) { - idx_t value_index = 0; + uint32_t value_index = 0; if (value_index_sel) { - value_index = value_index_sel->get_index(i); + value_index = NumericCast(value_index_sel->get_index(i)); } - idx_t row = i; + auto row = NumericCast(i); if (sel) { - row = sel->get_index(i); + row = NumericCast(sel->get_index(i)); } - idx_t result_index = i; + auto result_index = NumericCast(i); if (result_sel) { - result_index = result_sel->get_index(i); + result_index = NumericCast(result_sel->get_index(i)); } bool is_shredded = false; @@ -824,7 +824,7 @@ static void CreateValues(UnifiedVariantVectorData &variant, Vector &value, optio idx_t offset_index = 0; WriteValueData(variant, row, value_index, value_blob_data, offsets, offset_index, shredding_state); D_ASSERT(data_ptr_cast(value_blob.GetDataWriteable() + blob_length) == value_blob_data); - value_blob.SetSizeAndFinalize(blob_length, blob_length); + value_blob.SetSizeAndFinalize(NumericCast(blob_length), blob_length); } } diff --git a/src/duckdb/src/common/types/vector_buffer.cpp b/src/duckdb/src/common/types/vector_buffer.cpp index e1ad55496..957712872 100644 --- a/src/duckdb/src/common/types/vector_buffer.cpp +++ b/src/duckdb/src/common/types/vector_buffer.cpp @@ -338,11 +338,18 @@ void VectorBuffer::Copy(const Vector &source_p, const SelectionVector &source_se finished = true; break; default: { - // for exotic types we flatten followed by copying + // for exotic types we flatten followed by copying - only the copied range [source_offset, source_count) + // is flattened, as e.g. list functions copy the children of every list at a different offset + D_ASSERT(source_count >= source_offset); + const auto range_count = source_count - source_offset; + SelectionVector range_sel(range_count); + for (idx_t i = 0; i < range_count; i++) { + range_sel.set_index(i, sel.get_index(source_offset + i)); + } Vector flattened_vector(Vector::Ref(source)); - flattened_vector.Flatten(sel, source_offset + source_count); - Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), source_count, source_offset, - target_offset, copy_count); + flattened_vector.Flatten(range_sel, range_count); + Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), range_count, 0, target_offset, + copy_count); return; } } diff --git a/src/duckdb/src/common/vector/dictionary_vector.cpp b/src/duckdb/src/common/vector/dictionary_vector.cpp index 9cee519e8..b50cd4461 100644 --- a/src/duckdb/src/common/vector/dictionary_vector.cpp +++ b/src/duckdb/src/common/vector/dictionary_vector.cpp @@ -183,14 +183,12 @@ const Vector &DictionaryVector::GetCachedHashes(const Vector &input) { D_ASSERT(CanCacheHashes(input)); const auto &entry = input.Buffer().Cast().GetEntry(); - lock_guard guard(entry.cached_hashes_lock); - - if (!entry.cached_hashes) { - // Uninitialized: hash the dictionary + CallOnce(entry.cached_hashes_once, [&] { const auto dictionary_size = DictionarySize(input).GetIndex(); - entry.cached_hashes = make_uniq(LogicalType::HASH, dictionary_size); - VectorOperations::Hash(entry.data, *entry.cached_hashes); - } + auto hashes = make_uniq(LogicalType::HASH, dictionary_size); + VectorOperations::Hash(entry.data, *hashes); + entry.cached_hashes = std::move(hashes); + }); return *entry.cached_hashes; } diff --git a/src/duckdb/src/common/vector_operations/comparison_operators.cpp b/src/duckdb/src/common/vector_operations/comparison_operators.cpp index 30a5963f0..ba00b6ecb 100644 --- a/src/duckdb/src/common/vector_operations/comparison_operators.cpp +++ b/src/duckdb/src/common/vector_operations/comparison_operators.cpp @@ -191,6 +191,13 @@ static void ComparatorToBoolean(const Vector &left, const Vector &right, Vector VectorOperations::Comparator(left, right, comparator_result, comp); const auto count = comparator_result.size(); auto cmp_data = comparator_result.Values(); + if (left.GetVectorType() == VectorType::CONSTANT_VECTOR && right.GetVectorType() == VectorType::CONSTANT_VECTOR) { + // the comparison of two constants is a constant + auto entry = cmp_data[0]; + auto value = entry.IsValid() ? Value::BOOLEAN(predicate(entry.GetValue())) : Value(LogicalType::BOOLEAN); + result.Reference(value, count_t(count)); + return; + } result.SetVectorType(VectorType::FLAT_VECTOR); auto result_data = FlatVector::Writer(result, count); for (idx_t i = 0; i < count; i++) { diff --git a/src/duckdb/src/execution/aggregate_hashtable.cpp b/src/duckdb/src/execution/aggregate_hashtable.cpp index b5a2c9977..2b2b1ba5b 100644 --- a/src/duckdb/src/execution/aggregate_hashtable.cpp +++ b/src/duckdb/src/execution/aggregate_hashtable.cpp @@ -1,4 +1,6 @@ #include "duckdb/execution/aggregate_hashtable.hpp" + +#include "duckdb/common/error_data.hpp" #include "duckdb/main/client_context.hpp" #include "duckdb/storage/buffer_manager.hpp" @@ -192,7 +194,12 @@ shared_ptr GroupedAggregateHashTable::GetAggregateAllocator() { } GroupedAggregateHashTable::~GroupedAggregateHashTable() { - Destroy(); + // destroying the states pins their blocks, which can fail when the table is torn down after an out-of-memory + // error - a destructor cannot throw, so the states leak in that case + try { + Destroy(); + } catch (...) { // NOLINT + } } void GroupedAggregateHashTable::Destroy() { @@ -213,17 +220,28 @@ void GroupedAggregateHashTable::DestroyAggregateData(PartitionedTupleData &data, PartitionedTupleDataAppendState &append_state) { // Call the destructor for each of the aggregates data.FlushAppendState(append_state); + ErrorData first_error; for (auto &data_collection : data.GetPartitions()) { if (data_collection->Count() == 0) { continue; } - TupleDataChunkIterator iterator(*data_collection, TupleDataPinProperties::DESTROY_AFTER_DONE, false); - auto &row_locations = iterator.GetChunkState().row_locations; - do { - RowOperations::DestroyStates(state.row_state, *layout_ptr, row_locations); - } while (iterator.Next()); + try { + TupleDataChunkIterator iterator(*data_collection, TupleDataPinProperties::DESTROY_AFTER_DONE, false); + auto &row_locations = iterator.GetChunkState().row_locations; + do { + RowOperations::DestroyStates(state.row_state, *layout_ptr, row_locations); + } while (iterator.Next()); + } catch (std::exception &ex) { + // keep releasing the other partitions, freeing their memory may let their states be destroyed + if (!first_error.HasError()) { + first_error = ErrorData(ex); + } + } data_collection->Reset(); } + if (first_error.HasError()) { + first_error.Throw(); + } } shared_ptr GroupedAggregateHashTable::GetLayoutPtr() { diff --git a/src/duckdb/src/execution/expression_executor.cpp b/src/duckdb/src/execution/expression_executor.cpp index a61502e62..db45aaa8d 100644 --- a/src/duckdb/src/execution/expression_executor.cpp +++ b/src/duckdb/src/execution/expression_executor.cpp @@ -325,6 +325,8 @@ idx_t ExpressionExecutor::Select(const Expression &expr, ExpressionState *state, case ExpressionClass::BOUND_FUNCTION: return Select(expr.Cast(), state, sel, count, true_sel, false_sel); // NOLINT: c-style cast + case ExpressionClass::BOUND_OPERATOR: + return Select(expr.Cast(), *state, sel, count, true_sel, false_sel); default: return DefaultSelect(expr, state, sel, count, true_sel, false_sel); } diff --git a/src/duckdb/src/execution/expression_executor/execute_case.cpp b/src/duckdb/src/execution/expression_executor/execute_case.cpp index 05667af50..f1ff36c4c 100644 --- a/src/duckdb/src/execution/expression_executor/execute_case.cpp +++ b/src/duckdb/src/execution/expression_executor/execute_case.cpp @@ -1,3 +1,4 @@ +#include "duckdb/common/vector/array_vector.hpp" #include "duckdb/common/vector/list_vector.hpp" #include "duckdb/common/vector/map_vector.hpp" #include "duckdb/common/vector/string_vector.hpp" @@ -92,6 +93,27 @@ void ExpressionExecutor::Execute(const BoundCaseExpression &expr, ExpressionStat } } +static void FillNulls(ValidityMask &validity, const SelectionVector &sel, idx_t count) { + if (count == 0) { + return; + } + validity.EnsureWritable(); + idx_t current_entry = sel.get_index(0) / ValidityMask::BITS_PER_VALUE; + validity_t invalid_bits = 0; + for (idx_t i = 0; i < count; i++) { + auto row_idx = sel.get_index(i); + D_ASSERT(row_idx < validity.Capacity()); + auto entry_idx = row_idx / ValidityMask::BITS_PER_VALUE; + if (entry_idx != current_entry) { + validity.GetValidityEntryUnsafe(current_entry) &= ~invalid_bits; + current_entry = entry_idx; + invalid_bits = 0; + } + invalid_bits |= validity_t(1) << (row_idx % ValidityMask::BITS_PER_VALUE); + } + validity.GetValidityEntryUnsafe(current_entry) &= ~invalid_bits; +} + template void TemplatedFillLoop(const Vector &vector, Vector &result, const SelectionVector &sel, sel_t count) { result.SetVectorType(VectorType::FLAT_VECTOR); @@ -100,9 +122,7 @@ void TemplatedFillLoop(const Vector &vector, Vector &result, const SelectionVect if (vector.GetVectorType() == VectorType::CONSTANT_VECTOR) { auto data = ConstantVector::GetData(vector); if (ConstantVector::IsNull(vector)) { - for (idx_t i = 0; i < count; i++) { - result_mask.SetInvalid(sel.get_index(i)); - } + FillNulls(result_mask, sel, count); } else { for (idx_t i = 0; i < count; i++) { res[sel.get_index(i)] = *data; @@ -127,9 +147,7 @@ void ValidityFillLoop(const Vector &vector, Vector &result, const SelectionVecto auto &result_mask = FlatVector::ValidityMutable(result); if (vector.GetVectorType() == VectorType::CONSTANT_VECTOR) { if (ConstantVector::IsNull(vector)) { - for (idx_t i = 0; i < count; i++) { - result_mask.SetInvalid(sel.get_index(i)); - } + FillNulls(result_mask, sel, count); } } else { auto entries = vector.Validity(); @@ -224,6 +242,30 @@ void ExpressionExecutor::FillSwitch(const Vector &vector, Vector &result, const result.Verify(); break; } + case PhysicalType::ARRAY: { + const bool is_constant = vector.GetVectorType() == VectorType::CONSTANT_VECTOR; + if (!is_constant) { + // below code needs constant or flat arrays + vector.Flatten(); + } + ValidityFillLoop(vector, result, sel, count); + auto array_size = ArrayType::GetSize(result.GetType()); + auto child_count = count * array_size; + SelectionVector source_sel(child_count); + SelectionVector result_sel(child_count); + for (idx_t i = 0; i < count; i++) { + auto source_idx = is_constant ? 0 : i; + auto result_idx = sel.get_index(i); + for (idx_t k = 0; k < array_size; k++) { + source_sel.set_index(i * array_size + k, source_idx * array_size + k); + result_sel.set_index(i * array_size + k, result_idx * array_size + k); + } + } + Vector source_child(ArrayVector::GetChild(vector), source_sel, child_count); + source_child.Flatten(); + FillSwitch(source_child, ArrayVector::GetChildMutable(result), result_sel, NumericCast(child_count)); + break; + } default: throw NotImplementedException("Unimplemented type for case expression: %s", result.GetType().ToString()); } diff --git a/src/duckdb/src/execution/expression_executor/execute_operator.cpp b/src/duckdb/src/execution/expression_executor/execute_operator.cpp index 7837d405e..5e3c2683e 100644 --- a/src/duckdb/src/execution/expression_executor/execute_operator.cpp +++ b/src/duckdb/src/execution/expression_executor/execute_operator.cpp @@ -17,6 +17,67 @@ unique_ptr ExpressionExecutor::InitializeState(const BoundOpera return result; } +template +static idx_t SelectNullLoop(const VectorValidityIterator &entries, const SelectionVector &sel, idx_t count, + optional_ptr valid_sel, optional_ptr null_sel) { + idx_t valid_count = 0, null_count = 0; + for (idx_t i = 0; i < count; i++) { + const auto row_idx = sel.get_index(i); + const auto valid = entries.IsValid(i); + if (HAS_VALID_SEL) { + valid_sel->set_index(valid_count, row_idx); + valid_count += valid; + } + if (HAS_NULL_SEL) { + null_sel->set_index(null_count, row_idx); + null_count += !valid; + } + } + return HAS_VALID_SEL ? valid_count : count - null_count; +} + +static idx_t SelectNulls(const Vector &input, const SelectionVector &sel, idx_t count, + optional_ptr valid_sel, optional_ptr null_sel) { + auto entries = input.Validity(); + if (entries.CannotHaveNull() || input.GetVectorType() == VectorType::CONSTANT_VECTOR) { + const auto valid = entries.CannotHaveNull() || entries.IsValid(0); + auto result_sel = valid ? valid_sel : null_sel; + if (result_sel) { + for (idx_t i = 0; i < count; i++) { + result_sel->set_index(i, sel.get_index(i)); + } + } + return valid ? count : 0; + } + if (valid_sel && null_sel) { + return SelectNullLoop(entries, sel, count, valid_sel, null_sel); + } + if (valid_sel) { + return SelectNullLoop(entries, sel, count, valid_sel, null_sel); + } + return SelectNullLoop(entries, sel, count, valid_sel, null_sel); +} + +idx_t ExpressionExecutor::Select(const BoundOperatorExpression &expr, ExpressionState &state, + optional_ptr sel, idx_t count, + optional_ptr true_sel, optional_ptr false_sel) { + const auto type = expr.GetExpressionType(); + if (type != ExpressionType::OPERATOR_IS_NULL && type != ExpressionType::OPERATOR_IS_NOT_NULL) { + return DefaultSelect(expr, &state, sel.get(), count, true_sel.get(), false_sel.get()); + } + D_ASSERT(expr.GetChildren().size() == 1); + state.intermediate_chunk.Reset(); + auto &child = state.intermediate_chunk.data[0]; + Execute(*expr.GetChildren()[0], state.child_states[0].get(), sel.get(), count, child); + + const SelectionVector identity; + const auto &input_sel = sel ? *sel : identity; + if (type == ExpressionType::OPERATOR_IS_NOT_NULL) { + return SelectNulls(child, input_sel, count, true_sel, false_sel); + } + return count - SelectNulls(child, input_sel, count, false_sel, true_sel); +} + void ExpressionExecutor::Execute(const BoundOperatorExpression &expr, ExpressionState *state, const SelectionVector *sel, idx_t count, Vector &result) { // special handling for special snowflake 'IN' diff --git a/src/duckdb/src/execution/ie_join_union.cpp b/src/duckdb/src/execution/ie_join_union.cpp index f4652f189..c19619d65 100644 --- a/src/duckdb/src/execution/ie_join_union.cpp +++ b/src/duckdb/src/execution/ie_join_union.cpp @@ -304,9 +304,9 @@ void IEJoinUnion::InitializeTables(ClientContext &client, const PhysicalComparis ? OrderType::DESCENDING : OrderType::ASCENDING; BoundOrderByNode order1(first_order, OrderByNullType::NULLS_LAST, - make_uniq(conditions[0].GetLHS().GetReturnType(), 0)); + make_uniq(conditions[0].GetLHS().GetReturnType(), storage_t(0))); BoundOrderByNode order2(second_order, OrderByNullType::NULLS_LAST, - make_uniq(conditions[1].GetLHS().GetReturnType(), 0)); + make_uniq(conditions[1].GetLHS().GetReturnType(), storage_t(0))); // 2. if (op1 ∈ {>, ≥}) sort L1 in descending order // 3. else if (op1 ∈ {<, ≤}) sort L1 in ascending order diff --git a/src/duckdb/src/execution/index/unbound_index.cpp b/src/duckdb/src/execution/index/unbound_index.cpp index 7e4b76737..d65c8b900 100644 --- a/src/duckdb/src/execution/index/unbound_index.cpp +++ b/src/duckdb/src/execution/index/unbound_index.cpp @@ -6,6 +6,7 @@ #include "duckdb/parser/parsed_data/create_index_info.hpp" #include "duckdb/planner/expression_binder/index_binder.hpp" #include "duckdb/storage/block_manager.hpp" +#include "duckdb/storage/buffer_manager.hpp" #include "duckdb/storage/index_storage_info.hpp" #include "duckdb/storage/table_io_manager.hpp" @@ -98,10 +99,9 @@ void UnboundIndex::BufferChunk(DataChunk &table_chunk, Vector &row_ids, const Bu } combined_chunk.data.back().Reference(row_ids); - auto &allocator = Allocator::Get(db); auto &buffer = buffered_replays.GetBuffer(replay_type); if (buffer == nullptr) { - buffer = make_uniq(allocator, types); + buffer = make_uniq(BufferManager::GetBufferManager(db), types); } // The starting index of the buffer range is the size of the buffer. const idx_t start = buffer->Count(); diff --git a/src/duckdb/src/execution/join_hashtable.cpp b/src/duckdb/src/execution/join_hashtable.cpp index a3c102c4c..bd90a7982 100644 --- a/src/duckdb/src/execution/join_hashtable.cpp +++ b/src/duckdb/src/execution/join_hashtable.cpp @@ -101,15 +101,11 @@ JoinHashTable::JoinHashTable(ClientContext &context_p, const PhysicalOperator &o } void JoinHashTable::FinishInitWithLayout(shared_ptr published_layout, - vector dict_index_width_p) { + shared_ptr dict_info_p) { D_ASSERT(!layout_ptr); layout_ptr = std::move(published_layout); - if (dict_index_width_p.empty()) { - dict_index_width.assign(build_types.size(), 0); - } else { - D_ASSERT(dict_index_width_p.size() == build_types.size()); - dict_index_width = std::move(dict_index_width_p); - } + D_ASSERT(!dict_info_p || dict_info_p->columns.size() == build_types.size()); + dict_info = std::move(dict_info_p); // Initialize the row matcher that are used for filtering during the probing only if there are non-equality preds if (!non_equality_predicates.empty()) { @@ -126,6 +122,8 @@ void JoinHashTable::FinishInitWithLayout(shared_ptr published_l } chains_longer_than_one.store(false, std::memory_order_relaxed); + // a fresh matcher: Initialize appends, and a recursive CTE iteration publishes the layout again + row_matcher_build = RowMatcher(); row_matcher_build.Initialize(true, *layout_ptr, equality_predicates); const auto &offsets = layout_ptr->GetOffsets(); @@ -140,9 +138,6 @@ void JoinHashTable::FinishInitWithLayout(shared_ptr published_l dead_end = make_unsafe_uniq_array_uninitialized(layout_ptr->GetRowWidth()); memset(dead_end.get(), 0, layout_ptr->GetRowWidth()); - - // indexed parallel to build_types / payload columns (not to output_columns) - dict_registry.assign(build_types.size(), nullptr); } JoinHashTable::~JoinHashTable() { @@ -185,22 +180,7 @@ void JoinHashTable::Merge(JoinHashTable &other) { sink_collection->Combine(*other.sink_collection); - // Reconcile per-column pinned dictionary entries. For global dictionary producers every thread pins the - // same buffer_ptr, so this is normally a no-op or a "take theirs" adoption. - if (dict_registry.size() < other.dict_registry.size()) { - dict_registry.resize(other.dict_registry.size(), nullptr); - } - for (idx_t col = 0; col < other.dict_registry.size(); col++) { - if (!other.dict_registry[col]) { - continue; - } - if (!dict_registry[col]) { - dict_registry[col] = std::move(other.dict_registry[col]); - } else { - // Both threads pinned the same upstream entry, so ids match by construction; a mismatch is a producer bug. - D_ASSERT(dict_registry[col]->id == other.dict_registry[col]->id); - } - } + // dict_info is the descriptor shared by every per-thread JHT of this join; there is nothing to reconcile } static void ApplyBitmaskAndGetSaltBuild(Vector &hashes_v, Vector &salt_v, const idx_t &bitmask) { @@ -250,12 +230,13 @@ static idx_t ProbeForPointersInternal(JoinHashTable::ProbeState &state, JoinHash unsafe_optional_ptr entries, Vector &pointers_result_v, optional_ptr row_sel, idx_t &count) { auto hashes_dense = FlatVector::GetDataMutable(state.hashes_dense_v); + const auto bitmask = ht.bitmask; idx_t keys_to_compare_count = 0; for (idx_t i = 0; i < count; i++) { auto row_hash = hashes_dense[i]; // hashes have been flattened before -> always access dense - auto row_ht_offset = row_hash & ht.bitmask; + auto row_ht_offset = row_hash & bitmask; if (USE_SALTS) { // increment the ht_offset of the entry as long as the next entry is occupied and salt does not match @@ -279,7 +260,7 @@ static idx_t ProbeForPointersInternal(JoinHashTable::ProbeState &state, JoinHash } // full and salt do not match -> continue probing - IncrementAndWrap(row_ht_offset, ht.bitmask); + IncrementAndWrap(row_ht_offset, bitmask); } } else { const ht_entry_t entry = entries.get()[row_ht_offset]; @@ -539,8 +520,9 @@ uint8_t JoinHashTable::GetDictSurvivingIndexWidth(idx_t build_col_idx, const Vec if (!dict_size.IsValid()) { return 0; } - // only narrow when strictly smaller than the native slot (never regress; e.g. INTEGER over a D<=256 dict) - const uint8_t index_width = DictIndexWidth(dict_size.GetIndex()); + // only narrow when strictly smaller than the native slot (never regress; e.g. INTEGER over a D<=255 dict). + // One extra slot is reserved for the NULL index that encodes NULL-only chunks (see CreateDictSurvivingColumn). + const uint8_t index_width = DictIndexWidth(dict_size.GetIndex() + 1); const auto native_bytes = GetTypeIdSize(type.InternalType()); if (index_width >= native_bytes) { return 0; @@ -548,34 +530,70 @@ uint8_t JoinHashTable::GetDictSurvivingIndexWidth(idx_t build_col_idx, const Vec return index_width; } -void JoinHashTable::PinDictSurvivingColumn(idx_t build_col_idx, const Vector &incoming, uint8_t index_width) { - // Slot was narrowed on the first chunk; there is no fallback for a non-dict later chunk. Throw, not D_ASSERT: - // the Cast below is UB in release on a non-dict vector, scattering foreign bytes as indices. - if (incoming.GetVectorType() != VectorType::DICTIONARY_VECTOR || DictionaryVector::DictionaryId(incoming).empty() || - !DictionaryVector::IsGlobalDictionary(incoming)) { - throw InternalException("dict-surviving join: narrowed column %llu received a " - "non-global-dictionary chunk; build pipeline is not single-source", - static_cast(build_col_idx)); +LogicalType DictSurvivingInfo::IndexType(uint8_t index_width) { + switch (index_width) { + case sizeof(uint8_t): + return LogicalType::UTINYINT; + case sizeof(uint16_t): + return LogicalType::USMALLINT; + default: + D_ASSERT(index_width == sizeof(uint32_t)); + return LogicalType::UINTEGER; } - const auto &entry = incoming.Buffer().Cast().GetEntry(); - if (dict_registry[build_col_idx]) { - // Subsequent chunks wrap the same entry, so ids match by construction; a mismatch is a producer bug. - D_ASSERT(dict_registry[build_col_idx]->id == entry.id); +} + +DictSurvivingColumn JoinHashTable::CreateDictSurvivingColumn(const Vector &incoming, uint8_t index_width) { + D_ASSERT(index_width != 0); + D_ASSERT(incoming.GetVectorType() == VectorType::DICTIONARY_VECTOR); + D_ASSERT(DictionaryVector::IsGlobalDictionary(incoming)); + // The upstream child is a zero-copy gather: its long strings point into the producer's row-store heap, recycled + // before we gather on probe. Deep-copy into a self-owned entry so it outlives them, and append one NULL slot at + // index upstream_count: NULL-only chunks (the unmatched-build scan of an upstream FULL/RIGHT join fills the probe + // columns with NULL) are stored as that index, so the regular dictionary gather reconstructs them. + const auto &upstream_child = DictionaryVector::Child(incoming); + const auto upstream_count = upstream_child.size(); + // GetDictSurvivingIndexWidth sized the width to upstream_count + 1 + D_ASSERT(upstream_count < (idx_t(1) << (8 * index_width))); + DictSurvivingColumn column; + column.index_width = index_width; + // CreateReusableGlobalDictionary assigns a fresh id: this is a different dictionary than the upstream one (one + // more slot), so a downstream consumer must not treat the two as interchangeable. + column.entry = DictionaryVector::CreateReusableGlobalDictionary(upstream_child.GetType(), upstream_count + 1); + if (upstream_count > 0) { + VectorOperations::Copy(upstream_child, column.entry->data, upstream_count, 0, 0); + } + FlatVector::SetNull(column.entry->data, upstream_count, true); + column.upstream_id = DictionaryVector::DictionaryId(incoming); + return column; +} + +void JoinHashTable::EncodeDictSurvivingColumn(const DictSurvivingColumn &column, idx_t build_col_idx, + const Vector &incoming, idx_t count, vector &buffers, + vector &vectors) { + if (incoming.GetVectorType() == VectorType::DICTIONARY_VECTOR) { + // Slot was narrowed against one specific upstream dictionary; a different one would scatter foreign indices. + if (DictionaryVector::DictionaryId(incoming) != column.upstream_id) { + throw InternalException("dict-surviving join: narrowed column %llu received a chunk with an unexpected " + "dictionary; build pipeline is not single-source", + static_cast(build_col_idx)); + } + // Indices are < upstream size by construction, and the publisher sized the width to upstream size + 1 + ScatterDictIndices(column.index_width, DictionaryVector::SelVector(incoming), count, + buffer_manager.GetBufferAllocator(), buffers, vectors); return; } - // The upstream child is a zero-copy gather: its long strings point into the producer's row-store heap, recycled - // before we gather on probe. Deep-copy into a self-owned entry so it outlives them. - const auto &upstream_child = entry.data; - const auto child_count = upstream_child.size(); - // child_count fits index_width by construction (publisher sized the width to this dict). Assert so a producer - // that grows the child past it fails loudly instead of truncating in the UnsafeNumericCast. - D_ASSERT(child_count <= (idx_t(1) << (8 * index_width))); - auto owned_entry = DictionaryVector::CreateReusableGlobalDictionary(upstream_child.GetType(), child_count); - if (child_count > 0) { - VectorOperations::Copy(upstream_child, owned_entry->data, child_count, 0, 0); + if (!VectorOperations::HasNotNull(incoming)) { + // e.g. the unmatched-build scan of an upstream FULL/RIGHT join: every row stores the reserved NULL index + const auto null_index = + Value::Numeric(DictSurvivingInfo::IndexType(column.index_width), NumericCast(column.NullIndex())); + vectors.emplace_back(null_index, count_t(count)); + return; } - owned_entry->id = entry.id; - dict_registry[build_col_idx] = std::move(owned_entry); + // There is no fallback for values that are not in the pinned dictionary. Throw, not D_ASSERT: storing the + // native bytes into a narrowed slot would corrupt the row store. + throw InternalException("dict-surviving join: narrowed column %llu received a non-dictionary chunk with " + "values; build pipeline is not single-source", + static_cast(build_col_idx)); } static bool MarkJoinNullRows(const UnifiedVectorFormat &format, idx_t count, @@ -661,15 +679,13 @@ void JoinHashTable::Build(PartitionedTupleDataAppendState &append_state, DataChu vector dict_idx_vectors; for (idx_t i = 0; i < payload.ColumnCount(); i++) { auto &incoming = payload.data[i]; - const uint8_t index_width = i < dict_index_width.size() ? dict_index_width[i] : 0; - if (index_width == 0) { + auto dict_column = GetDictSurvivingColumn(i); + if (!dict_column) { source_chunk.data[col_offset + i].Reference(incoming); continue; } - // Narrowed column: pin the dictionary, then scatter its sel indices at the chosen width in place of the value. - PinDictSurvivingColumn(i, incoming, index_width); - ScatterDictIndices(index_width, DictionaryVector::SelVector(incoming), payload.size(), - buffer_manager.GetBufferAllocator(), dict_idx_buffers, dict_idx_vectors); + // Narrowed column: store dictionary indices (or the reserved NULL index) at the chosen width, not the value + EncodeDictSurvivingColumn(*dict_column, i, incoming, payload.size(), dict_idx_buffers, dict_idx_vectors); source_chunk.data[col_offset + i].Reference(dict_idx_vectors.back()); } col_offset += payload.ColumnCount(); @@ -830,6 +846,7 @@ static inline void InsertMatchesAndIncrementMisses(unsafe_optional_ptr(entry, row_ptr_to_insert, salt, ht.pointer_offset); + InsertRowToEntry(entry, row_ptr_to_insert, salt, pointer_offset); } } @@ -912,6 +929,7 @@ static void InsertHashesLoop(unsafe_optional_ptr> entries, Ve // use the ht bitmask to make the modulo operation faster but keep the salt bits intact idx_t capacity_mask = ht.bitmask | ht_entry_t::SALT_MASK; + const auto pointer_offset = ht.pointer_offset; while (remaining_count > 0) { idx_t salt_match_count = 0; @@ -944,7 +962,7 @@ static void InsertHashesLoop(unsafe_optional_ptr> entries, Ve auto &atomic_entry = entries.get()[ht_offset]; const auto row_ptr_to_insert = lhs_row_locations[row_index]; const auto potential_collided_ptr = - InsertRowToEntry(atomic_entry, row_ptr_to_insert, salt, ht.pointer_offset); + InsertRowToEntry(atomic_entry, row_ptr_to_insert, salt, pointer_offset); if (PARALLEL) { // if the insertion was not successful, the entry was occupied in the meantime, so we have to @@ -1706,14 +1724,13 @@ void JoinHashTable::GatherRHS(Vector &row_ptrs, const SelectionVector &ptr_sel, // Pinned column: re-emit the upstream dictionary. Layout is [conditions, build, (found), hash], so payload // columns sit at layout index >= cond_count; guard the subtraction so it cannot underflow into a spurious idx. if (output_col_idx >= cond_count) { - const idx_t payload_idx = output_col_idx - cond_count; - if (payload_idx < dict_registry.size() && dict_registry[payload_idx]) { + auto dict_column = GetDictSurvivingColumn(output_col_idx - cond_count); + if (dict_column) { SelectionVector build_sel_vec(count); const auto col_offset = offsets[output_col_idx]; // Read the dict index at the width chosen at layout-publication time - const uint8_t index_width = payload_idx < dict_index_width.size() ? dict_index_width[payload_idx] : 0; - LoadDictIndices(index_width, ptrs, ptr_sel, count, col_offset, build_sel_vec); - vector.Dictionary(dict_registry[payload_idx], build_sel_vec, count); + LoadDictIndices(dict_column->index_width, ptrs, ptr_sel, count, col_offset, build_sel_vec); + vector.Dictionary(dict_column->entry, build_sel_vec, count); continue; } } @@ -2563,16 +2580,17 @@ void JoinHashTable::ResetForNewIterationSinglePartition() { // layout was never published (no Sink chunk last iteration); the next first chunk will publish return; } - data_collection->Reset(); + // Return to the unpublished state (see FinishInitWithLayout): the next iteration's first chunk publishes the + // layout afresh. The rerun build is the same operator, but it may deliver a different dictionary (e.g. a + // nextval() in its build side), a dictionary of another size, or none at all, so a narrowed slot and the + // dictionary it indexes into cannot be carried over. + layout_ptr.reset(); + dict_info.reset(); + data_collection.reset(); // Always use a single partition (radix_bits=0) to avoid per-iteration overhead of resetting // and re-creating many radix partitions when only one thread builds the hash table. - if (radix_bits != 0) { - radix_bits = 0; - sink_collection = make_uniq(buffer_manager, layout_ptr, MemoryTag::HASH_TABLE, - idx_t(0), layout_ptr->ColumnCount() - 1, context); - } else { - sink_collection->Reset(); - } + radix_bits = 0; + sink_collection.reset(); InitializePartitionMasks(); capacity = DConstants::INVALID_INDEX; bitmask = DConstants::INVALID_INDEX; @@ -2593,10 +2611,6 @@ void JoinHashTable::ResetForNewIterationSinglePartition() { aux_next_ptrs.Reset(); aux_next_ptrs_data = nullptr; use_dict_emission = false; - // Drop pinned upstream dictionary entries; the next iteration's first chunk re-pins - for (auto &entry : dict_registry) { - entry.reset(); - } } bool JoinHashTable::PrepareExternalFinalize(const idx_t max_ht_size) { @@ -2792,10 +2806,8 @@ bool JoinHashTable::CanUseDictionaryEmission(const PhysicalHashJoin &op, bool ex } // mutually exclusive with the dict-surviving path: that path narrows the payload slot to a 1/2/4-byte // index, while NEXT_PTR embedding here overwrites a different field and assumes payload bytes are intact. - for (const auto &entry : dict_registry) { - if (entry) { - return false; - } + if (HasDictSurvivingColumns()) { + return false; } // SINGLE joins need FlatVector::SetNull for unmatched rows; dictionary vectors cannot supply it if (join_type == JoinType::SINGLE) { diff --git a/src/duckdb/src/execution/operator/aggregate/physical_limited_distinct.cpp b/src/duckdb/src/execution/operator/aggregate/physical_limited_distinct.cpp index 504ab3295..72bc29b25 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_limited_distinct.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_limited_distinct.cpp @@ -44,7 +44,7 @@ struct LimitedDistinctGlobalSinkState : public GlobalSinkState { LimitedDistinctGlobalSinkState(const PhysicalLimitedDistinct &op, ClientContext &context) : limit_reached(false) { auto aggregate_objects = GetAggregateObjects(op.aggregates); auto payload_types = GetPayloadTypes(op.aggregates); - ht = make_uniq(context, Allocator::Get(context), op.group_types, payload_types, + ht = make_uniq(context, BufferAllocator::Get(context), op.group_types, payload_types, aggregate_objects); } @@ -56,7 +56,7 @@ struct LimitedDistinctGlobalSinkState : public GlobalSinkState { struct LimitedDistinctLocalSinkState : public LocalSinkState { LimitedDistinctLocalSinkState(const PhysicalLimitedDistinct &op, ExecutionContext &context) : group_executor(context.client, op.groups), payload_executor(context.client) { - auto &allocator = Allocator::Get(context.client); + auto &allocator = BufferAllocator::Get(context.client); auto aggregate_objects = GetAggregateObjects(op.aggregates); auto payload_types = GetPayloadTypes(op.aggregates); diff --git a/src/duckdb/src/execution/operator/aggregate/physical_perfecthash_aggregate.cpp b/src/duckdb/src/execution/operator/aggregate/physical_perfecthash_aggregate.cpp index c0a2efcc1..8f8d3f944 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_perfecthash_aggregate.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_perfecthash_aggregate.cpp @@ -87,7 +87,7 @@ unique_ptr PhysicalPerfectHashAggregate::CreateHT(All class PerfectHashAggregateGlobalState : public GlobalSinkState { public: PerfectHashAggregateGlobalState(const PhysicalPerfectHashAggregate &op, ClientContext &context) - : ht(op.CreateHT(Allocator::Get(context), context)) { + : ht(op.CreateHT(BufferAllocator::Get(context), context)) { } //! The lock for updating the global aggregate state @@ -99,7 +99,7 @@ class PerfectHashAggregateGlobalState : public GlobalSinkState { class PerfectHashAggregateLocalState : public LocalSinkState { public: PerfectHashAggregateLocalState(const PhysicalPerfectHashAggregate &op, ExecutionContext &context) - : ht(op.CreateHT(Allocator::Get(context.client), context.client)) { + : ht(op.CreateHT(BufferAllocator::Get(context.client), context.client)) { group_chunk.InitializeEmpty(op.group_types); if (!op.payload_types.empty()) { aggregate_input_chunk.InitializeEmpty(op.payload_types); diff --git a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp index baff3cea0..81b42c17f 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp @@ -1,5 +1,6 @@ #include "duckdb/execution/operator/aggregate/physical_window.hpp" +#include "duckdb/common/once_flag.hpp" #include "duckdb/common/sorting/sort_strategy.hpp" #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/common/types/row/tuple_data_iterator.hpp" @@ -162,12 +163,14 @@ class WindowHashGroup { ValidityMask partition_mask; //! The order boundary mask (maps sort column END to mask) OrderMasks order_masks; + OnceFlag masks_initialized; //! The fully materialised data collection unique_ptr collection; //! The processing stage for this group atomic stage; //! The function global states for this hash group ExecutorGlobalStates gestates; + OnceFlag gestates_initialized; //! Executor local states, one per thread ThreadLocalStates thread_states; @@ -551,8 +554,8 @@ class WindowGlobalSourceState : public GlobalSourceState { protected: //! Build task list void CreateTaskList(); - //! Finish a task - void FinishTask(TaskPtr task); + //! Finish a task and return any completed group for destruction outside the source lock. + WindowHashGroupPtr FinishTask(TaskPtr task); }; WindowGlobalSourceState::WindowGlobalSourceState(ClientContext &client, WindowGlobalSinkState &gsink_p) @@ -661,28 +664,23 @@ void WindowHashGroup::UpdateScanner(ScannerPtr &scanner, idx_t begin_idx) const } void WindowHashGroup::AllocateMasks() { - // Single-threaded building as this is mostly memory allocation - lock_guard gestate_guard(lock); - if (partition_mask.IsMaskSet()) { - return; - } - - // Allocate masks inside the lock - partition_mask.Initialize(count); - - // All masks refer to the shared sort's partition columns, including duplicate expressions. - const auto &sort_expr = gsink.op.select_list[gsink.op.order_idx]->Cast(); - const auto order_begin = gsink.op.partition_info.RequiresPartitionColumns() ? 0 : sort_expr.Partitions().size(); - const auto &executors = gsink.executors; - for (auto &wexec : executors) { - auto &wexpr = wexec->wexpr; - - auto &order_mask = order_masks[order_begin + wexpr.OrderBy().size()]; - if (order_mask.IsMaskSet()) { - continue; + CallOnce(masks_initialized, [&] { + ValidityMask new_partition_mask; + new_partition_mask.Initialize(count); + OrderMasks new_order_masks; + + // All masks refer to the shared sort's partition columns, including duplicate expressions. + const auto &sort_expr = gsink.op.select_list[gsink.op.order_idx]->Cast(); + const auto order_begin = gsink.op.partition_info.RequiresPartitionColumns() ? 0 : sort_expr.Partitions().size(); + for (auto &wexec : gsink.executors) { + auto &order_mask = new_order_masks[order_begin + wexec->wexpr.OrderBy().size()]; + if (!order_mask.IsMaskSet()) { + order_mask.Initialize(count); + } } - order_mask.Initialize(count); - } + partition_mask = std::move(new_partition_mask); + order_masks = std::move(new_order_masks); + }); } void WindowHashGroup::ComputeMasks(const idx_t block_begin, const idx_t block_end) { @@ -903,22 +901,17 @@ void WindowLocalSourceState::Mask(ExecutionContext &context, InterruptState &int } WindowHashGroup::ExecutorGlobalStates &WindowHashGroup::GetGlobalStates(ClientContext &client) { - // Single-threaded building as this is mostly memory allocation - lock_guard gestate_guard(lock); - const auto &executors = gsink.executors; - if (gestates.size() == executors.size()) { - return gestates; - } - - // These can be large so we defer building them until we are ready. - const auto &sort_expr = gsink.op.select_list[gsink.op.order_idx]->Cast(); - const auto order_begin = gsink.op.partition_info.RequiresPartitionColumns() ? 0 : sort_expr.Partitions().size(); - for (auto &wexec : executors) { - auto &wexpr = wexec->wexpr; - auto &order_mask = order_masks[order_begin + wexpr.OrderBy().size()]; - gestates.emplace_back(wexec->GetGlobalState(client, count, partition_mask, order_mask)); - } - + CallOnce(gestates_initialized, [&] { + // These can be large so we defer building them until we are ready. + ExecutorGlobalStates new_gestates; + const auto &sort_expr = gsink.op.select_list[gsink.op.order_idx]->Cast(); + const auto order_begin = gsink.op.partition_info.RequiresPartitionColumns() ? 0 : sort_expr.Partitions().size(); + for (auto &wexec : gsink.executors) { + auto &order_mask = order_masks.at(order_begin + wexec->wexpr.OrderBy().size()); + new_gestates.emplace_back(wexec->GetGlobalState(client, count, partition_mask, order_mask)); + } + gestates = std::move(new_gestates); + }); return gestates; } @@ -1036,8 +1029,10 @@ WindowLocalSourceState::WindowLocalSourceState(WindowGlobalSourceState &gsource) } bool WindowGlobalSourceState::TryNextTask(TaskPtr &task, Task &task_local) { + // Destroy completed groups after releasing the source lock. + WindowHashGroupPtr finished_group; annotated_lock_guard guard(lock); - FinishTask(task); + finished_group = FinishTask(task); if (!HasMoreTasks()) { task = nullptr; @@ -1081,17 +1076,18 @@ bool WindowGlobalSourceState::TryNextTask(TaskPtr &task, Task &task_local) { return false; } -void WindowGlobalSourceState::FinishTask(TaskPtr task) { +WindowGlobalSourceState::WindowHashGroupPtr WindowGlobalSourceState::FinishTask(TaskPtr task) { if (!task) { - return; + return nullptr; } const auto group_idx = task->group_idx; auto &finished_hash_group = window_hash_groups[group_idx]; D_ASSERT(finished_hash_group); + WindowHashGroupPtr finished_group; if (++finished_hash_group->completed >= finished_hash_group->GetTaskCount()) { - finished_hash_group.reset(); + finished_group = std::move(finished_hash_group); // Remove it from the active groups auto &v = active_groups; v.erase(std::remove(v.begin(), v.end(), group_idx), v.end()); @@ -1099,6 +1095,7 @@ void WindowGlobalSourceState::FinishTask(TaskPtr task) { // Count the global tasks completed. ++completed; + return finished_group; } bool WindowLocalSourceState::TryAssignTask() { diff --git a/src/duckdb/src/execution/operator/csv_scanner/buffer_manager/csv_buffer.cpp b/src/duckdb/src/execution/operator/csv_scanner/buffer_manager/csv_buffer.cpp index 1b5e27843..8972d94c8 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/buffer_manager/csv_buffer.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/buffer_manager/csv_buffer.cpp @@ -80,17 +80,24 @@ void CSVBuffer::LoadRandomAccess(CSVFileHandle &file_handle) { shared_ptr CSVBuffer::Pin(CSVFileHandle &file_handle, bool &has_seeked) { lock_guard guard(load_lock); - if (!IsInMemory()) { - // We have to reload it from disk - block = nullptr; - if (random_access) { - LoadRandomAccess(file_handle); - } else { - Reload(file_handle); - has_seeked = true; + while (true) { + if (!IsInMemory()) { + // We have to reload it from disk + block = nullptr; + if (random_access) { + LoadRandomAccess(file_handle); + } else { + Reload(file_handle); + has_seeked = true; + } + } + auto pinned = CreatePin(); + if (pinned->handle.IsValid()) { + return pinned; } + // the block is destroyable and was evicted between the in-memory check and the pin - reload it + block = nullptr; } - return CreatePin(); } bool CSVBuffer::TryPin(shared_ptr &pinned) { diff --git a/src/duckdb/src/execution/operator/csv_scanner/encode/csv_encoder.cpp b/src/duckdb/src/execution/operator/csv_scanner/encode/csv_encoder.cpp index 7184daeb7..7e5207277 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/encode/csv_encoder.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/encode/csv_encoder.cpp @@ -68,11 +68,15 @@ CSVEncoder::CSVEncoder(ClientContext &context_p, const string &encoding_name_to_ throw InvalidInputException(error.str()); } + encoding_function = function; + if (encoding_name == "utf-8") { + // UTF-8 is read straight into the CSV buffers (see CSVFileHandle::Read), the encoder buffers are never used + return; + } // The encoded buffer is even for the two byte lookup of utf-16, and fits a full lookup plus the probe byte const idx_t lookup_bytes = function->GetLookupBytes(); encoded_buffer.Initialize(MaxValue(buffer_size - buffer_size % 2, lookup_bytes + lookup_bytes % 2)); remaining_bytes_buffer.Initialize(function->GetBytesPerIteration()); - encoding_function = function; } void CSVEncoder::Reset() { diff --git a/src/duckdb/src/execution/operator/csv_scanner/table_function/csv_file_scanner.cpp b/src/duckdb/src/execution/operator/csv_scanner/table_function/csv_file_scanner.cpp index 7cd367d73..794f43b8d 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/table_function/csv_file_scanner.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/table_function/csv_file_scanner.cpp @@ -11,7 +11,8 @@ CSVFileScan::CSVFileScan(ClientContext &context, const OpenFileInfo &file_p, CSV const vector &types, CSVSchema &file_schema, bool per_file_single_threaded, shared_ptr buffer_manager_p, bool fixed_schema) : file(file_p), buffer_manager(std::move(buffer_manager_p)), - error_handler(make_shared_ptr(options_p.ignore_errors.GetValue())), + error_handler( + make_shared_ptr(options_p.ignore_errors.GetValue(), options_p.store_rejects.GetValue())), options(std::move(options_p)) { // Initialize Buffer Manager if (!buffer_manager) { @@ -51,7 +52,8 @@ CSVFileScan::CSVFileScan(ClientContext &context, const OpenFileInfo &file_p, CSV CSVFileScan::CSVFileScan(ClientContext &context, const OpenFileInfo &file_p, const CSVReaderOptions &options_p, const MultiFileOptions &file_options) - : file(file_p), error_handler(make_shared_ptr(options_p.ignore_errors.GetValue())), + : file(file_p), error_handler(make_shared_ptr(options_p.ignore_errors.GetValue(), + options_p.store_rejects.GetValue())), options(options_p) { buffer_manager = CSVBufferManager::Open(context, options, file); // Initialize On Disk and Size of file diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_error.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_error.cpp index cdfbc1de6..aebc04f31 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_error.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_error.cpp @@ -18,7 +18,8 @@ LinesPerBoundary::LinesPerBoundary(idx_t boundary_idx_p, idx_t lines_in_batch_p) : boundary_idx(boundary_idx_p), lines_in_batch(lines_in_batch_p) { } -CSVErrorHandler::CSVErrorHandler(bool ignore_errors_p) : ignore_errors(ignore_errors_p) { +CSVErrorHandler::CSVErrorHandler(bool ignore_errors_p, bool store_ignored_errors_p) + : ignore_errors(ignore_errors_p), store_ignored_errors(store_ignored_errors_p) { } void CSVErrorHandler::ThrowError(const CSVError &csv_error) { @@ -62,8 +63,15 @@ void CSVErrorHandler::ThrowError(const CSVError &csv_error) { void CSVErrorHandler::Error(const CSVError &csv_error, bool force_error) { lock_guard parallel_lock(main_mutex); - if (!force_error && (ignore_errors || (PrintLineNumber(csv_error) && !CanGetLine(csv_error.GetBoundaryIndex())))) { - // We store this error, we can't throw it now, or we are ignoring it + if (!force_error && ignore_errors) { + // We are ignoring this error - it is only kept if something (e.g. the rejects table) will read it + if (store_ignored_errors) { + errors.push_back(csv_error); + } + return; + } + if (!force_error && PrintLineNumber(csv_error) && !CanGetLine(csv_error.GetBoundaryIndex())) { + // We store this error, we can't throw it now errors.push_back(csv_error); return; } diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp index 2b0cbb4f4..02de6895c 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp @@ -295,7 +295,11 @@ void CSVReaderOptions::SetReadOption(const Identifier &loption, const Value &val } else if (loption == "ignore_errors") { ignore_errors.Set(ParseBoolean(value, loption)); } else if (loption == "buffer_size") { - buffer_size_option.Set(NumericCast(ParseInteger(value, loption))); + auto buffer_size = ParseInteger(value, loption); + if (buffer_size < 0) { + throw InvalidInputException("Buffer Size option must be higher than 0"); + } + buffer_size_option.Set(NumericCast(buffer_size)); if (buffer_size_option == 0) { throw InvalidInputException("Buffer Size option must be higher than 0"); } diff --git a/src/duckdb/src/execution/operator/helper/physical_limit.cpp b/src/duckdb/src/execution/operator/helper/physical_limit.cpp index 2245a1c71..b3bbc6a9a 100644 --- a/src/duckdb/src/execution/operator/helper/physical_limit.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_limit.cpp @@ -81,30 +81,27 @@ void PhysicalLimit::SetInitialLimits(const BoundLimitNode &limit_val, const Boun } } +idx_t PhysicalLimit::GetLimitValue(const Value &val, idx_t null_value) { + if (val.IsNull()) { + return null_value; + } + auto value = val.GetValue(); + if (value > MAX_LIMIT_VALUE) { + throw BinderException("Max value %llu for LIMIT/OFFSET is %llu", value, MAX_LIMIT_VALUE); + } + return value; +} + bool PhysicalLimit::ComputeOffset(ExecutionContext &context, DataChunk &input, optional_idx &limit, optional_idx &offset, idx_t current_offset, idx_t &max_element, const BoundLimitNode &limit_val, const BoundLimitNode &offset_val) { if (!limit.IsValid()) { Value val = GetDelimiter(context, input, limit_val.GetValueExpression()); - if (!val.IsNull()) { - limit = val.GetValue(); - } else { - limit = MAX_LIMIT_VALUE; - } - if (limit.GetIndex() > MAX_LIMIT_VALUE) { - throw BinderException("Max value %lld for LIMIT/OFFSET is %lld", limit.GetIndex(), MAX_LIMIT_VALUE); - } + limit = GetLimitValue(val, MAX_LIMIT_VALUE); } if (!offset.IsValid()) { Value val = GetDelimiter(context, input, offset_val.GetValueExpression()); - if (!val.IsNull()) { - offset = val.GetValue(); - } else { - offset = 0; - } - if (offset.GetIndex() > MAX_LIMIT_VALUE) { - throw BinderException("Max value %lld for LIMIT/OFFSET is %lld", offset.GetIndex(), MAX_LIMIT_VALUE); - } + offset = GetLimitValue(val, 0); } max_element = limit.GetIndex() + offset.GetIndex(); if (limit == 0 || current_offset >= max_element) { @@ -257,9 +254,7 @@ Value PhysicalLimit::GetDelimiter(ExecutionContext &context, DataChunk &input, c // only evaluate the expression on the first row of the input DataChunk single_row_input; single_row_input.InitializeEmpty(input.GetTypes()); - for (idx_t c = 0; c < input.ColumnCount(); c++) { - ConstantVector::Reference(single_row_input.data[c], count_t(1), input.data[c], 0, input.size()); - } + single_row_input.Slice(input, 0, MinValue(input.size(), 1)); limit_executor.Execute(single_row_input, limit_chunk); auto limit_value = limit_chunk.GetValue(0, 0); return limit_value; diff --git a/src/duckdb/src/execution/operator/helper/physical_limit_percent.cpp b/src/duckdb/src/execution/operator/helper/physical_limit_percent.cpp index 73e70a89e..e18ad936f 100644 --- a/src/duckdb/src/execution/operator/helper/physical_limit_percent.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_limit_percent.cpp @@ -81,14 +81,7 @@ SinkResultType PhysicalLimitPercent::Sink(ExecutionContext &context, DataChunk & } if (!state.offset.IsValid()) { Value val = PhysicalLimit::GetDelimiter(context, chunk, offset_val.GetValueExpression()); - if (!val.IsNull()) { - offset = val.GetValue(); - } else { - offset = 0; - } - if (offset.GetIndex() > 1ULL << 62ULL) { - throw BinderException("Max value %lld for LIMIT/OFFSET is %lld", offset.GetIndex(), 1ULL << 62ULL); - } + offset = PhysicalLimit::GetLimitValue(val, 0); } if (!PhysicalLimit::HandleOffset(chunk, state.current_offset, offset.GetIndex(), NumericLimits::Maximum())) { diff --git a/src/duckdb/src/execution/operator/helper/physical_pragma.cpp b/src/duckdb/src/execution/operator/helper/physical_pragma.cpp index df48d7f8d..88d66e4f8 100644 --- a/src/duckdb/src/execution/operator/helper/physical_pragma.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_pragma.cpp @@ -5,7 +5,7 @@ namespace duckdb { SourceResultType PhysicalPragma::GetDataInternal(ExecutionContext &context, DataChunk &chunk, OperatorSourceInput &input) const { auto &client = context.client; - FunctionParameters parameters {info->parameters, info->named_parameters}; + FunctionParameters parameters {info->parameters, info->named_parameters, &info->function}; info->function.function(client, parameters); return SourceResultType::FINISHED; diff --git a/src/duckdb/src/execution/operator/helper/physical_reservoir_sample.cpp b/src/duckdb/src/execution/operator/helper/physical_reservoir_sample.cpp index e9b6c354f..f25139601 100644 --- a/src/duckdb/src/execution/operator/helper/physical_reservoir_sample.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_reservoir_sample.cpp @@ -35,7 +35,7 @@ class SampleGlobalSinkState : public GlobalSinkState { }; unique_ptr PhysicalReservoirSample::GetGlobalSinkState(ClientContext &context) const { - return make_uniq(Allocator::Get(context), *options); + return make_uniq(BufferAllocator::Get(context), *options); } SinkResultType PhysicalReservoirSample::Sink(ExecutionContext &context, DataChunk &chunk, @@ -45,7 +45,7 @@ SinkResultType PhysicalReservoirSample::Sink(ExecutionContext &context, DataChun lock_guard glock(global_state.lock); if (!global_state.sample) { // always gather full thread percentage - auto &allocator = Allocator::Get(context.client); + auto &allocator = BufferAllocator::Get(context.client); if (options->is_percentage) { double percentage = options->sample_size.GetValue(); if (percentage == 0) { diff --git a/src/duckdb/src/execution/operator/helper/physical_reset.cpp b/src/duckdb/src/execution/operator/helper/physical_reset.cpp index e94dc2243..b8a9866a3 100644 --- a/src/duckdb/src/execution/operator/helper/physical_reset.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_reset.cpp @@ -10,10 +10,13 @@ namespace duckdb { void PhysicalReset::ResetExtensionVariable(ExecutionContext &context, DBConfig &config, ExtensionOption &extension_option) const { - auto effective_scope = scope == SetScope::AUTOMATIC ? extension_option.default_scope : scope; + auto effective_scope = extension_option.ResolveScope(Identifier(name.ToStdString()), scope); if (extension_option.set_function) { extension_option.set_function(context.client, effective_scope, extension_option.default_value); } + if (extension_option.info) { + extension_option.info->OnSet(context.client, effective_scope, extension_option.default_value, true); + } if (effective_scope == SetScope::GLOBAL) { config.ResetOption(extension_option); } else { diff --git a/src/duckdb/src/execution/operator/helper/physical_set.cpp b/src/duckdb/src/execution/operator/helper/physical_set.cpp index 6e0f939c8..6833b124e 100644 --- a/src/duckdb/src/execution/operator/helper/physical_set.cpp +++ b/src/duckdb/src/execution/operator/helper/physical_set.cpp @@ -14,14 +14,21 @@ void PhysicalSet::SetGenericVariable(ClientContext &context, idx_t setting_index void PhysicalSet::SetExtensionVariable(ClientContext &context, ExtensionOption &extension_option, SetScope scope, const Value &value) { + SetExtensionVariable(context, Identifier(), extension_option, scope, value); +} + +void PhysicalSet::SetExtensionVariable(ClientContext &context, const Identifier &name, + ExtensionOption &extension_option, SetScope scope, const Value &value) { auto &target_type = extension_option.type; Value target_value = value.CastAs(context, target_type); + auto resolved_scope = extension_option.ResolveScope(name, scope); if (extension_option.set_function) { extension_option.set_function(context, scope, target_value); } - if (scope == SetScope::AUTOMATIC) { - scope = extension_option.default_scope; + if (extension_option.info) { + extension_option.info->OnSet(context, resolved_scope, target_value, false); } + scope = resolved_scope; auto setting_index = extension_option.setting_index.GetIndex(); SetGenericVariable(context, setting_index, scope, std::move(target_value)); } @@ -71,7 +78,7 @@ void PhysicalSet::SetVariable(ClientContext &context, const Identifier &name, Se throw InvalidInputException("Extension parameter %s was not found after autoloading", name); } } - SetExtensionVariable(context, extension_option, scope, value); + SetExtensionVariable(context, name, extension_option, scope, value); return; } SetScope variable_scope = GetSettingScope(*option, scope); diff --git a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp index 8eb26ce3f..d0db40702 100644 --- a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp @@ -3,6 +3,7 @@ #include "duckdb/logging/log_manager.hpp" #include "duckdb/common/assert.hpp" +#include "duckdb/common/once_flag.hpp" #include "duckdb/common/projection_index.hpp" #include "duckdb/common/radix_partitioning.hpp" #include "duckdb/common/types.hpp" @@ -14,6 +15,7 @@ #include "duckdb/execution/join_hashtable.hpp" #include "duckdb/execution/operator/aggregate/ungrouped_aggregate_state.hpp" #include "duckdb/execution/operator/join/perfect_hash_join_executor.hpp" +#include "duckdb/execution/operator/join/physical_delim_join.hpp" #include "duckdb/function/aggregate/distributive_function_utils.hpp" #include "duckdb/function/aggregate/distributive_functions.hpp" #include "duckdb/function/function_binder.hpp" @@ -310,6 +312,10 @@ static bool BuildSideHasMultipleSources(const PhysicalOperator &op) { case PhysicalOperatorType::RECURSIVE_RECURRING_CTE_SCAN: // CTE producers are not children of their scans and may forward different dictionaries. return true; + case PhysicalOperatorType::LEFT_DELIM_JOIN: + case PhysicalOperatorType::RIGHT_DELIM_JOIN: + // The output flows out of the wrapped join, which is not among the children. + return BuildSideHasMultipleSources(op.Cast().join); default: break; } @@ -328,18 +334,22 @@ static bool BuildSideHasMultipleSources(const PhysicalOperator &op) { //! Synchronisation state for the first-chunk publication of the TupleDataLayout: the canonical layout //! shared by all per-thread JHTs, plus the per-column decision on whether to narrow the row-store slot. struct LayoutGate { - mutex publish_mutex; - atomic published {false}; + //! Recreated by Reset(): the flag must be re-armed for the next recursive CTE iteration + unique_ptr publish_once = make_uniq(); shared_ptr layout_ptr; - vector dict_index_width; + //! Per narrowed build column: slot width, owned dictionary (+ NULL slot) and upstream dictionary id; shared by all + //! threads, nullptr when no column is narrowed + shared_ptr dict_info; void Reset() { - published.store(false, std::memory_order_release); + publish_once = make_uniq(); layout_ptr.reset(); - dict_index_width.clear(); + dict_info.reset(); } }; +static idx_t GetBuildSpaceRequirement(ClientContext &context, const JoinHashTable &ht, idx_t num_threads); + class HashJoinGlobalSinkState : public GlobalSinkState { public: HashJoinGlobalSinkState(const PhysicalHashJoin &op_p, ClientContext &context_p) @@ -348,6 +358,12 @@ class HashJoinGlobalSinkState : public GlobalSinkState { initial_radix_bits(num_threads < 100 ? 4 : 5), finalized(false), active_local_states(0), total_size(0), max_partition_size(0), max_partition_count(0), probe_side_requirement(0), scanned_data(false) { hash_table = op.InitializeHashTable(context, initial_radix_bits); + // Reserve the initial partition buffers before upstream hash tables are finalized. + auto minimum_reservation = MinValue(temporary_memory_state->GetMinimumReservation(), + GetBuildSpaceRequirement(context, *hash_table, num_threads)); + temporary_memory_state->SetMinimumReservation(minimum_reservation); + temporary_memory_state->SetRemainingSize(minimum_reservation); + temporary_memory_state->UpdateReservation(context); // For perfect hash join perfect_join_executor = make_uniq(op, *hash_table); @@ -415,8 +431,10 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } - // Keep the published layout across CTE iterations (same upstream operator, same arrival types). - // ResetForNewIterationSinglePartition already cleared the row data and dict_registry. + // The next iteration's first chunk publishes the layout afresh: the rerun build may deliver a different + // dictionary (or none), so neither a narrowed slot nor the dictionary it indexes into can be carried over. + // ResetForNewIterationSinglePartition returned the global JHT to the unpublished state accordingly. + layout_gate.Reset(); GlobalSinkState::Reset(context); } @@ -533,9 +551,10 @@ class HashJoinLocalSinkState : public LocalSinkState { join_keys.Reset(); payload_chunk.Reset(); if (hash_table && append_state_initialised) { - // the layout survives the iteration; only the row data is dropped + // Drop the row data and the layout: the next iteration's first chunk republishes it, and Sink then + // reruns the lazy per-thread setup (FinishInitWithLayout + InitializeAppendState) against it. hash_table->ResetForNewIterationSinglePartition(); - hash_table->GetSinkCollection().ResetAppendState(append_state); + append_state_initialised = false; } else { // HT was moved into gstate during Combine, or never had a layout published. Rebuild against the global // radix_bits so partition counts stay consistent in PrepareFinalize. @@ -552,27 +571,16 @@ class HashJoinLocalSinkState : public LocalSinkState { } }; -//! Map a dict-index byte width to its row-store slot type (the width is decided by GetDictSurvivingIndexWidth) -static LogicalType DictIndexType(uint8_t index_width) { - switch (index_width) { - case sizeof(uint8_t): - return LogicalType::UTINYINT; - case sizeof(uint16_t): - return LogicalType::USMALLINT; - default: - return LogicalType::UINTEGER; - } -} - //! Build the row layout [conditions, build payload, (found flag), hash], narrowing a payload column to its -//! dict-index slot when dict_index_width[col] != 0. Shared by publisher and empty-input fallback to avoid drift. +//! dict-index slot when dict_info narrows it. Shared by publisher and empty-input fallback to avoid drift. static shared_ptr BuildJoinLayout(const vector &cond_types, const vector &build_types, JoinType join_type, - const vector &dict_index_width) { + optional_ptr dict_info) { vector layout_types(cond_types); for (idx_t col = 0; col < build_types.size(); col++) { - if (col < dict_index_width.size() && dict_index_width[col] != 0) { - layout_types.emplace_back(DictIndexType(dict_index_width[col])); + auto dict_column = dict_info ? dict_info->Get(col) : nullptr; + if (dict_column) { + layout_types.emplace_back(DictSurvivingInfo::IndexType(dict_column->index_width)); } else { layout_types.emplace_back(build_types[col]); } @@ -592,8 +600,10 @@ static bool CanUseDictSurvivingJoin(const PhysicalHashJoin &op, const JoinHashTa bool build_side_multi_source) { // external is safe here: the dictionary is an in-memory self-owned copy and the index is a plain row-store // column, so a spill/repartition preserves both (unlike the pointer-embedding dict-emission/compressed-probe paths) - // a multi-source build can deliver a later chunk flat or as a different dictionary under the - // already-narrowed slot, so disqualify the whole join (see BuildSideHasMultipleSources) + // a multi-source build (UNION ALL, CTE) can deliver a later chunk flat or as a different dictionary under the + // already-narrowed slot, so disqualify the whole join (see BuildSideHasMultipleSources). A FULL/RIGHT join in + // the build subtree is fine: its second phase only ever delivers NULL-only probe columns, which the reserved + // NULL index of the owned dictionary encodes. if (build_side_multi_source) { return false; } @@ -622,50 +632,41 @@ static bool CanUseDictSurvivingJoin(const PhysicalHashJoin &op, const JoinHashTa } bool HashJoinGlobalSinkState::DictSurvivingActive() const { - if (!hash_table) { - return false; - } - for (const auto &entry : hash_table->dict_registry) { - if (entry) { - return true; - } - } - return false; + return hash_table && hash_table->HasDictSurvivingColumns(); } void HashJoinGlobalSinkState::PublishLayoutIfFirst(HashJoinLocalSinkState &lstate, DataChunk &payload_chunk) { - if (layout_gate.published.load(std::memory_order_acquire)) { - return; - } - unique_lock guard(layout_gate.publish_mutex); - if (layout_gate.published.load(std::memory_order_relaxed)) { - return; - } - - const auto &cond_types = lstate.hash_table->condition_types; - const auto &build_types = lstate.hash_table->build_types; - layout_gate.dict_index_width.assign(build_types.size(), 0); - - if (CanUseDictSurvivingJoin(op, *lstate.hash_table, can_use_perfect_hash, build_side_multi_source)) { - // Per-column width decision lives on the JHT (GetDictSurvivingIndexWidth); feed it each arriving vector. - for (idx_t col = 0; col < build_types.size(); col++) { - if (col >= payload_chunk.ColumnCount()) { - continue; + CallOnce(*layout_gate.publish_once, [&] { + const auto &cond_types = lstate.hash_table->condition_types; + const auto &build_types = lstate.hash_table->build_types; + + shared_ptr dict_info; + if (CanUseDictSurvivingJoin(op, *lstate.hash_table, can_use_perfect_hash, build_side_multi_source)) { + // Per-column width decision lives on the JHT (GetDictSurvivingIndexWidth); feed it each arriving vector. + // The dictionary a narrowed slot indexes into is created here, once, and handed to every thread with the + // layout: a later chunk may carry no dictionary at all (NULL-only), so no thread can pin it lazily. + DictSurvivingInfo info; + info.columns.resize(build_types.size()); + for (idx_t col = 0; col < MinValue(build_types.size(), payload_chunk.ColumnCount()); col++) { + auto &incoming = payload_chunk.data[col]; + const auto index_width = lstate.hash_table->GetDictSurvivingIndexWidth(col, incoming); + if (index_width != 0) { + info.columns[col] = JoinHashTable::CreateDictSurvivingColumn(incoming, index_width); + } + } + if (info.Any()) { + dict_info = make_shared_ptr(std::move(info)); } - layout_gate.dict_index_width[col] = - lstate.hash_table->GetDictSurvivingIndexWidth(col, payload_chunk.data[col]); } - } - - auto layout = BuildJoinLayout(cond_types, build_types, lstate.hash_table->join_type, layout_gate.dict_index_width); - layout_gate.layout_ptr = layout; - // global HT receives the same layout so Merge/Combine and finalize-time scans operate against it - if (hash_table && !hash_table->IsLayoutFinalized()) { - hash_table->FinishInitWithLayout(layout, layout_gate.dict_index_width); - } - - layout_gate.published.store(true, std::memory_order_release); + auto layout = BuildJoinLayout(cond_types, build_types, lstate.hash_table->join_type, dict_info.get()); + // global HT receives the same layout so Merge/Combine and finalize-time scans operate against it + if (hash_table && !hash_table->IsLayoutFinalized()) { + hash_table->FinishInitWithLayout(layout, dict_info); + } + layout_gate.layout_ptr = std::move(layout); + layout_gate.dict_info = std::move(dict_info); + }); } unique_ptr PhysicalHashJoin::InitializeHashTable(ClientContext &context, @@ -797,7 +798,7 @@ SinkResultType PhysicalHashJoin::Sink(ExecutionContext &context, DataChunk &chun // lazy per-thread setup against the published layout if (!lstate.append_state_initialised) { - lstate.hash_table->FinishInitWithLayout(gstate.layout_gate.layout_ptr, gstate.layout_gate.dict_index_width); + lstate.hash_table->FinishInitWithLayout(gstate.layout_gate.layout_ptr, gstate.layout_gate.dict_info); lstate.hash_table->GetSinkCollection().InitializeAppendState(lstate.append_state); lstate.append_state_initialised = true; } @@ -915,26 +916,26 @@ static idx_t GetPartitioningSpaceRequirement(ClientContext &context, const vecto return num_threads * num_partitions * size_per_partition; } +static idx_t GetBuildSpaceRequirement(ClientContext &context, const JoinHashTable &ht, idx_t num_threads) { + auto layout = BuildJoinLayout(ht.condition_types, ht.build_types, ht.join_type, {}); + return GetPartitioningSpaceRequirement(context, layout->GetTypes(), ht.GetRadixBits(), num_threads); +} + void PhysicalHashJoin::PrepareFinalize(ClientContext &context, GlobalSinkState &global_state) const { auto &gstate = global_state.Cast(); // If no Sink chunk ever arrived, the layout was never published. Fall back to a default layout // (all columns at their native width) so finalize-time scans can dereference data_collection. - if (!gstate.layout_gate.published.load(std::memory_order_acquire)) { - unique_lock guard(gstate.layout_gate.publish_mutex); - if (!gstate.layout_gate.published.load(std::memory_order_relaxed)) { - const auto &cond_types = gstate.hash_table->condition_types; - const auto &build_types = gstate.hash_table->build_types; - gstate.layout_gate.dict_index_width.assign(build_types.size(), 0); - // all-zero dict_index_width => BuildJoinLayout keeps every build column at its native width - auto layout = BuildJoinLayout(cond_types, build_types, gstate.hash_table->join_type, - gstate.layout_gate.dict_index_width); - gstate.layout_gate.layout_ptr = layout; - if (!gstate.hash_table->IsLayoutFinalized()) { - gstate.hash_table->FinishInitWithLayout(layout); - } - gstate.layout_gate.published.store(true, std::memory_order_release); - } - } + CallOnce(*gstate.layout_gate.publish_once, [&] { + const auto &cond_types = gstate.hash_table->condition_types; + const auto &build_types = gstate.hash_table->build_types; + // no dict_info => BuildJoinLayout keeps every build column at its native width + gstate.layout_gate.dict_info.reset(); + auto layout = BuildJoinLayout(cond_types, build_types, gstate.hash_table->join_type, nullptr); + if (!gstate.hash_table->IsLayoutFinalized()) { + gstate.hash_table->FinishInitWithLayout(layout); + } + gstate.layout_gate.layout_ptr = std::move(layout); + }); const auto &ht = *gstate.hash_table; gstate.total_size = @@ -962,26 +963,17 @@ struct PrefixRangeBuildLane { idx_t task_count = 0; }; -static void ExecuteHashJoinFinalizeTask(HashJoinGlobalSinkState &sink, optional_idx partition_idx, - optional_ptr prefix_range_lane) { - const auto &data_collection = sink.hash_table->GetDataCollection(); +static void ExecuteHashJoinFinalizeTask(HashJoinGlobalSinkState &sink, const vector> &chunk_ranges, + bool parallel, optional_ptr prefix_range_lane) { optional_ptr prefix_range_state; bool prefix_range_parallel = false; if (prefix_range_lane) { prefix_range_state = *prefix_range_lane->state; prefix_range_parallel = prefix_range_lane->task_count > 1; } - if (!partition_idx.IsValid() || sink.hash_table->GetRadixBits() == 0) { - // Unpartitioned builds still finalize over the full chunk range even if the scheduler created a - // single "partition 0" task, because tuple-data segments are not tagged with partition ids there. - sink.hash_table->Finalize(0U, data_collection.ChunkCount(), false, prefix_range_state, prefix_range_parallel); - } else { - // Parallel finalize - each thread processes one partition - const auto chunk_ranges = data_collection.GetChunkRangesForPartition(partition_idx.GetIndex()); - for (auto &chunk_range : chunk_ranges) { - sink.hash_table->Finalize(chunk_range.first, chunk_range.second, true, prefix_range_state, - prefix_range_parallel); - } + for (auto &chunk_range : chunk_ranges) { + sink.hash_table->Finalize(chunk_range.first, chunk_range.second, parallel, prefix_range_state, + prefix_range_parallel); } } @@ -1055,14 +1047,15 @@ class HashJoinTableInitEvent : public BasePipelineEvent { class HashJoinFinalizeTask : public ExecutorTask { public: - HashJoinFinalizeTask(HashJoinGlobalSinkState &sink_p, shared_ptr event, optional_idx partition_idx_p, + HashJoinFinalizeTask(HashJoinGlobalSinkState &sink_p, shared_ptr event, + vector> chunk_ranges_p, bool parallel_p, optional_ptr prefix_range_lane_p = nullptr) - : ExecutorTask(sink_p.context, std::move(event), sink_p.op), sink(sink_p), partition_idx(partition_idx_p), - prefix_range_lane(prefix_range_lane_p) { + : ExecutorTask(sink_p.context, std::move(event), sink_p.op), sink(sink_p), + chunk_ranges(std::move(chunk_ranges_p)), parallel(parallel_p), prefix_range_lane(prefix_range_lane_p) { } TaskExecutionResult ExecuteTask(TaskExecutionMode mode) override { - ExecuteHashJoinFinalizeTask(sink, partition_idx, prefix_range_lane); + ExecuteHashJoinFinalizeTask(sink, chunk_ranges, parallel, prefix_range_lane); event->FinishTask(); return TaskExecutionResult::TASK_FINISHED; } @@ -1072,7 +1065,8 @@ class HashJoinFinalizeTask : public ExecutorTask { private: HashJoinGlobalSinkState &sink; - optional_idx partition_idx; + vector> chunk_ranges; + bool parallel; optional_ptr prefix_range_lane; }; @@ -1087,31 +1081,35 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { public: vector> GetTasks() { auto &ht = *sink.hash_table; - vector finalize_partitions; - if (FinalizeSingleThreaded(sink, false)) { - finalize_partitions.emplace_back(); + auto &data_collection = ht.GetDataCollection(); + const bool parallel = !FinalizeSingleThreaded(sink, false) && ht.GetRadixBits() != 0; + vector>> task_ranges; + if (!parallel) { + task_ranges.push_back({{0, data_collection.ChunkCount()}}); } else { const auto num_partitions = RadixPartitioning::NumberOfPartitions(ht.GetRadixBits()); const auto ¤t_partitions = ht.GetCurrentPartitions(); + const auto max_tasks_per_partition = MaxValue(sink.num_threads / num_partitions, 1); for (idx_t partition_idx = 0; partition_idx < num_partitions; partition_idx++) { if (sink.external && !current_partitions.RowIsValidUnsafe(partition_idx)) { - continue; // Partition is not being built on + continue; } - finalize_partitions.emplace_back(partition_idx); + SplitPartitionRanges(data_collection.GetChunkRangesForPartition(partition_idx), max_tasks_per_partition, + task_ranges); } } - InitializePrefixRangeLanes(ht, finalize_partitions.size()); + InitializePrefixRangeLanes(ht, task_ranges.size()); vector> finalize_tasks; - finalize_tasks.reserve(finalize_partitions.size()); - for (idx_t task_idx = 0; task_idx < finalize_partitions.size(); task_idx++) { + finalize_tasks.reserve(task_ranges.size()); + for (idx_t task_idx = 0; task_idx < task_ranges.size(); task_idx++) { optional_ptr lane; if (!prefix_range_lanes.empty()) { lane = *prefix_range_lanes[task_idx % prefix_range_lanes.size()]; lane->task_count++; } - finalize_tasks.push_back( - make_uniq(sink, shared_from_this(), finalize_partitions[task_idx], lane)); + finalize_tasks.push_back(make_uniq(sink, shared_from_this(), + std::move(task_ranges[task_idx]), parallel, lane)); } return finalize_tasks; } @@ -1124,7 +1122,7 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { lane = *prefix_range_lanes[0]; lane->task_count = 1; } - ExecuteHashJoinFinalizeTask(sink, optional_idx(), lane); + ExecuteHashJoinFinalizeTask(sink, {{0, ht.GetDataCollection().ChunkCount()}}, false, lane); FinishTasks(); } @@ -1139,6 +1137,33 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { static constexpr idx_t CHUNKS_PER_TASK = 64; private: + static void SplitPartitionRanges(vector> ranges, idx_t max_tasks, + vector>> &task_ranges) { + if (max_tasks == 1) { + task_ranges.push_back(std::move(ranges)); + return; + } + idx_t partition_chunks = 0; + for (const auto &range : ranges) { + partition_chunks += range.second - range.first; + } + const auto chunks_per_task = MaxValue(CHUNKS_PER_TASK, (partition_chunks + max_tasks - 1) / max_tasks); + // A task can span disjoint ranges within the partition. + idx_t remaining = 0; + for (auto range : ranges) { + while (range.first < range.second) { + if (remaining == 0) { + task_ranges.emplace_back(); + remaining = chunks_per_task; + } + const auto count = MinValue(range.second - range.first, remaining); + task_ranges.back().emplace_back(range.first, range.first + count); + range.first += count; + remaining -= count; + } + } + } + void FinishTasks() { for (auto &lane : prefix_range_lanes) { sink.hash_table->MergePrefixRangeBuildState(*lane->state); @@ -1311,8 +1336,9 @@ bool JoinFilterPushdownInfo::CanUseInFilter(const ClientContext &context, option return ht && ht->Count() > 1 && ht->Count() <= dynamic_or_filter_threshold && cmp == ExpressionType::COMPARE_EQUAL; } -bool JoinFilterPushdownInfo::CanUseBloomFilter(const ClientContext &context, const PhysicalComparisonJoin &op, - const ExpressionType &cmp, optional_ptr ht) const { +bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &info, const ClientContext &context, + const PhysicalComparisonJoin &op, const ExpressionType &cmp, + optional_ptr ht) const { if (!ht) { return false; } @@ -1342,8 +1368,11 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const ClientContext &context, con if (build_count == 0) { build_count = ht->GetSinkCollection().Count(); } - const double build_to_probe_ratio = - static_cast(build_count) / static_cast(op.children[0].get().estimated_cardinality); + auto probe_cardinality = op.children[0].get().estimated_cardinality; + if (build_side_has_filter) { + probe_cardinality = MaxValue(info.estimated_scan_cardinality, probe_cardinality); + } + const double build_to_probe_ratio = static_cast(build_count) / static_cast(probe_cardinality); if (build_to_probe_ratio > BUILD_TO_PROBE_RATIO_THRESHOLD) { return false; } @@ -1359,9 +1388,10 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const ClientContext &context, con return true; } -bool JoinFilterPushdownInfo::CanUsePrefixRangeFilter(const ClientContext &context, const PhysicalComparisonJoin &op, - optional_ptr ht, const ExpressionType &cmp) const { - if (!CanUseBloomFilter(context, op, cmp, ht)) { +bool JoinFilterPushdownInfo::CanUsePrefixRangeFilter(const JoinFilterPushdownFilter &info, const ClientContext &context, + const PhysicalComparisonJoin &op, optional_ptr ht, + const ExpressionType &cmp) const { + if (!CanUseBloomFilter(info, context, op, cmp, ht)) { return false; } if (cmp != ExpressionType::COMPARE_EQUAL) { @@ -1785,7 +1815,7 @@ unique_ptr JoinFilterPushdownInfo::FinalizeFilters(ClientContext &con const auto can_compute_span = PrefixRangeFilter::TryComputeSpan(min_val_before_cast, max_val_before_cast, span); const auto can_emit_prf = allow_prefix_range_filters && can_emit_runtime_filters && gstate && - CanUsePrefixRangeFilter(context, op, ht, cmp) && can_compute_span; + CanUsePrefixRangeFilter(info, context, op, ht, cmp) && can_compute_span; bool pushed_in_filter = false; if (CanUseInFilter(context, ht, cmp)) { @@ -1820,7 +1850,7 @@ unique_ptr JoinFilterPushdownInfo::FinalizeFilters(ClientContext &con max_val, condition_type, reconstruct_filter_expression, false); } if (allow_bloom_filters && can_emit_runtime_filters && ht && gstate && - CanUseBloomFilter(context, op, cmp, ht)) { + CanUseBloomFilter(info, context, op, cmp, ht)) { ht->SetBuildBloomFilter(true); DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, filter_col_idx, *gstate); @@ -1899,7 +1929,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl sink.owned_local_hash_tables.clear(); if (filter_pushdown && !sink.skip_filter_pushdown && ht.GetSinkCollection().Count() > 0) { auto filter_min_max = filter_pushdown->FinalizeMinMax(*sink.global_filter_state); - filter_pushdown->FinalizeFilters(context, *this, std::move(filter_min_max), &ht, true, false, + filter_pushdown->FinalizeFilters(context, *this, std::move(filter_min_max), &ht, false, false, sink.global_filter_state.get()); } ht.PrepareBloomFilterForFinalize(); @@ -2212,6 +2242,8 @@ class HashJoinGlobalSourceState : public GlobalSourceState { //! For probe synchronization atomic probe_chunk_count; atomic probe_chunk_done; + //! Last reported progress, also used while partition masks are being updated + MonotonicProgress external_progress; //! To determine the number of threads idx_t probe_count; @@ -2236,6 +2268,7 @@ class HashJoinGlobalSourceState : public GlobalSourceState { build_chunks_per_thread = DConstants::INVALID_INDEX; probe_chunk_count = 0; probe_chunk_done = 0; + external_progress.Reset(); probe_count = op.children[0].get().estimated_cardinality; parallel_scan_chunk_count = context.config.verify_parallelism ? 1 : 120; full_outer_chunk_idx = DConstants::INVALID_INDEX; @@ -2471,7 +2504,7 @@ void HashJoinGlobalSourceState::PrepareScanHT(HashJoinGlobalSinkState &sink) { bool HashJoinGlobalSourceState::AssignTask(HashJoinGlobalSinkState &sink, HashJoinLocalSourceState &lstate) { D_ASSERT(lstate.TaskFinished()); - annotated_lock_guard guard(lock); + annotated_unique_lock guard(lock); switch (global_stage.load()) { case HashJoinSourceStage::BUILD: if (build_chunk_idx != build_chunk_count) { @@ -2488,7 +2521,10 @@ bool HashJoinGlobalSourceState::AssignTask(HashJoinGlobalSinkState &sink, HashJo lstate.empty_ht_probe_in_progress = false; return true; } - break; + guard.unlock(); + // Only touch local handles after unlocking: another thread may replace the consumer. + lstate.probe_local_scan.current_chunk_state.handles.clear(); + return false; case HashJoinSourceStage::SCAN_HT: if (full_outer_chunk_idx != full_outer_chunk_count) { lstate.local_stage = global_stage; @@ -2705,10 +2741,15 @@ ProgressData PhysicalHashJoin::GetProgress(ClientContext &context, GlobalSourceS } const auto &ht = *sink.hash_table; - const auto num_partitions = static_cast(RadixPartitioning::NumberOfPartitions(ht.GetRadixBits())); + res.total = static_cast(RadixPartitioning::NumberOfPartitions(ht.GetRadixBits())); + + // Progress holds the executor lock, which workers may acquire while holding this lock. + if (!gstate.lock.try_lock()) { + return gstate.external_progress.Update(res); + } + annotated_lock_guard guard(gstate.lock, std::adopt_lock); res.done = static_cast(ht.FinishedPartitionCount()); - res.total = num_partitions; const auto probe_chunk_done = static_cast(gstate.probe_chunk_done); const auto probe_chunk_count = static_cast(gstate.probe_chunk_count); @@ -2721,7 +2762,7 @@ ProgressData PhysicalHashJoin::GetProgress(ClientContext &context, GlobalSourceS res.done += probe_progress; } - return res; + return gstate.external_progress.Update(res); } InsertionOrderPreservingMap PhysicalHashJoin::ParamsToString() const { diff --git a/src/duckdb/src/execution/operator/join/physical_nested_loop_join.cpp b/src/duckdb/src/execution/operator/join/physical_nested_loop_join.cpp index 4797ca49c..9eea64017 100644 --- a/src/duckdb/src/execution/operator/join/physical_nested_loop_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_nested_loop_join.cpp @@ -134,6 +134,7 @@ bool PhysicalNestedLoopJoin::IsSupported(const vector &conditions if (join_type == JoinType::MARK) { return true; } + idx_t comparison_count = 0; for (auto &cond : conditions) { if (!cond.IsComparison()) { continue; @@ -143,18 +144,17 @@ bool PhysicalNestedLoopJoin::IsSupported(const vector &conditions cond.GetLHS().GetReturnType().InternalType() == PhysicalType::ARRAY) { return false; } + comparison_count++; + } + if (comparison_count == 0) { + // the other conditions are only evaluated on the pairs that match a comparison + return false; } // To avoid situations like https://github.com/duckdb/duckdb/issues/10046 // If there is an equality in the conditions, a hash join is planned // with one condition, we can use mark join logic, otherwise we should use physical blockwise nl join if (join_type == JoinType::SEMI || join_type == JoinType::ANTI) { - idx_t comparison_count = 0; - for (auto &cond : conditions) { - if (cond.IsComparison()) { - comparison_count++; - } - } return comparison_count == 1; } return true; diff --git a/src/duckdb/src/execution/operator/order/physical_top_n.cpp b/src/duckdb/src/execution/operator/order/physical_top_n.cpp index ef449579c..41cb651eb 100644 --- a/src/duckdb/src/execution/operator/order/physical_top_n.cpp +++ b/src/duckdb/src/execution/operator/order/physical_top_n.cpp @@ -431,7 +431,7 @@ void TopNHeap::Reduce() { return; } // we have too many values in the heap - reduce them - StringHeap new_sort_heap; + StringHeap new_sort_heap(allocator); DataChunk new_heap_data; new_heap_data.Initialize(allocator, payload_types, heap.size()); diff --git a/src/duckdb/src/execution/operator/persistent/physical_batch_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_batch_copy_to_file.cpp index 4f17c3937..3ca9f6a95 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_batch_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_batch_copy_to_file.cpp @@ -87,8 +87,8 @@ class FixedBatchCopyGlobalState : public GlobalSinkState { public: explicit FixedBatchCopyGlobalState(ClientContext &context_p, idx_t minimum_memory_per_thread) - : output_lifecycle(context_p), memory_manager(context_p, minimum_memory_per_thread), initialized(false), - rows_copied(0), scheduled_batch_index(0), flushed_batch_index(0), any_flushing(false), any_finished(false), + : output_lifecycle(context_p), memory_manager(context_p, minimum_memory_per_thread), rows_copied(0), + scheduled_batch_index(0), flushed_batch_index(0), any_flushing(false), any_finished(false), minimum_memory_per_thread(minimum_memory_per_thread) { } @@ -98,8 +98,7 @@ class FixedBatchCopyGlobalState : public GlobalSinkState { BatchTaskManager task_manager; mutex lock; mutex flush_lock; - //! Whether or not the copy has been initialized - atomic initialized; + atomic initialized {false}; //! The total number of rows copied to the file atomic rows_copied; //! Global copy state @@ -122,26 +121,28 @@ class FixedBatchCopyGlobalState : public GlobalSinkState { unique_ptr written_file_info; void Initialize(ClientContext &context, const PhysicalBatchCopyToFile &op) { - if (initialized) { + if (initialized.load(std::memory_order_acquire)) { return; } lock_guard guard(lock); - if (initialized) { + if (initialized.load(std::memory_order_relaxed)) { return; } - // initialize writing to the file output_lifecycle.RegisterFile(op.file_path); - global_state = op.function.copy_to_initialize_global(context, *op.bind_data, op.file_path); + auto new_global_state = op.function.copy_to_initialize_global(context, *op.bind_data, op.file_path); if (op.function.initialize_operator) { - op.function.initialize_operator(*global_state, op); + op.function.initialize_operator(*new_global_state, op); } + unique_ptr new_written_file_info; if (op.return_type == CopyFunctionReturnType::WRITTEN_FILE_STATISTICS) { - written_file_info = make_uniq(op.file_path); - written_file_info->file_stats = make_uniq(); - op.function.copy_to_get_written_statistics(context, *op.bind_data, *global_state, - *written_file_info->file_stats); + new_written_file_info = make_uniq(op.file_path); + new_written_file_info->file_stats = make_uniq(); + op.function.copy_to_get_written_statistics(context, *op.bind_data, *new_global_state, + *new_written_file_info->file_stats); } - initialized = true; + global_state = std::move(new_global_state); + written_file_info = std::move(new_written_file_info); + initialized.store(true, std::memory_order_release); } void AddBatchData(idx_t batch_index, unique_ptr new_batch, idx_t memory_usage, diff --git a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp index 263cca686..ebbb8a93c 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp @@ -2148,9 +2148,10 @@ optional PartitionedCopyHashGroup::TryNextBatchTask() { if (batch_size_bytes.IsValid()) { const auto partition_end = batch_row_idx; batch_row_idx = row_before; - do { + // the next partition can start right here, then this batch has no rows to count + while (batch_row_idx < partition_end && current_batch_size_bytes < batch_size_bytes.GetIndex()) { current_batch_size_bytes += row_sizes[batch_row_idx++]; - } while (batch_row_idx < partition_end && current_batch_size_bytes < batch_size_bytes.GetIndex()); + } found_next_partition = found_next_partition && batch_row_idx == partition_end; } else { auto &segment = segments[batch_scan_state.segment_index]; @@ -3911,6 +3912,11 @@ bool PhysicalCopyToFile::Rotate() const { static bool PhysicalCopyRotateNow(const PhysicalCopyToFile &op, GlobalFileState &global_state) DUCKDB_REQUIRES(global_state.lock) { + if (global_state.num_batches == 0) { + // never rotate a file that holds no data yet - e.g. a CSV header or a Parquet header can exceed the + // file size limit by itself, and rotating would then create new empty files forever + return false; + } if (op.file_size_bytes.IsValid()) { return op.function.file_size_bytes(*global_state.data) >= op.file_size_bytes.GetIndex(); } diff --git a/src/duckdb/src/execution/operator/persistent/physical_delete.cpp b/src/duckdb/src/execution/operator/persistent/physical_delete.cpp index 317301f9f..366e10da9 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_delete.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_delete.cpp @@ -8,6 +8,7 @@ #include "duckdb/common/vector_size.hpp" #include "duckdb/execution/expression_executor.hpp" #include "duckdb/execution/index/bound_index.hpp" +#include "duckdb/execution/row_id_deduplicator.hpp" #include "duckdb/storage/data_table.hpp" #include "duckdb/storage/table/delete_state.hpp" #include "duckdb/storage/table/scan_state.hpp" @@ -30,7 +31,7 @@ PhysicalDelete::PhysicalDelete(PhysicalPlan &physical_plan, vector class DeleteGlobalState : public GlobalSinkState { public: explicit DeleteGlobalState(ClientContext &context, const vector &return_types, DuckTableEntry &table, - const vector> &bound_constraints) + const vector> &bound_constraints, bool return_chunk) : deleted_count(0), return_collection(context, return_types), has_unique_indexes(false) { // We need to append deletes to the local delete-ART. auto &storage = table.GetStorage(); @@ -38,17 +39,20 @@ class DeleteGlobalState : public GlobalSinkState { storage.InitializeLocalStorage(delete_index_append_state, table, context, bound_constraints); has_unique_indexes = true; } + if (return_chunk) { + deleted_rows = make_uniq(context, vector {LogicalType::ROW_TYPE}); + } } - //! Protects deleted_row_ids and return_collection Combine() + //! Protects deleted_rows and return_collection Combine() annotated_mutex return_lock; //! Conservatively protect delete-index maintenance (unique indexes) annotated_mutex index_lock; atomic deleted_count; ColumnDataCollection return_collection; - //! Global set of deleted row_ids (for cross-thread dedup in Sink; only accessed under return_lock) - unordered_set deleted_row_ids DUCKDB_GUARDED_BY(return_lock); + //! Global deleted row_ids (for cross-thread dedup in Sink; only accessed under return_lock) + unique_ptr deleted_rows DUCKDB_GUARDED_BY(return_lock); LocalAppendState delete_index_append_state; bool has_unique_indexes; }; @@ -70,9 +74,10 @@ class DeleteLocalState : public LocalSinkState { if (return_chunk) { // Collect RETURNING rows per-thread, merge into global in Combine() return_collection = make_uniq(context, return_types); - // Per-thread set of deleted row_ids (filters intra-thread duplicates lock-free) - deleted_row_ids = make_uniq>(); - // Reusable buffer for global-pass selection to avoid per-chunk allocation + // Per-thread deleted row_ids (filters intra-thread duplicates lock-free) + deleted_rows = make_uniq(context, vector {LogicalType::ROW_TYPE}); + // Reusable buffers for the global pass to avoid per-chunk allocation + global_sel.Initialize(STANDARD_VECTOR_SIZE); final_sel.Initialize(STANDARD_VECTOR_SIZE); } } @@ -81,8 +86,9 @@ class DeleteLocalState : public LocalSinkState { DataChunk delete_chunk; unique_ptr delete_state; unique_ptr return_collection; - unique_ptr> deleted_row_ids; - //! Reusable selection for global row_id pass (avoids allocation in Sink hot path) + unique_ptr deleted_rows; + //! Reusable selections for the global row_id pass (avoids allocation in Sink hot path) + SelectionVector global_sel; SelectionVector final_sel; }; @@ -107,45 +113,34 @@ SinkResultType PhysicalDelete::Sink(ExecutionContext &context, DataChunk &chunk, idx_t delete_count = chunk.size(); Vector delete_row_ids(Vector::Ref(row_ids)); - // If RETURNING is enabled, deduplicate row_ids: per-thread set (lock-free) then global set (batched lock). + // If RETURNING is enabled, deduplicate row_ids: per-thread pass (lock-free) then global pass (batched lock). + // Both passes use buffer-managed hash tables so the set of deleted row ids counts against the memory limit. if (return_chunk) { - D_ASSERT(l_state.deleted_row_ids); - auto flat_ids = FlatVector::GetData(row_ids); - idx_t count = 0; - for (idx_t i = 0; i < chunk.size(); i++) { - auto row_id = flat_ids[i]; - if (l_state.deleted_row_ids->insert(row_id).second) { - delete_sel.set_index(count++, i); - } - } + D_ASSERT(l_state.deleted_rows); + idx_t count = l_state.deleted_rows->Register(row_ids, chunk.size(), delete_sel); if (count == 0) { return SinkResultType::NEED_MORE_INPUT; } + // the global pass only sees the rows that are new to this thread + Vector local_new_row_ids(Vector::Ref(row_ids)); + local_new_row_ids.Slice(row_ids, delete_sel, count); { annotated_lock_guard guard(g_state.return_lock); - idx_t final_count = 0; - unique_ptr large_sel; - SelectionVector *write_sel = (count <= STANDARD_VECTOR_SIZE) ? &l_state.final_sel : nullptr; - if (!write_sel) { - large_sel = make_uniq(chunk.size()); - write_sel = large_sel.get(); - } - for (idx_t i = 0; i < count; i++) { - auto orig_idx = delete_sel[i]; - auto row_id = flat_ids[orig_idx]; - if (g_state.deleted_row_ids.insert(row_id).second) { - write_sel->set_index(final_count++, orig_idx); - } + D_ASSERT(g_state.deleted_rows); + if (l_state.global_sel.Capacity() < count) { + l_state.global_sel.Initialize(count); + l_state.final_sel.Initialize(count); } + auto final_count = g_state.deleted_rows->Register(local_new_row_ids, count, l_state.global_sel); if (final_count == 0) { return SinkResultType::NEED_MORE_INPUT; } - delete_count = final_count; - if (write_sel == &l_state.final_sel) { - delete_sel.Initialize(l_state.final_sel.data(), l_state.final_sel.Capacity()); - } else { - delete_sel = std::move(*large_sel); + // map the selection over the locally new rows back to the input chunk + for (idx_t i = 0; i < final_count; i++) { + l_state.final_sel.set_index(i, delete_sel.get_index(l_state.global_sel.get_index(i))); } + delete_count = final_count; + delete_sel.Initialize(l_state.final_sel.data(), l_state.final_sel.Capacity()); } if (delete_count != chunk.size()) { delete_row_ids.Slice(row_ids, delete_sel, delete_count); @@ -252,14 +247,14 @@ SinkCombineResultType PhysicalDelete::Combine(ExecutionContext &, OperatorSinkCo if (!l_state.return_collection || l_state.return_collection->Count() == 0) { return SinkCombineResultType::FINISHED; } - // Merge per-thread return_collection into global (deleted_row_ids is only needed during Sink) + // Merge per-thread return_collection into global (deleted_rows is only needed during Sink) annotated_lock_guard guard(g_state.return_lock); g_state.return_collection.Combine(*l_state.return_collection); return SinkCombineResultType::FINISHED; } unique_ptr PhysicalDelete::GetGlobalSinkState(ClientContext &context) const { - return make_uniq(context, GetTypes(), tableref, bound_constraints); + return make_uniq(context, GetTypes(), tableref, bound_constraints, return_chunk); } unique_ptr PhysicalDelete::GetLocalSinkState(ExecutionContext &context) const { diff --git a/src/duckdb/src/execution/operator/scan/physical_column_data_scan.cpp b/src/duckdb/src/execution/operator/scan/physical_column_data_scan.cpp index 5608ca4b6..eb6d228f5 100644 --- a/src/duckdb/src/execution/operator/scan/physical_column_data_scan.cpp +++ b/src/duckdb/src/execution/operator/scan/physical_column_data_scan.cpp @@ -3,6 +3,7 @@ #include "duckdb/logging/log_type.hpp" #include "duckdb/logging/logger.hpp" +#include "duckdb/common/atomic.hpp" #include "duckdb/common/types/column/column_data_collection.hpp" #include "duckdb/common/types/column/column_data_collection_segment.hpp" #include "duckdb/execution/operator/aggregate/physical_hash_aggregate.hpp" @@ -70,27 +71,6 @@ PhysicalColumnDataScan::PhysicalColumnDataScan(PhysicalPlan &physical_plan, vect column_ids(GenerateColumnDataColumnIds(this->types)), cte_index(cte_index) { } -class PhysicalColumnDataGlobalScanState : public GlobalSourceState { -public: - PhysicalColumnDataGlobalScanState(ClientContext &context, const ColumnDataCollection &collection, - const vector &column_ids, const OperatorPartitionInfo &partition_info) - : batch_size(GetColumnDataScanBatchSize(context, collection.Count(), partition_info)), - max_threads(ColumnDataScanBatchCount(collection.Count(), batch_size)) { - collection.InitializeScan(global_scan_state, column_ids); - } - - idx_t MaxThreads() override { - return max_threads; - } - -public: - ColumnDataParallelScanState global_scan_state; - - const idx_t batch_size; - const idx_t max_threads; - idx_t next_batch_index = 0; -}; - struct ColumnDataScanEntry { ColumnDataScanEntry(idx_t chunk_index_p, idx_t segment_index_p, idx_t row_index_p) : chunk_index(chunk_index_p), segment_index(segment_index_p), row_index(row_index_p) { @@ -101,36 +81,63 @@ struct ColumnDataScanEntry { idx_t row_index; }; -class PhysicalColumnDataLocalScanState : public LocalSourceState { +class PhysicalColumnDataGlobalScanState : public GlobalSourceState { public: - bool AssignTask(const ColumnDataCollection &collection, PhysicalColumnDataGlobalScanState &gstate) { - entries.clear(); - entry_index = 0; - batch_index = DConstants::INVALID_INDEX; + PhysicalColumnDataGlobalScanState(ClientContext &context, const ColumnDataCollection &collection, + const vector &column_ids, const OperatorPartitionInfo &partition_info) { + collection.InitializeScan(global_scan_state, column_ids); + auto batch_size = GetColumnDataScanBatchSize(context, collection.Count(), partition_info); + auto chunk_count = collection.ChunkCount(); + scan_entries.reserve(chunk_count); + batch_offsets.reserve(MinValue(chunk_count, ColumnDataScanBatchCount(collection.Count(), batch_size)) + 1); + batch_offsets.push_back(0); - idx_t task_count = 0; - lock_guard l(gstate.global_scan_state.lock); - while (task_count < gstate.batch_size) { - idx_t chunk_index; - idx_t segment_index; - idx_t row_index; - if (!collection.NextScanIndex(gstate.global_scan_state.scan_state, chunk_index, segment_index, row_index)) { - break; + idx_t batch_rows = 0; + idx_t chunk_index; + idx_t segment_index; + idx_t row_index; + while (collection.NextScanIndex(global_scan_state.scan_state, chunk_index, segment_index, row_index)) { + scan_entries.emplace_back(chunk_index, segment_index, row_index); + batch_rows += collection.GetSegments()[segment_index]->chunk_data[chunk_index].count; + if (batch_rows >= batch_size) { + batch_offsets.push_back(scan_entries.size()); + batch_rows = 0; } - entries.emplace_back(chunk_index, segment_index, row_index); - task_count += collection.GetSegments()[segment_index]->chunk_data[chunk_index].count; } + if (batch_offsets.back() != scan_entries.size()) { + batch_offsets.push_back(scan_entries.size()); + } + } + + idx_t MaxThreads() override { + return MaxValue(batch_offsets.size() - 1, 1); + } - if (entries.empty()) { +public: + ColumnDataParallelScanState global_scan_state; + vector scan_entries; + //! Batch boundaries in scan_entries, including the end sentinel. + vector batch_offsets; + atomic next_batch_index {0}; +}; + +class PhysicalColumnDataLocalScanState : public LocalSourceState { +public: + bool AssignTask(PhysicalColumnDataGlobalScanState &gstate) { + // Metadata is immutable; the atomic ticket assigns exclusive ownership of an ordered batch. + batch_index = gstate.next_batch_index.fetch_add(1, std::memory_order_relaxed); + if (batch_index >= gstate.batch_offsets.size() - 1) { + batch_index = DConstants::INVALID_INDEX; return false; } - batch_index = gstate.next_batch_index++; + entry_index = gstate.batch_offsets[batch_index]; + entry_end = gstate.batch_offsets[batch_index + 1]; return true; } ColumnDataLocalScanState local_scan_state; - vector entries; idx_t entry_index = 0; + idx_t entry_end = 0; idx_t batch_index = DConstants::INVALID_INDEX; }; @@ -159,12 +166,12 @@ SourceResultType PhysicalColumnDataScan::GetDataInternal(ExecutionContext &conte OperatorSourceInput &input) const { auto &gstate = input.global_state.Cast(); auto &lstate = input.local_state.Cast(); - if (lstate.entry_index >= lstate.entries.size() && !lstate.AssignTask(*collection, gstate)) { + if (lstate.entry_index >= lstate.entry_end && !lstate.AssignTask(gstate)) { chunk.Reset(); return SourceResultType::FINISHED; } - auto &entry = lstate.entries[lstate.entry_index++]; + auto &entry = gstate.scan_entries[lstate.entry_index++]; if (column_ids.empty()) { chunk.Reset(); auto &chunk_data = collection->GetSegments()[entry.segment_index]->chunk_data[entry.chunk_index]; @@ -204,11 +211,13 @@ ProgressData PhysicalColumnDataScan::GetProgress(ClientContext &context, GlobalS return progress; } auto &state = gstate.Cast(); - lock_guard guard(state.global_scan_state.lock); - auto total = MaxValue(collection->Count(), 1); + auto next_batch = state.next_batch_index.load(std::memory_order_relaxed); + auto next_entry = + next_batch < state.batch_offsets.size() - 1 ? state.batch_offsets[next_batch] : state.scan_entries.size(); ProgressData progress; - progress.done = double(MinValue(state.global_scan_state.scan_state.next_row_index, total)); - progress.total = double(total); + progress.done = + double(next_entry < state.scan_entries.size() ? state.scan_entries[next_entry].row_index : collection->Count()); + progress.total = double(MaxValue(collection->Count(), 1)); return progress; } diff --git a/src/duckdb/src/execution/radix_partitioned_hashtable.cpp b/src/duckdb/src/execution/radix_partitioned_hashtable.cpp index 926088701..61cec7ac5 100644 --- a/src/duckdb/src/execution/radix_partitioned_hashtable.cpp +++ b/src/duckdb/src/execution/radix_partitioned_hashtable.cpp @@ -217,23 +217,24 @@ idx_t RadixHTConfig::GetMaximumSinkRadixBits() const { } void RadixHTConfig::SetRadixBitsInternal(const idx_t radix_bits_p, bool external) { - if (sink_radix_bits > radix_bits_p || sink.any_combined) { + if ((!external && sink_radix_bits > radix_bits_p) || sink.any_combined) { return; } const annotated_lock_guard guard {sink.lock}; - if (sink_radix_bits > radix_bits_p || sink.any_combined) { + if ((!external && sink_radix_bits > radix_bits_p) || sink.any_combined) { return; } + const auto radix_bits = MaxValue(radix_bits_p, sink_radix_bits.load()); if (external) { - const auto partition_multiplier = RadixPartitioning::NumberOfPartitions(radix_bits_p) / - RadixPartitioning::NumberOfPartitions(sink_radix_bits); + const auto partition_multiplier = + RadixPartitioning::NumberOfPartitions(radix_bits) / RadixPartitioning::NumberOfPartitions(sink_radix_bits); sink.minimum_reservation = sink.minimum_reservation * partition_multiplier; sink.external = true; } - sink_radix_bits = radix_bits_p; + sink_radix_bits = radix_bits; } idx_t RadixHTConfig::InitialSinkRadixBits() const { @@ -1228,6 +1229,7 @@ SourceResultType RadixPartitionedHashTable::GetData(ExecutionContext &context, D sink.scan_pin_properties == TupleDataPinProperties::DESTROY_AFTER_DONE); if (gstate.finished) { + lstate.ht.reset(); return SourceResultType::FINISHED; } @@ -1283,6 +1285,7 @@ SourceResultType RadixPartitionedHashTable::GetData(ExecutionContext &context, D if (chunk.size() != 0) { return SourceResultType::HAVE_MORE_OUTPUT; } else { + lstate.ht.reset(); return SourceResultType::FINISHED; } } diff --git a/src/duckdb/src/execution/sample/reservoir_sample.cpp b/src/duckdb/src/execution/sample/reservoir_sample.cpp index b47fbd8b4..da5bf2da7 100644 --- a/src/duckdb/src/execution/sample/reservoir_sample.cpp +++ b/src/duckdb/src/execution/sample/reservoir_sample.cpp @@ -153,7 +153,7 @@ unique_ptr ReservoirSample::GetChunk() { unique_ptr ReservoirSample::CreateNewSampleChunk(vector &types, idx_t size) const { auto new_sample_chunk = make_uniq(); - new_sample_chunk->chunk.Initialize(Allocator::DefaultAllocator(), types, size); + new_sample_chunk->chunk.Initialize(allocator, types, size); // set the NULL columns correctly for (idx_t col_idx = 0; col_idx < types.size(); col_idx++) { @@ -190,7 +190,7 @@ void ReservoirSample::Vacuum() { } unique_ptr ReservoirSample::Copy() const { - auto ret = make_uniq(sample_count); + auto ret = make_uniq(allocator, sample_count); ret->stats_sample = stats_sample; ret->base_reservoir_sample = base_reservoir_sample->Copy(); @@ -739,7 +739,7 @@ void ReservoirSample::AddToReservoir(DataChunk &chunk) { for (idx_t i = 0; i < samples_remaining; i++) { input_sel.set_index(i, tuples_consumed + i); } - slice->Initialize(Allocator::DefaultAllocator(), types, samples_remaining); + slice->Initialize(allocator, types, samples_remaining); slice->Slice(chunk, input_sel, samples_remaining); AddToReservoir(*slice); return; diff --git a/src/duckdb/src/function/aggregate/distributive/minmax.cpp b/src/duckdb/src/function/aggregate/distributive/minmax.cpp index e17231bc4..eaca13d6e 100644 --- a/src/duckdb/src/function/aggregate/distributive/minmax.cpp +++ b/src/duckdb/src/function/aggregate/distributive/minmax.cpp @@ -356,6 +356,19 @@ shared_ptr GetCollatedMinMaxFunction(ClientContext &con return func->functions.GetFunctionByOffset(best_function.GetIndex()); } +//! Whether min/max over the type are computed through arg_min/arg_max over the collated value +static bool MinMaxUsesCollation(ClientContext &context, const LogicalType &input_type) { + // The generic non-VARCHAR collation path is not ready yet (see internal #8704). BIT, TIMETZ and VARIANT use + // explicit binary-comparable keys so min/max follows the same logical order as comparisons and ORDER BY. + const auto varchar_collation = + input_type.id() == LogicalTypeId::VARCHAR && + (!StringType::GetCollation(input_type).empty() || !Settings::Get(context).empty()); + const auto nested_collation = StructType::IsStruct(input_type) || input_type.id() == LogicalTypeId::LIST || + input_type.id() == LogicalTypeId::ARRAY; + return input_type.id() == LogicalTypeId::BIT || input_type.id() == LogicalTypeId::TIME_TZ || + input_type.id() == LogicalTypeId::VARIANT || varchar_collation || nested_collation; +} + template unique_ptr BindMinMax(BindAggregateFunctionInput &input) { auto &context = input.GetClientContext(); @@ -363,15 +376,7 @@ unique_ptr BindMinMax(BindAggregateFunctionInput &input) { auto &arguments = input.GetArguments(); auto input_type = arguments[0]->GetReturnType(); - // The generic non-VARCHAR collation path is not ready yet (see internal #8704). BIT and VARIANT use explicit - // binary-comparable keys so min/max follows the same logical order as comparisons and ORDER BY. - const auto varchar_collation = - input_type.id() == LogicalTypeId::VARCHAR && - (!StringType::GetCollation(input_type).empty() || !Settings::Get(context).empty()); - const auto nested_collation = StructType::IsStruct(input_type) || input_type.id() == LogicalTypeId::LIST || - input_type.id() == LogicalTypeId::ARRAY; - const auto collation = input_type.id() == LogicalTypeId::BIT || input_type.id() == LogicalTypeId::VARIANT || - varchar_collation || nested_collation; + const auto collation = MinMaxUsesCollation(context, input_type); auto collated_arg = collation ? arguments[0]->Copy() : nullptr; if (collation && ExpressionBinder::PushCollation(context, collated_arg, collated_arg->GetReturnType())) { // If aggr function is min/max and uses collations, replace bound_function with arg_min/arg_max @@ -566,7 +571,7 @@ unique_ptr MinMaxNBind(BindAggregateFunctionInput &input) { throw ParameterNotResolvedException(); } } - if (arguments[0]->GetReturnType().id() == LogicalTypeId::VARIANT) { + if (MinMaxUsesCollation(context, arguments[0]->GetReturnType())) { auto collated_arg = arguments[0]->Copy(); if (ExpressionBinder::PushCollation(context, collated_arg, collated_arg->GetReturnType())) { vector types {arguments[0]->GetReturnType(), collated_arg->GetReturnType(), diff --git a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp index a2e21e8ae..c36df8b1d 100644 --- a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp +++ b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp @@ -388,6 +388,35 @@ struct SortedAggregateFunction { AggregateStateInput state_input(aggr, bind_info); data_ptr_t agg_state_ptr = agg_state.data(); + // The inner aggregate (or the sort) can throw while its state is initialized - destroy it in that case + struct InnerStateGuard { + ~InnerStateGuard() { + if (live && destructor) { + try { + destructor(state_vec, input, 1); + } catch (...) { // NOLINT + } + } + } + aggregate_destructor_t destructor; + Vector &state_vec; + AggregateInputData &input; + bool live; + }; + InnerStateGuard inner_state_guard {destructor, agg_state_vec, aggr_bind_info, false}; + auto initialize_inner_state = [&]() { + initialize(state_input, &agg_state_ptr, 1); + inner_state_guard.live = true; + }; + auto finalize_inner_state = [&](idx_t result_idx) { + agg_state_vec.SetVectorType(states.GetVectorType()); + finalize(agg_state_vec, aggr_bind_info, result, 1, result_idx); + if (destructor) { + destructor(agg_state_vec, aggr_bind_info, 1); + } + inner_state_guard.live = false; + }; + auto sdata = states.Values(); vector state_unprocessed(count, 0); @@ -438,7 +467,7 @@ struct SortedAggregateFunction { auto global_source = sort->GetGlobalSourceState(client, *global_sink); auto local_source = sort->GetLocalSourceState(context, *global_source); - initialize(state_input, &agg_state_ptr, 1); + initialize_inner_state(); for (;;) { OperatorSourceInput source {*global_source, *local_source, interrupt}; scanned.Reset(); @@ -452,13 +481,8 @@ struct SortedAggregateFunction { // Find the next aggregate that needs data for (; !state_unprocessed[sorted]; ++sorted) { // Finalize a single value at the next offset - agg_state_vec.SetVectorType(states.GetVectorType()); - finalize(agg_state_vec, aggr_bind_info, result, 1, sorted + offset); - if (destructor) { - destructor(agg_state_vec, aggr_bind_info, 1); - } - - initialize(state_input, &agg_state_ptr, 1); + finalize_inner_state(sorted + offset); + initialize_inner_state(); } const auto input_count = MinValue(state_unprocessed[sorted], scanned.size() - consumed); for (column_t col_idx = 0; col_idx < scanned.ColumnCount(); ++col_idx) { @@ -484,11 +508,7 @@ struct SortedAggregateFunction { } // Finalize the last state for this sort - agg_state_vec.SetVectorType(states.GetVectorType()); - finalize(agg_state_vec, aggr_bind_info, result, 1, sorted + offset); - if (destructor) { - destructor(agg_state_vec, aggr_bind_info, 1); - } + finalize_inner_state(sorted + offset); ++sorted; // Stop if we are done @@ -503,15 +523,9 @@ struct SortedAggregateFunction { } for (; sorted < count; ++sorted) { - initialize(state_input, &agg_state_ptr, 1); - + initialize_inner_state(); // Finalize a single value at the next offset - agg_state_vec.SetVectorType(states.GetVectorType()); - finalize(agg_state_vec, aggr_bind_info, result, 1, sorted + offset); - - if (destructor) { - destructor(agg_state_vec, aggr_bind_info, 1); - } + finalize_inner_state(sorted + offset); } result.Verify(); diff --git a/src/duckdb/src/function/builtin_function_lookup.cpp b/src/duckdb/src/function/builtin_function_lookup.cpp index b65bb3dc6..6b38e38e7 100644 --- a/src/duckdb/src/function/builtin_function_lookup.cpp +++ b/src/duckdb/src/function/builtin_function_lookup.cpp @@ -54,8 +54,8 @@ shared_ptr GetBuiltinWindowFunction(ClientContext &context return entry.functions.GetFunctionByArguments(context, arguments); } -unique_ptr BindBuiltinScalarFunction(ClientContext &context, const Identifier &name, - vector> children) { +unique_ptr BindBuiltinScalarExpression(ClientContext &context, const Identifier &name, + vector> children) { auto &catalog = Catalog::GetSystemCatalog(context); auto &entry = catalog.GetEntry(context, BuiltinName(catalog, name)); @@ -73,6 +73,12 @@ unique_ptr BindBuiltinScalarFunction(ClientContext &con if (!expr) { error.Throw(); } + return expr; +} + +unique_ptr BindBuiltinScalarFunction(ClientContext &context, const Identifier &name, + vector> children) { + auto expr = BindBuiltinScalarExpression(context, name, std::move(children)); if (expr->GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) { throw InvalidInputException("BindBuiltinScalarFunction did not return a BoundFunctionExpression"); } diff --git a/src/duckdb/src/function/cast/array_casts.cpp b/src/duckdb/src/function/cast/array_casts.cpp index a115cc3e5..6e373fb6b 100644 --- a/src/duckdb/src/function/cast/array_casts.cpp +++ b/src/duckdb/src/function/cast/array_casts.cpp @@ -7,6 +7,7 @@ #include "duckdb/function/cast/default_casts.hpp" #include "duckdb/function/cast/bound_cast_data.hpp" #include "duckdb/common/operator/cast_operators.hpp" +#include "duckdb/function/cast/vector_cast_helpers.hpp" namespace duckdb { @@ -106,6 +107,14 @@ static bool ArrayToVarcharCast(Vector &source, Vector &result, idx_t count, Cast auto in_data = FlatVector::GetData(child); + // string elements are quoted and escaped where required, as for lists + auto child_is_nested = ArrayType::GetChildType(source.GetType()).IsNested(); + auto string_length_func = child_is_nested ? VectorCastHelpers::CalculateStringLength + : VectorCastHelpers::CalculateEscapedStringLength; + auto write_string_func = + child_is_nested ? VectorCastHelpers::WriteString : VectorCastHelpers::WriteEscapedString; + auto needs_quotes = make_unsafe_uniq_array_uninitialized(MaxValue(size, 1)); + static constexpr idx_t SEP_LENGTH = 2; static constexpr idx_t NULL_LENGTH = 4; @@ -124,7 +133,8 @@ static bool ArrayToVarcharCast(Vector &source, Vector &result, idx_t count, Cast if (j > 0) { array_varchar_length += SEP_LENGTH; } - array_varchar_length += child_validity.RowIsValid(elem_idx) ? elem.GetSize() : NULL_LENGTH; + array_varchar_length += + child_validity.RowIsValid(elem_idx) ? string_length_func(elem, needs_quotes[j]) : NULL_LENGTH; } auto &out_str = result_data.WriteEmptyString(array_varchar_length); @@ -141,9 +151,7 @@ static bool ArrayToVarcharCast(Vector &source, Vector &result, idx_t count, Cast offset += SEP_LENGTH; } if (child_validity.RowIsValid(elem_idx)) { - auto len = elem.GetSize(); - memcpy(dataptr + offset, elem.GetData(), len); - offset += len; + offset += write_string_func(dataptr + offset, elem, needs_quotes[j]); } else { memcpy(dataptr + offset, "NULL", NULL_LENGTH); offset += NULL_LENGTH; diff --git a/src/duckdb/src/function/cast/nested_to_varchar_cast.cpp b/src/duckdb/src/function/cast/nested_to_varchar_cast.cpp index 2af4cb569..af473fe05 100644 --- a/src/duckdb/src/function/cast/nested_to_varchar_cast.cpp +++ b/src/duckdb/src/function/cast/nested_to_varchar_cast.cpp @@ -20,7 +20,7 @@ const bool NestedToVarcharCast::LOOKUP_TABLE[256] = { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, // [ - false, + true, // backslash true, // ] false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, diff --git a/src/duckdb/src/function/cast/variant/from_variant.cpp b/src/duckdb/src/function/cast/variant/from_variant.cpp index c6f366b1b..a38820cbd 100644 --- a/src/duckdb/src/function/cast/variant/from_variant.cpp +++ b/src/duckdb/src/function/cast/variant/from_variant.cpp @@ -2,6 +2,8 @@ #include "duckdb/common/vector/constant_vector.hpp" #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/list_vector.hpp" +#include "duckdb/common/vector/map_vector.hpp" +#include "duckdb/common/error_data.hpp" #include "duckdb/common/vector/shredded_vector.hpp" #include "duckdb/common/vector/string_vector.hpp" #include "duckdb/common/vector/struct_vector.hpp" @@ -558,6 +560,51 @@ static bool CastVariantToJSON(FromVariantConversionData &conversion_data, Vector //! * @param offset The offset into the result where to write the converted values //! * @param count The amount of values we're converting //! * @param row The row of the Variant to pull data from, if 'IsValid()' is true +//! Maps cannot have NULL or duplicate keys - such rows are set to NULL, reporting the error +static bool VerifyConvertedMaps(FromVariantConversionData &conversion_data, Vector &result, idx_t offset, idx_t count) { + bool all_valid = true; + SelectionVector row_sel(1); + for (idx_t i = 0; i < count; i++) { + if (FlatVector::IsNull(result, offset + i)) { + continue; + } + row_sel.set_index(0, offset + i); + auto reason = MapVector::CheckMapValidity(result, 1, row_sel); + if (reason == MapInvalidReason::VALID) { + continue; + } + if (conversion_data.error.empty()) { + conversion_data.error = + reason == MapInvalidReason::DUPLICATE_KEY ? "Map keys must be unique." : "Map keys can not be NULL."; + } + FlatVector::SetNull(result, offset + i, true); + all_valid = false; + } + return all_valid; +} + +//! A VARIANT holds a MAP as a list of {key, value} structs - cast it to the entries of the target MAP +static optional TryCastVariantValueToMap(FromVariantConversionData &conversion_data, const Value &value, + const LogicalType &target_type) { + auto entry_type = ListType::GetChildType(target_type); + auto entries = conversion_data.TryCastAs(value, LogicalType::LIST(entry_type), nullptr, true); + if (!entries) { + return entries; + } + if (entries->IsNull()) { + return Value(target_type); + } + try { + return Value::MAP(entry_type, ListValue::GetChildren(*entries)); + } catch (const InvalidInputException &ex) { + // the entries do not form a valid map (e.g. duplicate keys) + if (conversion_data.error.empty()) { + conversion_data.error = ErrorData(ex).RawMessage(); + } + return optional(); + } +} + static bool CastVariant(FromVariantConversionData &conversion_data, Vector &result, const SelectionVector &sel, idx_t offset, idx_t count, optional_idx row) { auto &target_type = result.GetType(); @@ -583,11 +630,15 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu } break; case LogicalTypeId::LIST: - case LogicalTypeId::MAP: { if (ConvertVariantToList(conversion_data, result, sel, offset, count, row)) { return true; } break; + case LogicalTypeId::MAP: { + if (ConvertVariantToList(conversion_data, result, sel, offset, count, row)) { + return VerifyConvertedMaps(conversion_data, result, offset, count); + } + break; } case LogicalTypeId::UNION: { error = "Can't convert VARIANT"; @@ -616,7 +667,9 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu uint32_t value_index = sel[i]; auto value = VariantUtils::ConvertVariantToValue(conversion_data.variant, row_index, value_index); try { - auto cast_value = conversion_data.TryCastAs(value, target_type, nullptr, true); + auto cast_value = target_type.id() == LogicalTypeId::MAP + ? TryCastVariantValueToMap(conversion_data, value, target_type) + : conversion_data.TryCastAs(value, target_type, nullptr, true); if (!cast_value) { cast_value = Value(target_type); all_valid = false; @@ -629,6 +682,9 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu all_valid = false; } } + if (target_type.id() == LogicalTypeId::MAP) { + all_valid = VerifyConvertedMaps(conversion_data, result, offset, count) && all_valid; + } return all_valid; } else { EmptyConversionPayloadFromVariant empty_payload; diff --git a/src/duckdb/src/function/copy_blob.cpp b/src/duckdb/src/function/copy_blob.cpp index ba522e16e..0b4360c1c 100644 --- a/src/duckdb/src/function/copy_blob.cpp +++ b/src/duckdb/src/function/copy_blob.cpp @@ -43,6 +43,9 @@ unique_ptr WriteBlobBind(ClientContext &context, CopyFunctionBindI for (auto &[option_name, option_values] : input.info.options) { if (option_name == "compression") { + if (option_values.empty()) { + throw BinderException("COPY (FORMAT BLOB) option %s requires a value", SQLIdentifier(option_name)); + } auto compression_str = ParseStringOption(option_values[0], option_name); result->compression_type = FileCompressionType(compression_str); } else { diff --git a/src/duckdb/src/function/encoding_function.cpp b/src/duckdb/src/function/encoding_function.cpp index b32a612ce..392f86da3 100644 --- a/src/duckdb/src/function/encoding_function.cpp +++ b/src/duckdb/src/function/encoding_function.cpp @@ -21,6 +21,13 @@ void DecodeUTF16ToUTF8(CSVEncoderBuffer &encoded_buffer, char *target_buffer, id // We are done return; } + if (encoded_buffer.cur_pos + 1 >= encoded_buffer.actual_encoded_buffer_size) { + if (encoded_buffer.last_buffer) { + throw InvalidInputException("File is not utf-16 encoded"); + } + // the second byte is passed on to the next buffer + return; + } const uint16_t ch = static_cast(static_cast(encoded_ptr[encoded_buffer.cur_pos]) | (static_cast(encoded_ptr[encoded_buffer.cur_pos + 1]) << 8)); diff --git a/src/duckdb/src/function/function.cpp b/src/duckdb/src/function/function.cpp index 0e33a4b44..103cca8a4 100644 --- a/src/duckdb/src/function/function.cpp +++ b/src/duckdb/src/function/function.cpp @@ -462,6 +462,40 @@ idx_t BoundSimpleFunction::GetKwargsCount(const FunctionSignature &signature) co return result; } +void BoundSimpleFunction::SetCallArguments(const FunctionSignature &signature, const vector ¶meters, + const named_argument_map_t &named_parameters) { + const auto positional_count = signature.GetPositionalParameterCount(); + arguments.clear(); + for (idx_t i = 0; i < positional_count; i++) { + arguments.push_back(signature.GetParameter(i).GetType()); + } + auto args = signature.GetArgs(); + for (idx_t i = positional_count; i < parameters.size(); i++) { + auto is_typed = args && args->GetType().id() != LogicalTypeId::ANY; + arguments.push_back(is_typed ? args->GetType() : parameters[i].type()); + } + const auto positional_argument_count = arguments.size(); + + // only the options the signature receives by name, in the order they were passed + vector names; + for (auto &entry : named_parameters) { + auto param_idx = signature.GetParameterIndexByName(entry.first); + if (!param_idx.IsValid()) { + if (signature.GetKwargs()) { + names.push_back(entry.first); + arguments.push_back(entry.second.type()); + } + continue; + } + auto ¶m = signature.GetParameter(param_idx.GetIndex()); + if (!param.AcceptsPosition()) { + names.push_back(entry.first); + arguments.push_back(param.GetType()); + } + } + SetNamedArguments(positional_argument_count, std::move(names)); +} + FunctionParameterKind BoundSimpleFunction::GetArgumentParameterKind(const FunctionSignature &signature, idx_t argument_index) const { if (argument_index >= positional_arguments + named_arguments.size()) { diff --git a/src/duckdb/src/function/function_binder.cpp b/src/duckdb/src/function/function_binder.cpp index 35eab828e..25f9fc9fd 100644 --- a/src/duckdb/src/function/function_binder.cpp +++ b/src/duckdb/src/function/function_binder.cpp @@ -475,9 +475,25 @@ static LogicalTypeComparisonResult RequiresCast(const LogicalType &source_type, return LogicalTypeComparisonResult::DIFFERENT_TYPES; } +//! Resolve any ANY in the target type by substituting the matching part of the source, so we never cast a value to a +//! type that still contains ANY - ANY has no physical representation, so building such a vector throws. This happens +//! e.g. when an ARRAY argument fills a LIST(ANY) parameter (the file list of the multi-file readers). +static LogicalType ResolveAnyInParameterType(const LogicalType &target, const LogicalType &source) { + if (target.id() == LogicalTypeId::ANY) { + return source; + } + if (target.id() == LogicalTypeId::LIST && + (source.id() == LogicalTypeId::LIST || source.id() == LogicalTypeId::ARRAY)) { + auto &source_child = + source.id() == LogicalTypeId::LIST ? ListType::GetChildType(source) : ArrayType::GetChildType(source); + return LogicalType::LIST(ResolveAnyInParameterType(ListType::GetChildType(target), source_child)); + } + return target; +} + Value FunctionBinder::CastToParameterType(ClientContext &context, Value value, const LogicalType ¶meter_type) { if (RequiresCast(value.type(), parameter_type) == LogicalTypeComparisonResult::DIFFERENT_TYPES) { - return value.CastAs(context, parameter_type); + return value.CastAs(context, ResolveAnyInParameterType(parameter_type, value.type())); } return value; } @@ -834,24 +850,43 @@ static bool HasUntypedNullArgument(const vector> ®ular return false; } -static bool FoldsToNull(ClientContext &context, const Expression &child) { - if (!child.IsFoldable()) { +//! Evaluates a constant argument and returns true if it is NULL. A non-NULL result replaces the argument with a +//! constant: the argument has been evaluated anyway, and keeping the result means that it is not evaluated again by +//! every enclosing function call - which made binding nested constant expressions quadratic in their depth. +static bool FoldConstantArgument(ClientContext &context, unique_ptr &child) { + if (child->GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { + return child->Cast().GetValue().IsNull(); + } + // a lambda has no value of its own, the function it is passed to executes its body + if (child->GetExpressionClass() == ExpressionClass::BOUND_LAMBDA || !child->IsFoldable()) { return false; } Value result; - return ExpressionExecutor::TryEvaluateScalar(context, child, result) && result.IsNull(); + if (!ExpressionExecutor::TryEvaluateScalar(context, *child, result)) { + // the argument cannot be evaluated: leave it in place so that the error surfaces at execution + return false; + } + if (result.IsNull()) { + return true; + } + auto constant = make_uniq(std::move(result)); + constant->SetAlias(child->GetAlias()); + constant->SetQueryLocation(child->GetQueryLocation()); + child = std::move(constant); + return false; } -//! Returns true if any argument is a constant expression that evaluates to NULL -static bool HasNullConstantArgument(ClientContext &context, const vector> ®ular_args, - const vector>> &keyword_args) { +//! Returns true if any argument is a constant expression that evaluates to NULL. Arguments that evaluate to a non-NULL +//! value are folded to constants along the way. +static bool HasNullConstantArgument(ClientContext &context, vector> ®ular_args, + vector>> &keyword_args) { for (auto &child : regular_args) { - if (FoldsToNull(context, *child)) { + if (FoldConstantArgument(context, child)) { return true; } } for (auto &child : keyword_args) { - if (FoldsToNull(context, *child.second)) { + if (FoldConstantArgument(context, child.second)) { return true; } } @@ -885,8 +920,8 @@ unique_ptr FunctionBinder::BindScalarFunction(const ScalarFunctionCa auto [regular_args, keyword_args] = SplitArguments(std::move(arguments)); // If any of the parameters are NULL, the function will just be replaced with a NULL constant. - // Untyped NULLs never go through type resolution. For typed NULL constants, the types of the function are resolved - // if that is the only way to determine the return type - the bind callback is not invoked for these. + // Untyped NULLs never go through type resolution. For typed NULL constants, resolve the function's types only + // when needed to determine the return type. Preserve result collation without invoking bind callbacks. if (bound_function.GetNullHandling() == FunctionNullHandling::DEFAULT_NULL_HANDLING) { const auto &return_type = bound_function.GetReturnType(); if (HasUntypedNullArgument(regular_args, keyword_args)) { @@ -894,7 +929,12 @@ unique_ptr FunctionBinder::BindScalarFunction(const ScalarFunctionCa } if (HasNullConstantArgument(context, regular_args, keyword_args)) { if (return_type.IsComplete()) { - return make_uniq(Value(return_type)); + BoundScalarFunction null_function(std::move(selected_function)); + for (auto &child : keyword_args) { + regular_args.push_back(std::move(child.second)); + } + PropagateReturnCollation(null_function, null_function.GetProperties(), regular_args, string()); + return make_uniq(Value(null_function.GetReturnType())); } auto resolved = ResolveScalarFunctionTypes(std::move(selected_function), regular_args, keyword_args); PropagateReturnCollation(resolved.bound_function, resolved.bound_function.GetProperties(), regular_args, diff --git a/src/duckdb/src/function/pragma_function.cpp b/src/duckdb/src/function/pragma_function.cpp index d97396846..b63450d96 100644 --- a/src/duckdb/src/function/pragma_function.cpp +++ b/src/duckdb/src/function/pragma_function.cpp @@ -3,6 +3,9 @@ namespace duckdb { +PragmaFunctionInfo::~PragmaFunctionInfo() { +} + //! A pragma's varargs are a positional list, so it declares "*args" and never a "**kwargs" - an argument named after //! no parameter stays an error rather than being absorbed static FunctionSignature PragmaSignature(vector arguments, LogicalType varargs) { diff --git a/src/duckdb/src/function/scalar/list/list_resize.cpp b/src/duckdb/src/function/scalar/list/list_resize.cpp index 6651ce2e0..33d53a291 100644 --- a/src/duckdb/src/function/scalar/list/list_resize.cpp +++ b/src/duckdb/src/function/scalar/list/list_resize.cpp @@ -74,7 +74,8 @@ static void ListResizeFunction(DataChunk &args, ExpressionState &, Vector &resul sel.set_index(j, row_idx); } const auto &default_vector = args.data[2]; - list.Append(default_vector, sel, args.size(), 0, remaining_count.GetValue()); + // the selection only has an entry for every padded row + list.Append(default_vector, sel, remaining_count.GetValue(), 0, remaining_count.GetValue()); continue; } diff --git a/src/duckdb/src/function/scalar/list/list_select.cpp b/src/duckdb/src/function/scalar/list/list_select.cpp index a52d7c4d6..a3f67377b 100644 --- a/src/duckdb/src/function/scalar/list/list_select.cpp +++ b/src/duckdb/src/function/scalar/list/list_select.cpp @@ -14,9 +14,9 @@ struct SetSelectionVectorSelect { using CHILD_TYPE = int64_t; static void SetSelectionVector(SelectionVector &selection_vector, ValidityMask &validity_mask, - const ValidityMask &input_validity, const VectorIterator &child_data, - idx_t child_idx, idx_t &target_offset, idx_t selection_offset, idx_t input_offset, - idx_t target_length) { + const VectorValidityIterator &input_validity, + const VectorIterator &child_data, idx_t child_idx, idx_t &target_offset, + idx_t selection_offset, idx_t input_offset, idx_t target_length) { auto child_entry = child_data[selection_offset + child_idx]; int64_t sel_idx = -1; if (child_entry.IsValid()) { @@ -29,7 +29,7 @@ struct SetSelectionVectorSelect { if (sel_idx >= 0 && sel_idx < UnsafeNumericCast(target_length)) { auto sel_idx_unsigned = UnsafeNumericCast(sel_idx); selection_vector.set_index(target_offset, input_offset + sel_idx_unsigned); - if (!input_validity.RowIsValid(input_offset + sel_idx_unsigned)) { + if (!input_validity.IsValid(input_offset + sel_idx_unsigned)) { validity_mask.SetInvalid(target_offset); } } else { @@ -49,7 +49,7 @@ struct SetSelectionVectorWhere { using CHILD_TYPE = bool; static void SetSelectionVector(SelectionVector &selection_vector, ValidityMask &validity_mask, - const ValidityMask &input_validity, const VectorIterator &child_data, + const VectorValidityIterator &input_validity, const VectorIterator &child_data, idx_t child_idx, idx_t &target_offset, idx_t selection_offset, idx_t input_offset, idx_t target_length) { auto child_val = child_data[selection_offset + child_idx]; @@ -65,7 +65,7 @@ struct SetSelectionVectorWhere { } selection_vector.set_index(target_offset, input_offset + child_idx); - if (!input_validity.RowIsValid(input_offset + child_idx)) { + if (!input_validity.IsValid(input_offset + child_idx)) { validity_mask.SetInvalid(target_offset); } @@ -97,7 +97,7 @@ void ListSelectFunction(const DataChunk &args, ExpressionState &state, Vector &r auto &selection_entry = ListVector::GetChild(selection_list); auto input_lists_data = list.Values(); auto &input_entry = ListVector::GetChild(list); - auto &input_validity = FlatVector::Validity(input_entry); + auto input_validity = input_entry.Validity(); auto selection_entry_data = selection_entry.Values(); idx_t result_length = 0; diff --git a/src/duckdb/src/function/scalar/operator/arithmetic.cpp b/src/duckdb/src/function/scalar/operator/arithmetic.cpp index d3a2d693b..ca3983d28 100644 --- a/src/duckdb/src/function/scalar/operator/arithmetic.cpp +++ b/src/duckdb/src/function/scalar/operator/arithmetic.cpp @@ -336,7 +336,9 @@ LogicalType DecimalArithmeticResultType(const vector &input_types, uint8_t width, scale; auto can_convert = input_type.GetDecimalProperties(width, scale); if (!can_convert) { - throw InternalException("Could not convert type %s to a decimal.", input_type.ToString()); + throw BinderException("Could not convert type %s to a decimal for the arithmetic operator - add an " + "explicit cast", + input_type.ToString()); } if (width > max_width) { max_width = width; @@ -1017,7 +1019,9 @@ LogicalType DecimalMultiplyResultType(const vector &input_types, bo uint8_t width, scale; auto can_convert = input_type.GetDecimalProperties(width, scale); if (!can_convert) { - throw InternalException("Could not convert type %s to a decimal?", input_type.ToString()); + throw BinderException("Could not convert type %s to a decimal for the arithmetic operator - add an " + "explicit cast", + input_type.ToString()); } max_width = MaxValue(width, max_width); result_width += width; diff --git a/src/duckdb/src/function/scalar/sequence/nextval.cpp b/src/duckdb/src/function/scalar/sequence/nextval.cpp index 6d7e0af5b..c8bd13b70 100644 --- a/src/duckdb/src/function/scalar/sequence/nextval.cpp +++ b/src/duckdb/src/function/scalar/sequence/nextval.cpp @@ -47,7 +47,12 @@ SequenceCatalogEntry &BindSequenceFromContext(ClientContext &context, const Qual } SequenceCatalogEntry &BindSequence(Binder &binder, const Identifier &name) { - return BindSequence(binder, QualifiedName::Parse(name.GetIdentifierName())); + QualifiedName qname; + string error; + if (!QualifiedName::TryParse(name.GetIdentifierName(), qname, error)) { + throw ParserException(error); + } + return BindSequence(binder, qname); } struct NextValLocalState : public FunctionLocalState { diff --git a/src/duckdb/src/function/scalar/strftime_format.cpp b/src/duckdb/src/function/scalar/strftime_format.cpp index a96c08162..672224fac 100644 --- a/src/duckdb/src/function/scalar/strftime_format.cpp +++ b/src/duckdb/src/function/scalar/strftime_format.cpp @@ -219,7 +219,7 @@ char *StrfTimeFormat::WritePadded3(char *target, uint32_t value) const { } } -// write a value in the range of 0..999999... padded to the given number of digits +// write a year padded to 4 digits if it is within [0, 9999], or in full (with its sign) otherwise char *StrfTimeFormat::WriteYear(char *target, int32_t year) const { if (year >= 0 && year <= 9999) { return WritePadded(target, UnsafeNumericCast(year), 4); @@ -234,6 +234,7 @@ char *StrfTimeFormat::WriteYear(char *target, int32_t year) const { return target + len; } +// write a value in the range of 0..999999... padded to the given number of digits char *StrfTimeFormat::WritePadded(char *target, uint32_t value, size_t padding) const { D_ASSERT(padding > 1); if (padding % 2) { diff --git a/src/duckdb/src/function/scalar/string/like.cpp b/src/duckdb/src/function/scalar/string/like.cpp index 133b1738b..199e00a3f 100644 --- a/src/duckdb/src/function/scalar/string/like.cpp +++ b/src/duckdb/src/function/scalar/string/like.cpp @@ -457,7 +457,8 @@ bool ILikeOperatorFunctionInternal(string_t &str, string_t &pattern, char escape if (!HAS_ESCAPE) { return LikeOperatorFunction(str_lcase, pat_lcase); } - return LikeOperatorFunction(str_lcase, pat_lcase, escape); + // the pattern is lowercased, so the escape character must be as well + return LikeOperatorFunction(str_lcase, pat_lcase, StringUtil::CharacterToLower(escape)); } bool ILikeOperatorFunction(string_t &str, string_t &pattern) { @@ -553,7 +554,8 @@ void ILikeEscapeFunction(DataChunk &args, ExpressionState &state, Vector &result escape_vec.GetVectorType() == VectorType::CONSTANT_VECTOR && !ConstantVector::IsNull(escape_vec)) { auto pattern = *ConstantVector::GetData(pattern_vec); auto escape = *ConstantVector::GetData(escape_vec); - char escape_char = GetEscapeChar(escape); + // the pattern is lowercased, so the escape character must be as well + char escape_char = StringUtil::CharacterToLower(GetEscapeChar(escape)); bool has_escape = escape.GetSize() != 0; // lowercase the pattern exactly once, up front diff --git a/src/duckdb/src/function/scalar/string/regexp.cpp b/src/duckdb/src/function/scalar/string/regexp.cpp index e75fdddd6..cdc2b5edb 100644 --- a/src/duckdb/src/function/scalar/string/regexp.cpp +++ b/src/duckdb/src/function/scalar/string/regexp.cpp @@ -281,6 +281,9 @@ static void RegexExtractFunction(DataChunk &args, ExpressionState &state, Vector BinaryExecutor::Execute( strings, patterns, result, [&](string_t input, string_t pattern) { RE2 re(CreateStringPiece(pattern), info.options); + if (!re.ok()) { + throw InvalidInputException(re.error()); + } return ExtractCaptureGroup(input, re, info.group_index, info.no_match_returns_input); }); } diff --git a/src/duckdb/src/function/scalar/string/regexp/regexp_extract_all.cpp b/src/duckdb/src/function/scalar/string/regexp/regexp_extract_all.cpp index c4339b633..6d81c6ae8 100644 --- a/src/duckdb/src/function/scalar/string/regexp/regexp_extract_all.cpp +++ b/src/duckdb/src/function/scalar/string/regexp/regexp_extract_all.cpp @@ -44,12 +44,11 @@ bool ExtractAll(duckdb_re2::StringPiece &input, duckdb_re2::RE2 &pattern, idx_t if (!pattern.Match(input, *startpos, input.size(), pattern.UNANCHORED, groups, ngroups + 1)) { return false; } - idx_t consumed = static_cast(groups[0].end() - (input.begin() + *startpos)); - if (!consumed) { - // Empty match: advance exactly one UTF-8 codepoint - consumed = regexp_util::AdvanceOneUTF8Basic(input, *startpos); + *startpos = static_cast(groups[0].end() - input.begin()); + if (groups[0].empty()) { + // Empty match: advance exactly one UTF-8 codepoint past it + *startpos += regexp_util::AdvanceOneUTF8Basic(input, *startpos); } - *startpos += consumed; return true; } @@ -189,11 +188,10 @@ static inline bool ExtractAllStruct(duckdb_re2::StringPiece &input, duckdb_re2:: if (!re.Match(input, startpos, input.size(), re.UNANCHORED, groups, provided_groups + 1)) { return false; } - idx_t consumed = static_cast(groups[0].end() - (input.begin() + startpos)); - if (!consumed) { - consumed = regexp_util::AdvanceOneUTF8Basic(input, startpos); + startpos = static_cast(groups[0].end() - input.begin()); + if (groups[0].empty()) { + startpos += regexp_util::AdvanceOneUTF8Basic(input, startpos); } - startpos += consumed; return true; } diff --git a/src/duckdb/src/function/scalar/string/regexp/regexp_util.cpp b/src/duckdb/src/function/scalar/string/regexp/regexp_util.cpp index 71fff04b7..04db81e3e 100644 --- a/src/duckdb/src/function/scalar/string/regexp/regexp_util.cpp +++ b/src/duckdb/src/function/scalar/string/regexp/regexp_util.cpp @@ -96,6 +96,9 @@ void ParseGroupNameList(const string &function_name, const Value &list_val, cons throw BinderException("NULL group name in %s", function_name); } auto name = child.ToString(); + if (name.empty()) { + throw BinderException("Empty group name in %s", function_name); + } if (name_set.find(Identifier(name)) != name_set.end()) { throw BinderException("Duplicate group name '%s' in %s", name, function_name); } diff --git a/src/duckdb/src/function/scalar/string/string_split.cpp b/src/duckdb/src/function/scalar/string/string_split.cpp index 322c7e122..388375a56 100644 --- a/src/duckdb/src/function/scalar/string/string_split.cpp +++ b/src/duckdb/src/function/scalar/string/string_split.cpp @@ -16,26 +16,33 @@ namespace { struct RegularStringSplit { static constexpr bool VERIFY_UTF8 = false; - static idx_t Find(const char *input_data, idx_t input_size, const char *delim_data, idx_t delim_size, + static idx_t Find(const char *input_data, idx_t input_size, idx_t start, const char *delim_data, idx_t delim_size, idx_t &match_size, void *data) { match_size = delim_size; if (delim_size == 0) { - return 0; + return start; } - return FindStrInStr(const_uchar_ptr_cast(input_data), input_size, const_uchar_ptr_cast(delim_data), delim_size); + auto pos = FindStrInStr(const_uchar_ptr_cast(input_data + start), input_size - start, + const_uchar_ptr_cast(delim_data), delim_size); + if (pos == DConstants::INVALID_INDEX) { + return pos; + } + return start + pos; } }; -struct ConstantRegexpStringSplit { +struct RegexpStringSplit { //! \C can split a multi-byte character static constexpr bool VERIFY_UTF8 = true; - static idx_t Find(const char *input_data, idx_t input_size, const char *delim_data, idx_t delim_size, + static idx_t Find(const char *input_data, idx_t input_size, idx_t start, const char *delim_data, idx_t delim_size, idx_t &match_size, void *data) { D_ASSERT(data); auto regex = reinterpret_cast(data); duckdb_re2::StringPiece match; - if (!regex->Match(duckdb_re2::StringPiece(input_data, input_size), 0, input_size, RE2::UNANCHORED, &match, 1)) { + // match against the full input so that anchors and assertions see the original context + if (!regex->Match(duckdb_re2::StringPiece(input_data, input_size), start, input_size, RE2::UNANCHORED, &match, + 1)) { return DConstants::INVALID_INDEX; } match_size = match.size(); @@ -43,19 +50,6 @@ struct ConstantRegexpStringSplit { } }; -struct RegexpStringSplit { - static constexpr bool VERIFY_UTF8 = true; - - static idx_t Find(const char *input_data, idx_t input_size, const char *delim_data, idx_t delim_size, - idx_t &match_size, void *data) { - duckdb_re2::RE2 regex(duckdb_re2::StringPiece(delim_data, delim_size)); - if (!regex.ok()) { - throw InvalidInputException(regex.error()); - } - return ConstantRegexpStringSplit::Find(input_data, input_size, delim_data, delim_size, match_size, ®ex); - } -}; - struct StringSplitter { template static idx_t Split(string_t input, string_t delim, void *data, CB &&emit) { @@ -64,14 +58,15 @@ struct StringSplitter { auto delim_data = delim.GetData(); auto delim_size = delim.GetSize(); idx_t list_idx = 0; - while (input_size > 0) { + idx_t start = 0; + while (start < input_size) { idx_t match_size = 0; - auto pos = OP::Find(input_data, input_size, delim_data, delim_size, match_size, data); + auto pos = OP::Find(input_data, input_size, start, delim_data, delim_size, match_size, data); if (pos > input_size) { break; } - if (match_size == 0 && pos == 0) { - // special case: 0 length match and pos is 0 + if (match_size == 0 && pos == start) { + // special case: 0 length match at the start // move to the next character for (pos++; pos < input_size; pos++) { if (IsCharacter(input_data[pos])) { @@ -83,19 +78,18 @@ struct StringSplitter { } } D_ASSERT(input_size >= pos + match_size); - emit(input_data, pos); + emit(input_data + start, pos - start); list_idx++; - input_data += (pos + match_size); - input_size -= (pos + match_size); + start = pos + match_size; } - emit(input_data, input_size); + emit(input_data + start, input_size - start); list_idx++; return list_idx; } }; -template -void StringSplitExecutor(DataChunk &args, ExpressionState &state, Vector &result, void *data = nullptr) { +template +void StringSplitExecutor(DataChunk &args, ExpressionState &state, Vector &result, GET_DATA &&get_data) { auto input_entries = args.data[0].Values(); auto delim_entries = args.data[1].Values(); @@ -116,8 +110,9 @@ void StringSplitExecutor(DataChunk &args, ExpressionState &state, Vector &result list.WriteElement().WriteStringRef(input_entry.GetValue()); continue; } + auto delim = delim_entry.GetValue(); StringSplitter::Split( - input_entry.GetValue(), delim_entry.GetValue(), data, [&](const char *split_data, idx_t split_size) { + input_entry.GetValue(), delim, get_data(delim), [&](const char *split_data, idx_t split_size) { if (OP::VERIFY_UTF8) { regexp_util::VerifyUTF8Result(split_data, split_size); } @@ -129,7 +124,7 @@ void StringSplitExecutor(DataChunk &args, ExpressionState &state, Vector &result } void StringSplitFunction(DataChunk &args, ExpressionState &state, Vector &result) { - StringSplitExecutor(args, state, result, nullptr); + StringSplitExecutor(args, state, result, [](const string_t &) -> void * { return nullptr; }); } void StringSplitRegexFunction(DataChunk &args, ExpressionState &state, Vector &result) { @@ -138,10 +133,18 @@ void StringSplitRegexFunction(DataChunk &args, ExpressionState &state, Vector &r if (info.constant_pattern) { // fast path: pre-compiled regex auto &lstate = ExecuteFunctionState::GetFunctionState(state)->Cast(); - StringSplitExecutor(args, state, result, &lstate.constant_pattern); + StringSplitExecutor(args, state, result, + [&](const string_t &) -> void * { return &lstate.constant_pattern; }); } else { // slow path: have to re-compile regex for every row - StringSplitExecutor(args, state, result); + unique_ptr regex; + StringSplitExecutor(args, state, result, [&](const string_t &delim) -> void * { + regex = make_uniq(regexp_util::CreateStringPiece(delim), info.options); + if (!regex->ok()) { + throw InvalidInputException(regex->error()); + } + return regex.get(); + }); } } diff --git a/src/duckdb/src/function/scalar/string/substring.cpp b/src/duckdb/src/function/scalar/string/substring.cpp index b2e3a6bf5..fcf4483c5 100644 --- a/src/duckdb/src/function/scalar/string/substring.cpp +++ b/src/duckdb/src/function/scalar/string/substring.cpp @@ -396,7 +396,7 @@ unique_ptr SubstringStatsFromSharedPrefix(FunctionStatisticsInpu optional_idx max_output_length; if (has_parameters && parameters.character_count.IsValid()) { - auto bytes_per_character = StringStats::CanContainUnicode(input.child_stats[0]) ? 4 : 1; + idx_t bytes_per_character = StringStats::CanContainUnicode(input.child_stats[0]) ? 4 : 1; auto character_count = parameters.character_count.GetIndex(); if (character_count <= NumericLimits::Maximum() / bytes_per_character) { max_output_length = character_count * bytes_per_character; diff --git a/src/duckdb/src/function/scalar/system/aggregate_export.cpp b/src/duckdb/src/function/scalar/system/aggregate_export.cpp index 584ec1bfb..7a598344f 100644 --- a/src/duckdb/src/function/scalar/system/aggregate_export.cpp +++ b/src/duckdb/src/function/scalar/system/aggregate_export.cpp @@ -243,7 +243,7 @@ unique_ptr BindExportedAggregate(ClientContext &context context, QualifiedName(Catalog::GetSystemCatalog(context).GetName(), Identifier::DefaultSchema(), Identifier(function_name))); if (func.type != CatalogType::AGGREGATE_FUNCTION_ENTRY) { - throw InternalException("Could not find aggregate %s", function_name); + throw BinderException("Could not find aggregate %s", function_name); } auto &aggr_entry = func.Cast(); @@ -271,8 +271,8 @@ unique_ptr BindExportedAggregate(ClientContext &context } } - // FIXME: this is really hacky - // but the aggregate state export needs a rework around how it handles more complex aggregates anyway + // Bind callbacks can use NULL placeholders for parameters carried by the serialized state. + // Parameters required by the binding itself must retain their recorded constant values. vector> args; args.reserve(argument_types.size()); for (idx_t arg_idx = 0; arg_idx < argument_types.size(); arg_idx++) { @@ -290,7 +290,8 @@ unique_ptr BindExportedAggregate(ClientContext &context bool signature_matches = bound_args.size() == argument_types.size(); for (idx_t arg_idx = 0; signature_matches && arg_idx < bound_args.size(); arg_idx++) { // an ANY argument in the function signature (e.g. string_agg's data argument) matches any requested type - if (bound_args[arg_idx].id() != LogicalTypeId::ANY && bound_args[arg_idx] != argument_types[arg_idx]) { + if (bound_args[arg_idx].id() != LogicalTypeId::ANY && + !bound_args[arg_idx].EqualsIncludingCollation(argument_types[arg_idx])) { signature_matches = false; } } @@ -387,6 +388,10 @@ unique_ptr BindAggregateStateInternal(ClientContext &co ParseOrderBys(order_entry->second, column_count, orders); // the leading buffered columns are the inner aggregate's bound arguments (post constant-erasure) const idx_t argument_count = inner->aggr.GetArguments().size(); + if (argument_count == 0) { + throw BinderException("Aggregate \"%s\" has no arguments and cannot have an ordered aggregate state", + function_name); + } if (argument_count > column_count) { throw BinderException("to_aggregate_state: argument count %llu exceeds the number of state columns (%llu)", (uint64_t)argument_count, (uint64_t)column_count); @@ -791,6 +796,11 @@ unique_ptr BindToAggregateState(INPUT &input) { const idx_t column_count = buffer_columns.size(); // the leading buffered columns are the aggregate's arguments - cast them to the argument types auto &aggr_arguments = aggr.GetArguments(); + if (aggr_arguments.empty()) { + throw BinderException("to_aggregate_state: aggregate \"%s\" has no arguments and cannot have an ordered " + "aggregate state", + function_name); + } if (aggr_arguments.size() > column_count) { throw BinderException("to_aggregate_state: argument count %llu exceeds the number of state columns (%llu)", (uint64_t)aggr_arguments.size(), (uint64_t)column_count); @@ -855,16 +865,19 @@ unique_ptr ExportAggregateFunction::StateToSQL(const LogicalTy vector types; map constants; ParseStateParameters(parameters->second, types, constants); + // Concrete TYPE values retain collations that the SQL type name alone cannot express. + // Canonical ANY arguments are signature markers, not SQL value types. + const bool has_any = std::find(types.begin(), types.end(), LogicalType::ANY) != types.end(); vector signature; vector> constant_arguments; for (idx_t i = 0; i < types.size(); i++) { - if ((types[i] != LogicalType::ANY && !TypeExpression::CanRepresent(types[i])) || - TypeVisitor::Contains(types[i], [](const LogicalType &child) { - return child.id() == LogicalTypeId::VARCHAR && !StringType::GetCollation(child).empty(); - })) { + if (has_any && ((types[i] != LogicalType::ANY && !TypeExpression::CanRepresent(types[i])) || + TypeVisitor::Contains(types[i], [](const LogicalType &child) { + return child.id() == LogicalTypeId::VARCHAR && !StringType::GetCollation(child).empty(); + }))) { return nullptr; } - signature.emplace_back(types[i].ToString()); + signature.push_back(has_any ? Value(types[i].ToString()) : Value::TYPE(types[i])); auto entry = constants.find(i); unique_ptr constant; try { @@ -877,7 +890,12 @@ unique_ptr ExportAggregateFunction::StateToSQL(const LogicalTy vector> arguments; arguments.push_back(std::move(value)); arguments.push_back(ConstantExpression::FromValue(name->second)); - arguments.push_back(ConstantExpression::FromValue(Value::LIST(LogicalType::VARCHAR, std::move(signature)))); + try { + arguments.push_back(ConstantExpression::FromValue( + Value::LIST(has_any ? LogicalType::VARCHAR : LogicalType::TYPE(), std::move(signature)))); + } catch (const NotImplementedException &) { + return nullptr; + } arguments.push_back( make_uniq(QualifiedName("system", "main", "list_value"), std::move(constant_arguments))); auto orders = info->properties.find("order_bys"); @@ -921,9 +939,11 @@ ExportAggregateFunction::Bind(unique_ptr child_aggrega D_ASSERT(bound_function.HasStateSizeCallback()); D_ASSERT(bound_function.HasStateFinalizeCallback()); D_ASSERT(child_aggregate->Function().GetReturnType().id() != LogicalTypeId::INVALID); - if (child_aggregate->GetOrderBys() && !child_aggregate->GetOrderBys()->orders.empty()) { - // ordered aggregate: export the buffer of values. The sorted wrapper is built later (physical planning); here - // we only fix the AGGREGATE_STATE type so downstream binding (finalize/combine) sees it + if (child_aggregate->GetOrderBys() && !child_aggregate->GetOrderBys()->orders.empty() && + !child_aggregate->GetChildren().empty()) { + // ordered aggregate (the ORDER BY of an aggregate without arguments is ignored): export the buffer of values. + // The sorted wrapper is built later (physical planning); here we only fix the AGGREGATE_STATE type so + // downstream binding (finalize/combine) sees it LogicalType buffer_struct; vector orders; idx_t argument_count; // re-derived from the inner aggregate on re-bind diff --git a/src/duckdb/src/function/scalar/system/parse_log_message.cpp b/src/duckdb/src/function/scalar/system/parse_log_message.cpp index 81a4454d3..e21c4c65a 100644 --- a/src/duckdb/src/function/scalar/system/parse_log_message.cpp +++ b/src/duckdb/src/function/scalar/system/parse_log_message.cpp @@ -9,6 +9,8 @@ #include "duckdb/logging/log_type.hpp" #include "duckdb/function/cast/vector_cast_helpers.hpp" #include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/main/extension_entries.hpp" +#include "duckdb/main/extension_helper.hpp" #include "utf8proc.hpp" @@ -49,6 +51,11 @@ unique_ptr ParseLogMessageBind(BindScalarFunctionInput &input) { auto type = StringValue::Get(type_val); auto lookup = LogManager::Get(context).LookupLogType(type); + if (!lookup) { + // the log type may belong to an extension that is not loaded yet + ExtensionHelper::TryAutoloadFromEntry(*context.db, Identifier(type), EXTENSION_LOG_TYPES); + lookup = LogManager::Get(context).LookupLogType(type); + } if (!lookup) { throw InvalidInputException("structured_log_schema: '%s' not found", type); diff --git a/src/duckdb/src/function/scalar/system/write_log.cpp b/src/duckdb/src/function/scalar/system/write_log.cpp index de3757b05..d614b1782 100644 --- a/src/duckdb/src/function/scalar/system/write_log.cpp +++ b/src/duckdb/src/function/scalar/system/write_log.cpp @@ -175,6 +175,8 @@ ScalarFunctionSet WriteLogFun::GetFunctions() { fun.GetSignature().AddKwargs("kwargs", LogicalType::ANY); fun.SetResolveTypesCallback(WriteLogResolveTypes); fun.GetProperties().SetRequiresExpressionNames(true); + // an unknown scope is only detected while writing + fun.SetFallible(); fun.SetUnbindCallback(WriteLogUnbind); set.AddFunction(std::move(fun)); diff --git a/src/duckdb/src/function/table/arrow_conversion.cpp b/src/duckdb/src/function/table/arrow_conversion.cpp index 899830562..89a30178f 100644 --- a/src/duckdb/src/function/table/arrow_conversion.cpp +++ b/src/duckdb/src/function/table/arrow_conversion.cpp @@ -732,12 +732,12 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBRunEndEncoded(Vector &vector, c run_end_encoding.run_ends = make_uniq(run_ends_type.GetDuckType(), compressed_size); run_end_encoding.values = make_uniq(values_type.GetDuckType(), compressed_size); - ArrowToDuckDBConversion::ColumnArrowToDuckDB(*run_end_encoding.run_ends, run_ends_array, chunk_offset, - array_state, compressed_size, run_ends_type); + // the run ends and values are scanned in full - the offsets of the scan apply to the logical (decoded) rows + ArrowToDuckDBConversion::ColumnArrowToDuckDB(*run_end_encoding.run_ends, run_ends_array, 0, array_state, + compressed_size, run_ends_type); auto &values = *run_end_encoding.values; - ArrowToDuckDBConversion::SetValidityMask(values, values_array, chunk_offset, compressed_size, - NumericCast(parent_offset), nested_offset); - ArrowToDuckDBConversion::ColumnArrowToDuckDB(values, values_array, chunk_offset, array_state, compressed_size, + ArrowToDuckDBConversion::SetValidityMask(values, values_array, 0, compressed_size, 0, -1); + ArrowToDuckDBConversion::ColumnArrowToDuckDB(values, values_array, 0, array_state, compressed_size, values_type); } @@ -1251,6 +1251,7 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar auto &validity_mask = FlatVector::ValidityMutable(vector); auto &union_info = arrow_type.GetTypeInfo(); + const auto member_offset = parent_offset + NumericCast(array.offset); duckdb::vector children; for (idx_t child_idx = 0; child_idx < NumericCast(array.n_children); child_idx++) { Vector child(members[child_idx].second, size); @@ -1258,22 +1259,25 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar auto &child_state = array_state.GetChild(child_idx); auto &child_type = union_info.GetChild(child_idx); + // the members of a sparse union have the same length as the union - they are scanned at the same offset ArrowToDuckDBConversion::SetValidityMask(child, child_array, chunk_offset, size, - NumericCast(parent_offset), nested_offset); + NumericCast(member_offset), nested_offset); auto array_physical_type = child_type.GetPhysicalType(); switch (array_physical_type) { case ArrowArrayPhysicalType::DICTIONARY_ENCODED: ArrowToDuckDBConversion::ColumnArrowToDuckDBDictionary(child, child_array, chunk_offset, child_state, - size, child_type); + size, child_type, nested_offset, &validity_mask, + member_offset); break; case ArrowArrayPhysicalType::RUN_END_ENCODED: ArrowToDuckDBConversion::ColumnArrowToDuckDBRunEndEncoded(child, child_array, chunk_offset, child_state, - size, child_type); + size, child_type, nested_offset, + &validity_mask, member_offset); break; case ArrowArrayPhysicalType::DEFAULT: ArrowToDuckDBConversion::ColumnArrowToDuckDB(child, child_array, chunk_offset, child_state, size, - child_type, nested_offset, &validity_mask, false); + child_type, nested_offset, &validity_mask, member_offset); break; default: throw NotImplementedException("ArrowArrayPhysicalType not recognized"); diff --git a/src/duckdb/src/function/table/query_function.cpp b/src/duckdb/src/function/table/query_function.cpp index 48f494e9e..542122a00 100644 --- a/src/duckdb/src/function/table/query_function.cpp +++ b/src/duckdb/src/function/table/query_function.cpp @@ -49,7 +49,12 @@ static string TableReference(const string &input) { if (IsFilePath(input)) { return SQLString::ToString(input); } - return QualifiedName::Parse(input).ToString(QualifiedNameToStringMode::HIDE_DEFAULT_SCHEMA); + QualifiedName qname; + string error; + if (!QualifiedName::TryParse(input, qname, error)) { + throw ParserException(error); + } + return qname.ToString(QualifiedNameToStringMode::HIDE_DEFAULT_SCHEMA); } static string UnionTablesQuery(TableFunctionBindInput &input) { diff --git a/src/duckdb/src/function/table/read_duckdb.cpp b/src/duckdb/src/function/table/read_duckdb.cpp index d8d1298b5..966afa893 100644 --- a/src/duckdb/src/function/table/read_duckdb.cpp +++ b/src/duckdb/src/function/table/read_duckdb.cpp @@ -113,6 +113,8 @@ class DuckDBReader : public BaseFileReader { unique_ptr bind_data; unique_ptr global_state; atomic finished; + //! The column indexes to scan - the row id is scanned if no columns are projected + vector scan_column_indexes; idx_t column_count; Identifier schema_name; Identifier table_name; @@ -123,6 +125,8 @@ struct DuckDBReadGlobalState : GlobalTableFunctionState {}; struct DuckDBReadLocalState : LocalTableFunctionState { unique_ptr local_state; shared_ptr attached_database; + //! The chunk to scan into when no columns are projected (we then scan the row ids) + DataChunk row_id_chunk; }; string DuckDBFileReaderOptions::GetCandidates(const vector> &tables) const { @@ -234,7 +238,8 @@ DuckDBReader::DuckDBReader(ClientContext &context_p, OpenFileInfo file_p, const } auto &table = tables[0].get(); for (auto &col : table.GetColumns().Logical()) { - columns.emplace_back(col.Name().GetIdentifierName(), col.Type()); + // create the child column definitions as well, so that STRUCT children are mapped by name + columns.push_back(MultiFileColumnDefinition::CreateFromNameAndType(col.Name(), col.Type())); } column_count = columns.size(); schema_name = table.ParentSchema().name; @@ -296,16 +301,24 @@ bool DuckDBReader::TryInitializeScan(ClientContext &context, GlobalTableFunction } } + scan_column_indexes = column_indexes; + if (scan_column_indexes.empty()) { + // the table scan requires at least one column - scan the row id if no columns are projected + scan_column_indexes.emplace_back(COLUMN_IDENTIFIER_ROW_ID); + } // initialize the scan over this table - TableFunctionInitInput input(bind_data.get(), column_indexes, vector(), filters.get()); + TableFunctionInitInput input(bind_data.get(), scan_column_indexes, vector(), filters.get()); global_state = scan_function.init_global(context, input); } AssignSharedPointer(lstate.attached_database, db_wrapper); // initialize the local scan ThreadContext thread(context); ExecutionContext exec_context(context, thread, nullptr); - TableFunctionInitInput input(bind_data.get(), column_indexes, vector(), filters.get()); + TableFunctionInitInput input(bind_data.get(), scan_column_indexes, vector(), filters.get()); lstate.local_state = scan_function.init_local(exec_context, input, global_state.get()); + if (column_indexes.empty() && lstate.row_id_chunk.ColumnCount() == 0) { + lstate.row_id_chunk.Initialize(context, {LogicalType::ROW_TYPE}); + } return true; } @@ -319,7 +332,14 @@ AsyncResult DuckDBReader::Scan(ClientContext &context, GlobalTableFunctionState } else { input.async_result = AsyncResultType::IMPLICIT; input.results_execution_mode = AsyncResultsExecutionMode::TASK_EXECUTOR; - scan_function.function(context, input, chunk); + if (column_indexes.empty()) { + // no columns are projected - scan the row ids and only emit the count + lstate.row_id_chunk.Reset(); + scan_function.function(context, input, lstate.row_id_chunk); + chunk.SetChildCardinality(lstate.row_id_chunk.size()); + } else { + scan_function.function(context, input, chunk); + } if (input.async_result.GetResultType() == AsyncResultType::BLOCKED) { return std::move(input.async_result); @@ -383,6 +403,9 @@ bool DuckDBMultiFileInfo::ParseCopyOption(ClientContext &context, const Identifi bool DuckDBMultiFileInfo::ParseOption(ClientContext &context, const Identifier &key, const Value &val, MultiFileOptions &file_options, BaseFileReaderOptions &options_p) { auto &options = options_p.Cast(); + if ((key == "schema_name" || key == "table_name") && val.IsNull()) { + throw BinderException("read_duckdb parameter %s cannot be NULL", key); + } if (key == "schema_name") { options.schema_name = Identifier(StringValue::Get(val)); return true; diff --git a/src/duckdb/src/function/table/system/pragma_storage_info.cpp b/src/duckdb/src/function/table/system/pragma_storage_info.cpp index e76d24a2f..3927923a9 100644 --- a/src/duckdb/src/function/table/system/pragma_storage_info.cpp +++ b/src/duckdb/src/function/table/system/pragma_storage_info.cpp @@ -119,7 +119,11 @@ static unique_ptr PragmaStorageInfoBind(ClientContext &context, Ta names.emplace_back("additional_block_ids"); return_types.emplace_back(LogicalType::LIST(LogicalTypeId::BIGINT)); - auto qname = QualifiedName::Parse(input.inputs[0].GetValue()); + QualifiedName qname; + string error; + if (!QualifiedName::TryParse(input.inputs[0].GetValue(), qname, error)) { + throw ParserException(error); + } // look up the table name in the catalog CatalogEntryRetriever retriever(context); diff --git a/src/duckdb/src/function/table/system/pragma_table_info.cpp b/src/duckdb/src/function/table/system/pragma_table_info.cpp index 71b7e9516..392892e46 100644 --- a/src/duckdb/src/function/table/system/pragma_table_info.cpp +++ b/src/duckdb/src/function/table/system/pragma_table_info.cpp @@ -167,7 +167,11 @@ static unique_ptr PragmaTableInfoBind(ClientContext &context, Tabl PragmaShowHelper::GetSchema(return_types, names); } - auto qname = QualifiedName::Parse(input.inputs[0].GetValue()); + QualifiedName qname; + string error; + if (!QualifiedName::TryParse(input.inputs[0].GetValue(), qname, error)) { + throw ParserException(error); + } // look up the table name in the catalog CatalogEntryRetriever retriever(context); diff --git a/src/duckdb/src/function/table/system/pragma_table_sample.cpp b/src/duckdb/src/function/table/system/pragma_table_sample.cpp index 9f14accbe..4cc7461e9 100644 --- a/src/duckdb/src/function/table/system/pragma_table_sample.cpp +++ b/src/duckdb/src/function/table/system/pragma_table_sample.cpp @@ -33,7 +33,11 @@ struct DuckDBTableSampleOperatorData : public GlobalTableFunctionState { static unique_ptr DuckDBTableSampleBind(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { // look up the table name in the catalog - auto qname = QualifiedName::Parse(input.inputs[0].GetValue()); + QualifiedName qname; + string error; + if (!QualifiedName::TryParse(input.inputs[0].GetValue(), qname, error)) { + throw ParserException(error); + } CatalogEntryRetriever retriever(context); qname = Binder::BindTableName(retriever, qname); diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index f6309c670..c0db64a9e 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha46057" +#define DUCKDB_PATCH_VERSION "0-alpha47600" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha46057" +#define DUCKDB_VERSION "v2.0.0-alpha47600" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "b15358c371" +#define DUCKDB_SOURCE_ID "d4355d01c5" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/table_function.cpp b/src/duckdb/src/function/table_function.cpp index 64dd63ea7..34e7d5001 100644 --- a/src/duckdb/src/function/table_function.cpp +++ b/src/duckdb/src/function/table_function.cpp @@ -138,37 +138,7 @@ BoundTableFunction::BoundTableFunction(shared_ptr function_ void BoundTableFunction::SetCallArguments(const vector ¶meters, const named_argument_map_t &named_parameters) { - auto &signature = GetSignature(); - const auto positional_count = signature.GetPositionalParameterCount(); - arguments.clear(); - for (idx_t i = 0; i < positional_count; i++) { - arguments.push_back(signature.GetParameter(i).GetType()); - } - auto args = signature.GetArgs(); - for (idx_t i = positional_count; i < parameters.size(); i++) { - auto is_typed = args && args->GetType().id() != LogicalTypeId::ANY; - arguments.push_back(is_typed ? args->GetType() : parameters[i].type()); - } - const auto positional_argument_count = arguments.size(); - - // only the options the signature receives by name, in the order they were passed - vector names; - for (auto &entry : named_parameters) { - auto param_idx = signature.GetParameterIndexByName(entry.first); - if (!param_idx.IsValid()) { - if (signature.GetKwargs()) { - names.push_back(entry.first); - arguments.push_back(entry.second.type()); - } - continue; - } - auto ¶m = signature.GetParameter(param_idx.GetIndex()); - if (!param.AcceptsPosition()) { - names.push_back(entry.first); - arguments.push_back(param.GetType()); - } - } - SetNamedArguments(positional_argument_count, std::move(names)); + BoundSimpleFunction::SetCallArguments(GetSignature(), parameters, named_parameters); } bool BoundTableFunction::operator==(const BoundTableFunction &rhs) const { diff --git a/src/duckdb/src/function/window/window_aggregate_states.cpp b/src/duckdb/src/function/window/window_aggregate_states.cpp index 75de72a3c..a11fbfe1c 100644 --- a/src/duckdb/src/function/window/window_aggregate_states.cpp +++ b/src/duckdb/src/function/window/window_aggregate_states.cpp @@ -4,15 +4,18 @@ namespace duckdb { WindowAggregateStates::WindowAggregateStates(ClientContext &client, const AggregateObject &aggr) : client(client), aggr(aggr), state_size(aggr.function.GetStateSize(aggr.GetFunctionData())), - allocator(Allocator::Get(client)) { + allocator(BufferAllocator::Get(client)) { } -void WindowAggregateStates::Initialize(idx_t count) { +void WindowAggregateStates::Initialize(idx_t count_p) { // Don't leak - every Initialize must be matched with a Destroy - D_ASSERT(states.empty()); + D_ASSERT(count == 0); - states.resize(count * state_size); - auto state_ptr = states.data(); + count = count_p; + if (count > 0) { + states = BufferAllocator::Get(client).Allocate(count * state_size); + } + auto state_ptr = states.get(); statef = make_uniq(LogicalType::POINTER, count); auto state_f_data = FlatVector::Writer(*statef, count); @@ -37,7 +40,7 @@ void WindowAggregateStates::Finalize(Vector &result) { } void WindowAggregateStates::Destroy() { - if (states.empty()) { + if (count == 0) { return; } @@ -46,7 +49,8 @@ void WindowAggregateStates::Destroy() { aggr.function.GetStateDestructorCallback()(*statef, aggr_input_data, GetCount()); } - states.clear(); + states.Reset(); + count = 0; } } // namespace duckdb diff --git a/src/duckdb/src/function/window/window_collection.cpp b/src/duckdb/src/function/window/window_collection.cpp index 370065e92..5a1b0875f 100644 --- a/src/duckdb/src/function/window/window_collection.cpp +++ b/src/duckdb/src/function/window/window_collection.cpp @@ -37,20 +37,24 @@ void WindowCollection::GetCollection(idx_t row_idx, ColumnDataCollectionSpec &sp } void WindowCollection::Combine(const ColumnSet &validity_cols) { - lock_guard collection_guard(lock); + CallOnce(combine_once, [&] { + try { + CombineInternal(validity_cols); + } catch (...) { + combine_error = std::current_exception(); + } + }); + if (combine_error) { + std::rethrow_exception(combine_error); + } +} +void WindowCollection::CombineInternal(const ColumnSet &validity_cols) { // If there are no columns (COUNT(*)) then this is a NOP if (types.empty()) { return; } - // Have we already combined? - if (inputs->Count()) { - D_ASSERT(collections.empty()); - D_ASSERT(ranges.empty()); - return; - } - // If there are columns, we should have data D_ASSERT(!collections.empty()); D_ASSERT(!ranges.empty()); diff --git a/src/duckdb/src/function/window/window_distinct_aggregator.cpp b/src/duckdb/src/function/window/window_distinct_aggregator.cpp index 182d9ff76..107f54dc9 100644 --- a/src/duckdb/src/function/window/window_distinct_aggregator.cpp +++ b/src/duckdb/src/function/window/window_distinct_aggregator.cpp @@ -45,7 +45,8 @@ class WindowDistinctSortTree : public MergeSortTree { using ZippedTuple = std::tuple; using ZippedElements = vector; - WindowDistinctSortTree(WindowDistinctAggregatorGlobalState &gdastate, idx_t count) : gdastate(gdastate) { + WindowDistinctSortTree(Allocator &allocator, WindowDistinctAggregatorGlobalState &gdastate, idx_t count) + : MergeSortTree(allocator), gdastate(gdastate) { // Set up for parallel build build_level = 0; build_complete = 0; @@ -118,7 +119,8 @@ WindowDistinctAggregatorGlobalState::WindowDistinctAggregatorGlobalState(ClientC const WindowDistinctAggregator &aggregator, idx_t group_count) : WindowAggregatorGlobalState(client, aggregator, group_count), stage(WindowDistinctSortStage::INIT), - tasks_assigned(0), tasks_completed(0), merge_sort_tree(*this, group_count), levels_flat_native(client, aggr) { + tasks_assigned(0), tasks_completed(0), zipped_tree(BufferAllocator::Get(client)), + merge_sort_tree(BufferAllocator::Get(client), *this, group_count), levels_flat_native(client, aggr) { // 1: functionComputePrevIdcs(𝑖𝑛) // 2: sorted ← [] // We sort the aggregate arguments and use the partition index as a tie-breaker. @@ -160,8 +162,8 @@ WindowDistinctAggregatorGlobalState::WindowDistinctAggregatorGlobalState(ClientC merge_sort_tree.tree.reserve(zipped_tree.tree.size()); for (idx_t level_nr = 0; level_nr < zipped_tree.tree.size(); ++level_nr) { auto &zipped_level = zipped_tree.tree[level_nr].first; - WindowDistinctSortTree::Elements level; - WindowDistinctSortTree::Offsets cascades; + WindowDistinctSortTree::Elements level(merge_sort_tree.allocator); + WindowDistinctSortTree::Offsets cascades(merge_sort_tree.allocator); level.resize(zipped_level.size()); merge_sort_tree.tree.emplace_back(std::move(level), std::move(cascades)); } diff --git a/src/duckdb/src/function/window/window_merge_sort_tree.cpp b/src/duckdb/src/function/window/window_merge_sort_tree.cpp index 035dde505..0d330ee74 100644 --- a/src/duckdb/src/function/window/window_merge_sort_tree.cpp +++ b/src/duckdb/src/function/window/window_merge_sort_tree.cpp @@ -16,10 +16,10 @@ WindowMergeSortTree::WindowMergeSortTree(ClientContext &client, const vector::max() && !force_external) { index_type = LogicalType::INTEGER; - mst32 = make_uniq(); + mst32 = make_uniq(BufferAllocator::Get(client)); } else { index_type = LogicalType::BIGINT; - mst64 = make_uniq(); + mst64 = make_uniq(BufferAllocator::Get(client)); } vector orders; diff --git a/src/duckdb/src/function/window/window_segment_tree.cpp b/src/duckdb/src/function/window/window_segment_tree.cpp index 80e4aeca7..7ff165776 100644 --- a/src/duckdb/src/function/window/window_segment_tree.cpp +++ b/src/duckdb/src/function/window/window_segment_tree.cpp @@ -1,5 +1,6 @@ #include "duckdb/function/window/window_segment_tree.hpp" +#include "duckdb/common/error_data.hpp" #include "duckdb/function/window/window_aggregate_states.hpp" #include "duckdb/planner/expression/bound_window_expression.hpp" @@ -52,6 +53,11 @@ class WindowSegmentTreeGlobalState : public WindowAggregatorGlobalState { unique_ptr build_started; //! The number of entries completed so far at each level unique_ptr build_completed; + //! Whether building an entry failed - the level being built will then never be completed + std::atomic build_failed; + //! The error that made building an entry fail + mutex build_lock; + ErrorData build_error; //! The tree allocators. //! We need to hold onto them for the tree lifetime, //! not the lifetime of the local state that constructed part of the tree @@ -82,6 +88,8 @@ class WindowSegmentTreePart { data_ptr_t current_state); //! Writes result and calls destructors void Finalize(Vector &result, idx_t count); + //! Calls the destructors of the accumulation states (statef) without finalizing + void Destroy(idx_t count); void Combine(WindowSegmentTreePart &other, idx_t count); @@ -297,6 +305,14 @@ void WindowSegmentTreePart::Finalize(Vector &result, idx_t count) { } } +void WindowSegmentTreePart::Destroy(idx_t count) { + if (!aggr.function.HasStateDestructorCallback()) { + return; + } + AggregateFinalizeInputData aggr_input_data(aggr, allocator); + aggr.function.GetStateDestructorCallback()(statef, aggr_input_data, count); +} + WindowSegmentTreeGlobalState::WindowSegmentTreeGlobalState(ClientContext &client, const WindowSegmentTree &aggregator, idx_t group_count, const ValidityMask &partition_mask) : WindowAggregatorGlobalState(client, aggregator, group_count), tree(aggregator), levels_flat_native(client, aggr) { @@ -332,6 +348,7 @@ WindowSegmentTreeGlobalState::WindowSegmentTreeGlobalState(ClientContext &client // Start by building from the bottom level build_level = 0; + build_failed = false; build_started = make_uniq(levels_flat_start.size()); for (auto &counter : *build_started) { @@ -382,14 +399,20 @@ void WindowSegmentTreeLocalState::Finalize(ExecutionContext &context, WindowAggr } const idx_t build_count = (level_size + gstate.TREE_FANOUT - 1) / gstate.TREE_FANOUT; - // Build the next fan-in - const idx_t build_idx = (*gstate.build_started).at(level_current)++; - if (build_idx >= build_count) { + // Keep adjacent leaves on the same cursor to reuse its pinned input chunk. + const idx_t build_batch = + level_current == 0 ? MaxValue(1, STANDARD_VECTOR_SIZE / gstate.TREE_FANOUT) : 1; + const idx_t build_begin = (*gstate.build_started).at(level_current).fetch_add(build_batch); + if (build_begin >= build_count) { // Nothing left at this level, so wait until other threads are done. - // Since we are only building TREE_FANOUT values at a time, this will be quick. - while (level_current == gstate.build_level.load()) { + while (level_current == gstate.build_level.load() && !gstate.build_failed) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } + if (gstate.build_failed) { + // another thread failed to build its part of the level, so the tree will never be complete + const lock_guard guard(gstate.build_lock); + gstate.build_error.Throw(); + } continue; } @@ -399,28 +422,43 @@ void WindowSegmentTreeLocalState::Finalize(ExecutionContext &context, WindowAggr combine_begin = partition_offsets.rend(); } - // Check for crossing a partition boundary - // Note that it is OK for a state to _start_ on a partition boundary - // - it just shouldn't _contain_ one. - const idx_t combine_left = build_idx * combine_width; - const idx_t combine_right = MinValue(combine_left + combine_width, leaf_count); - combine_begin = std::lower_bound(partition_offsets.rbegin(), combine_begin, combine_left, combine_cmp); - auto combine_end = std::lower_bound(partition_offsets.rbegin(), combine_begin, combine_right - 1, combine_cmp); - const bool has_boundary = (combine_begin != combine_end); - combine_begin = combine_end; - - // compute the aggregate for this entry in the segment tree - if (!has_boundary) { - const idx_t pos = build_idx * gstate.TREE_FANOUT; - const idx_t levels_flat_offset = levels_flat_start[level_current] + build_idx; - auto state_ptr = levels_flat_native.GetStatePtr(levels_flat_offset); - gtstate.WindowSegmentValue(gstate, level_current, pos, MinValue(level_size, pos + gstate.TREE_FANOUT), - state_ptr); - gtstate.FlushStates(level_current > 0); + const idx_t build_end = MinValue(build_begin + build_batch, build_count); + for (idx_t build_idx = build_begin; build_idx < build_end; ++build_idx) { + // Check for crossing a partition boundary + // Note that it is OK for a state to _start_ on a partition boundary + // - it just shouldn't _contain_ one. + const idx_t combine_left = build_idx * combine_width; + const idx_t combine_right = MinValue(combine_left + combine_width, leaf_count); + combine_begin = std::lower_bound(partition_offsets.rbegin(), combine_begin, combine_left, combine_cmp); + auto combine_end = + std::lower_bound(partition_offsets.rbegin(), combine_begin, combine_right - 1, combine_cmp); + const bool has_boundary = (combine_begin != combine_end); + combine_begin = combine_end; + + // compute the aggregate for this entry in the segment tree + if (!has_boundary) { + const idx_t pos = build_idx * gstate.TREE_FANOUT; + const idx_t levels_flat_offset = levels_flat_start[level_current] + build_idx; + auto state_ptr = levels_flat_native.GetStatePtr(levels_flat_offset); + try { + gtstate.WindowSegmentValue(gstate, level_current, pos, + MinValue(level_size, pos + gstate.TREE_FANOUT), state_ptr); + gtstate.FlushStates(level_current > 0); + } catch (std::exception &ex) { + // wake up the threads waiting for this level to be completed + { + const lock_guard guard(gstate.build_lock); + gstate.build_error = ErrorData(ex); + } + gstate.build_failed = true; + throw; + } + } } // If that was the last one, mark the level as complete. - const idx_t build_complete = ++(*gstate.build_completed).at(level_current); + const idx_t completed = build_end - build_begin; + const idx_t build_complete = (*gstate.build_completed).at(level_current).fetch_add(completed) + completed; if (build_complete == build_count) { gstate.build_level++; continue; @@ -466,6 +504,10 @@ void WindowSegmentTreeLocalState::Evaluate(ExecutionContext &context, const Wind // 4. combine the buffer state into the Segment Tree State part->Combine(*right_part, count); + + // right_part's states are re-initialized on every chunk, so destroy them after combining to avoid leaking + // aggregate states that own heap memory (e.g. histogram, reservoir_quantile) + right_part->Destroy(count); } else { part->Evaluate(gtstate, window_begin, window_end, nullptr, result, count, row_idx, WindowSegmentTreePart::FULL); } diff --git a/src/duckdb/src/function/window/window_token_tree.cpp b/src/duckdb/src/function/window/window_token_tree.cpp index 36673fd27..81dfe8b1e 100644 --- a/src/duckdb/src/function/window/window_token_tree.cpp +++ b/src/duckdb/src/function/window/window_token_tree.cpp @@ -55,8 +55,9 @@ idx_t WindowTokenTree::MeasurePayloadBlocks() { return count; } -template -static void BuildTokens(WindowTokenTree &token_tree, vector &tokens) { +template +static void BuildTokens(WindowTokenTree &token_tree, TOKENS &tokens) { + using T = typename TOKENS::value_type; auto &collection = *token_tree.sorted; if (!collection.Count()) { return; diff --git a/src/duckdb/src/include/duckdb.h b/src/duckdb/src/include/duckdb.h index 608800432..517bb60b1 100644 --- a/src/duckdb/src/include/duckdb.h +++ b/src/duckdb/src/include/duckdb.h @@ -404,6 +404,7 @@ typedef enum duckdb_error_type { DUCKDB_ERROR_SEQUENCE = 41, DUCKDB_ERROR_DATA_CORRUPTION = 43, DUCKDB_INVALID_CONFIGURATION = 42, + DUCKDB_ERROR_RESOURCE_IN_USE = 44, } duckdb_error_type; /*! @@ -2244,10 +2245,10 @@ DUCKDB_C_API duckdb_error_data duckdb_appender_error_data(duckdb_appender append #if DUCKDB_API_VERSION_AT_LEAST(0, 2, 5) /*! - * Flush the appender to the table, forcing the cache of the appender to be cleared. If flushing the data triggers a - * constraint violation or any other error, then all data is invalidated, and this function returns DuckDBError. It is - * not possible to append more values. Call duckdb_appender_error_data to obtain the error data followed by - * duckdb_appender_destroy to destroy the invalidated appender. + * Flush the appender to the table, forcing the cache of the appender to be cleared. Requires a connection with no open + * result. If flushing the data triggers a constraint violation or any other error, then all data is invalidated, and + * this function returns DuckDBError. It is not possible to append more values. Call duckdb_appender_error_data to + * obtain the error data followed by duckdb_appender_destroy to destroy the invalidated appender. * * history: * - stable: v0.2.5 @@ -5630,12 +5631,11 @@ DUCKDB_C_API duckdb_state duckdb_pending_prepared(duckdb_prepared_statement prep * * Destroying a pending result whose statement has not completed aborts the statement. On autocommit, its writes are * rolled back. Inside a transaction, a statement that may write invalidates the transaction, and a read-only statement - * just stops. Preparing or running another statement on the connection aborts a statement that has not completed the - * same way, so a COMMIT issued before a statement that may write completed rolls the transaction back. - * `duckdb_pending_execution_is_finished` reporting true does not mean that the statement completed. To make sure a - * statement takes effect, complete it before destroying the pending result: call `duckdb_execute_pending`, and fetch a - * streaming result until `duckdb_fetch_chunk` returns NULL. A NULL chunk is also returned on an error, so check - * `duckdb_result_error` afterwards. + * just stops. Until the statement has completed, preparing or running another statement on the connection fails with + * `DUCKDB_ERROR_RESOURCE_IN_USE` and leaves it untouched. `duckdb_pending_execution_is_finished` reporting true does + * not mean that the statement completed. To make sure a statement takes effect, complete it before destroying the + * pending result: call `duckdb_execute_pending`, and fetch a streaming result until `duckdb_fetch_chunk` returns NULL. + * A NULL chunk is also returned on an error, so check `duckdb_result_error` afterwards. * * history: * - stable: v0.5.0 @@ -6513,10 +6513,10 @@ DUCKDB_C_API duckdb_state duckdb_query(duckdb_connection connection, const char * * Destroying a streaming result whose statement has not completed aborts the statement. On autocommit, its writes are * rolled back. Inside a transaction, a statement that may write invalidates the transaction, and a read-only statement - * just stops. Preparing or running another statement on the connection aborts a statement that has not completed the - * same way, so a COMMIT issued before a statement that may write completed rolls the transaction back. To make sure the - * statement takes effect, complete it by fetching until `duckdb_fetch_chunk` returns NULL before destroying the result. - * A NULL chunk is also returned on an error, so check `duckdb_result_error` afterwards. + * just stops. Until the statement has completed, preparing or running another statement on the connection fails with + * `DUCKDB_ERROR_RESOURCE_IN_USE` and leaves it untouched. To make sure the statement takes effect, complete it by + * fetching until `duckdb_fetch_chunk` returns NULL before destroying the result. A NULL chunk is also returned on an + * error, so check `duckdb_result_error` afterwards. * * history: * - stable: v0.1.0 diff --git a/src/duckdb/src/include/duckdb/catalog/catalog_entry/column_dependency_manager.hpp b/src/duckdb/src/include/duckdb/catalog/catalog_entry/column_dependency_manager.hpp index 51cc82862..c28587527 100644 --- a/src/duckdb/src/include/duckdb/catalog/catalog_entry/column_dependency_manager.hpp +++ b/src/duckdb/src/include/duckdb/catalog/catalog_entry/column_dependency_manager.hpp @@ -38,6 +38,9 @@ class ColumnDependencyManager { //! Removes the column(s) and outputs the new column indices vector RemoveColumn(LogicalIndex index, idx_t column_amount); + //! Return a deep copy (the copy constructor is deleted to prevent accidental copies) + ColumnDependencyManager Copy() const; + bool IsDependencyOf(LogicalIndex dependent, LogicalIndex dependency) const; bool HasDependencies(LogicalIndex index) const; const logical_index_set_t &GetDependencies(LogicalIndex index) const; diff --git a/src/duckdb/src/include/duckdb/common/adbc/wrappers.hpp b/src/duckdb/src/include/duckdb/common/adbc/wrappers.hpp index ca71e4e24..b22fda3ae 100644 --- a/src/duckdb/src/include/duckdb/common/adbc/wrappers.hpp +++ b/src/duckdb/src/include/duckdb/common/adbc/wrappers.hpp @@ -34,6 +34,8 @@ struct DuckDBAdbcConnectionWrapper { void UnregisterStream(duckdb_adbc::DuckDBAdbcStreamWrapper *stream); //! Materialize all active streams, fetching remaining data into memory. void MaterializeStreams(); + //! Ends every open stream on this connection, which then reports `reason` once its materialized arrays are read + void CloseStreams(const char *reason); //! Detach all streams from this connection and clear the list (called on connection release). void DetachAndClearStreams(); diff --git a/src/duckdb/src/include/duckdb/common/clustered_aggregate.hpp b/src/duckdb/src/include/duckdb/common/clustered_aggregate.hpp index 85f580615..5998c383c 100644 --- a/src/duckdb/src/include/duckdb/common/clustered_aggregate.hpp +++ b/src/duckdb/src/include/duckdb/common/clustered_aggregate.hpp @@ -62,7 +62,7 @@ struct ClusteredAggr { struct GroupRun { data_ptr_t state; //! caller fills this after TryClustered; advanced between aggregates - const sel_t *sel; //! points to the tuple positions for this run + const sel_t *sel; //! strictly increasing input row positions, or nullptr for 0..count-1 uint64_t gid; //! raw group id for this run idx_t count; //! number of tuples in this group }; diff --git a/src/duckdb/src/include/duckdb/common/compressed_file_system.hpp b/src/duckdb/src/include/duckdb/common/compressed_file_system.hpp index 9ef129c70..515c2fb43 100644 --- a/src/duckdb/src/include/duckdb/common/compressed_file_system.hpp +++ b/src/duckdb/src/include/duckdb/common/compressed_file_system.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/allocator.hpp" #include "duckdb/common/atomic.hpp" #include "duckdb/common/common.hpp" #include "duckdb/common/file_system.hpp" @@ -20,8 +21,8 @@ struct StreamData { // various buffers & pointers bool write = false; bool refresh = false; - unsafe_unique_array in_buff; - unsafe_unique_array out_buff; + AllocatedData in_buff; + AllocatedData out_buff; data_ptr_t out_buff_start = nullptr; data_ptr_t out_buff_end = nullptr; data_ptr_t in_buff_start = nullptr; diff --git a/src/duckdb/src/include/duckdb/common/enum_util.hpp b/src/duckdb/src/include/duckdb/common/enum_util.hpp index 3776a8a7c..6a2297a91 100644 --- a/src/duckdb/src/include/duckdb/common/enum_util.hpp +++ b/src/duckdb/src/include/duckdb/common/enum_util.hpp @@ -154,6 +154,8 @@ enum class CompressionValidity : uint8_t; enum class ConflictManagerMode : uint8_t; +enum class ConnectionType : uint8_t; + enum class ConstraintType : uint8_t; enum class CoordinateReferenceSystemType : uint8_t; @@ -220,6 +222,8 @@ enum class ExponentType : uint8_t; enum class ExpressionClass : uint8_t; +enum class ExpressionDepthKind : uint8_t; + enum class ExpressionTailType : uint8_t; enum class ExpressionType : uint8_t; @@ -494,8 +498,6 @@ enum class RequestSizing : uint8_t; enum class RequestType : uint8_t; -enum class ResultEagerness : uint8_t; - enum class ResultLifetime : uint8_t; enum class ResultModifierType : uint8_t; @@ -850,6 +852,9 @@ const char* EnumUtil::ToChars(CompressionValidity value); template<> const char* EnumUtil::ToChars(ConflictManagerMode value); +template<> +const char* EnumUtil::ToChars(ConnectionType value); + template<> const char* EnumUtil::ToChars(ConstraintType value); @@ -949,6 +954,9 @@ const char* EnumUtil::ToChars(ExponentType value); template<> const char* EnumUtil::ToChars(ExpressionClass value); +template<> +const char* EnumUtil::ToChars(ExpressionDepthKind value); + template<> const char* EnumUtil::ToChars(ExpressionTailType value); @@ -1360,9 +1368,6 @@ const char* EnumUtil::ToChars(RequestSizing value); template<> const char* EnumUtil::ToChars(RequestType value); -template<> -const char* EnumUtil::ToChars(ResultEagerness value); - template<> const char* EnumUtil::ToChars(ResultLifetime value); @@ -1802,6 +1807,9 @@ CompressionValidity EnumUtil::FromString(const char *value) template<> ConflictManagerMode EnumUtil::FromString(const char *value); +template<> +ConnectionType EnumUtil::FromString(const char *value); + template<> ConstraintType EnumUtil::FromString(const char *value); @@ -1901,6 +1909,9 @@ ExponentType EnumUtil::FromString(const char *value); template<> ExpressionClass EnumUtil::FromString(const char *value); +template<> +ExpressionDepthKind EnumUtil::FromString(const char *value); + template<> ExpressionTailType EnumUtil::FromString(const char *value); @@ -2312,9 +2323,6 @@ RequestSizing EnumUtil::FromString(const char *value); template<> RequestType EnumUtil::FromString(const char *value); -template<> -ResultEagerness EnumUtil::FromString(const char *value); - template<> ResultLifetime EnumUtil::FromString(const char *value); diff --git a/src/duckdb/src/include/duckdb/common/enums/connection_type.hpp b/src/duckdb/src/include/duckdb/common/enums/connection_type.hpp new file mode 100644 index 000000000..b6193a260 --- /dev/null +++ b/src/duckdb/src/include/duckdb/common/enums/connection_type.hpp @@ -0,0 +1,22 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// duckdb/common/enums/connection_type.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/common/constants.hpp" + +namespace duckdb { + +enum class ConnectionType : uint8_t { + //! A connection opened by a user + USER, + //! A connection DuckDB opened for its own use (e.g. a checkpoint) + INTERNAL, +}; + +} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/enums/result_eagerness.hpp b/src/duckdb/src/include/duckdb/common/enums/result_eagerness.hpp deleted file mode 100644 index 491bbba77..000000000 --- a/src/duckdb/src/include/duckdb/common/enums/result_eagerness.hpp +++ /dev/null @@ -1,20 +0,0 @@ -//===----------------------------------------------------------------------===// -// DuckDB -// -// duckdb/common/enums/result_eagerness.hpp -// -// -//===----------------------------------------------------------------------===// - -#pragma once - -#include "duckdb/common/constants.hpp" - -namespace duckdb { - -//! When the retention of a result is settled. FORCED settles it on retained at submission, so -//! producers never wait for a consumer and no stream can be opened on the handle. AUTO leaves the -//! decision to the statement and the consumer -enum class ResultEagerness : uint8_t { FORCED, AUTO }; - -} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/enums/statement_type.hpp b/src/duckdb/src/include/duckdb/common/enums/statement_type.hpp index b11cf6f00..d458a00c6 100644 --- a/src/duckdb/src/include/duckdb/common/enums/statement_type.hpp +++ b/src/duckdb/src/include/duckdb/common/enums/statement_type.hpp @@ -9,7 +9,6 @@ #pragma once #include "duckdb/common/constants.hpp" -#include "duckdb/common/enums/result_eagerness.hpp" #include "duckdb/common/identifier.hpp" #include "duckdb/common/optional_idx.hpp" #include "duckdb/common/unordered_set.hpp" @@ -75,8 +74,8 @@ class ClientContext; //! A struct containing various properties of a SQL statement struct StatementProperties { StatementProperties() - : requires_valid_transaction(true), result_eagerness(ResultEagerness::FORCED), bound_all_parameters(true), - return_type(StatementReturnType::QUERY_RESULT), parameter_count(0), always_require_rebind(false) { + : requires_valid_transaction(true), bound_all_parameters(true), return_type(StatementReturnType::QUERY_RESULT), + parameter_count(0), always_require_rebind(false) { } struct CatalogIdentity { @@ -104,9 +103,6 @@ struct StatementProperties { //! Whether or not the statement requires a valid transaction. Almost all statements require this, with the //! exception of ROLLBACK bool requires_valid_transaction; - //! FORCED for a statement whose side effects cannot wait for a consumer: it runs to completion - //! before the call that returns its result does, and its result is never streamed - ResultEagerness result_eagerness; //! Whether or not all parameters have successfully had their types determined bool bound_all_parameters; //! What type of data the statement returns @@ -116,7 +112,7 @@ struct StatementProperties { //! Whether or not the statement ALWAYS requires a rebind bool always_require_rebind; - bool IsReadOnly() { + bool IsReadOnly() const { return modified_databases.empty(); } diff --git a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_function.hpp b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_function.hpp index c8bd0884e..5a34fef5e 100644 --- a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_function.hpp +++ b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_function.hpp @@ -118,6 +118,7 @@ class MultiFileFunction : public TableFunction { MultiFileOptions file_options_p, unique_ptr options_p, unique_ptr interface_p) { + file_options_p.ValidateBoundSchema(); auto &interface = *interface_p; auto result = make_uniq(); @@ -1161,6 +1162,10 @@ class MultiFileFunction : public TableFunction { } } + if (!bind_data.file_options.bound_schema.empty() && bind_data.IsGeneratedColumn(primary_index)) { + // A physical shadow's bounds do not describe the generated path. Explicit scan statistics above still do. + return nullptr; + } // NOTE: we do not want to parse the file metadata for the sole purpose of getting column statistics const bool multiple_files = bind_data.file_list->GetExpandResult() == FileExpandResult::MULTIPLE_FILES; if (multiple_files && !bind_data.file_options.union_by_name) { @@ -1168,6 +1173,11 @@ class MultiFileFunction : public TableFunction { return nullptr; } auto result = bind_data.initial_reader->GetStatistics(context, col_name); + if (result && !bind_data.file_options.bound_schema.empty() && + !bind_data.MatchesPhysicalColumn(primary_index, col_name, result->GetType())) { + // Check the whole root before interpreting an extracted child's positional statistics. + return nullptr; + } if (result && multiple_files) { for (idx_t i = 1; i < bind_data.union_readers.size(); i++) { auto &union_reader = *bind_data.union_readers[i]; diff --git a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_options.hpp b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_options.hpp index c51c42a1f..bacc4526d 100644 --- a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_options.hpp +++ b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_options.hpp @@ -38,6 +38,9 @@ struct MultiFileOptions { //! (Optional) The schema of the scan, given by the "schema" option. Every file is mapped onto it - by field id //! when its columns are identified by INTEGER, by name otherwise - instead of the schema being read from the files vector schema; + //! (Optional) Complete ordered scan schema, mapped by name before conversion. Unlike schema, no defaults or + //! field identifiers are supplied. Physical reader columns remain independent of these output columns. + vector bound_schema; //! Whether to add a "file_row_number" column with the row number of each row within its file - it is read from the //! row number virtual column of the reader bool file_row_number = false; @@ -72,6 +75,9 @@ struct MultiFileOptions { DUCKDB_API LogicalType GetHiveLogicalType(const string &hive_partition_column) const; DUCKDB_API Value GetHivePartitionValue(const string &base, const string &entry, ClientContext &context) const; DUCKDB_API bool AnySet() const; + DUCKDB_API void SetBoundSchema(const Value &value, ClientContext &context); + //! Validate owned declarations before binding or restoring even an empty file list. + DUCKDB_API void ValidateBoundSchema() const; //! Set "column_statistics" from an option value - see column_statistics for its shape DUCKDB_API void SetColumnStatistics(const Identifier &key, const Value &val); //! Check "column_statistics" against the scan's columns once they are bound: every key must name a column, or a diff --git a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_states.hpp b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_states.hpp index 6bc992e5e..7ccf59ddd 100644 --- a/src/duckdb/src/include/duckdb/common/multi_file/multi_file_states.hpp +++ b/src/duckdb/src/include/duckdb/common/multi_file/multi_file_states.hpp @@ -103,6 +103,14 @@ struct MultiFileBindData : public TableFunctionData { } unique_ptr Copy() const override; + + //! Whether an output column is supplied by a filename, row-number or Hive partition role. + DUCKDB_API bool IsGeneratedColumn(idx_t column_idx) const; + //! Match a physical column to an output column without a generated override or conversion (including collations). + DUCKDB_API bool MatchesPhysicalColumn(idx_t column_idx, const Identifier &physical_name, + const LogicalType &physical_type) const; + //! Whether all physical roots have the output names and types at the same positions, without generated roles. + DUCKDB_API bool HasIdentityMapping(const vector &physical_columns) const; }; //! Per-file data for the multi file reader diff --git a/src/duckdb/src/include/duckdb/common/multi_file/table_function_multi_file.hpp b/src/duckdb/src/include/duckdb/common/multi_file/table_function_multi_file.hpp index f6fc9b487..7691d6f6f 100644 --- a/src/duckdb/src/include/duckdb/common/multi_file/table_function_multi_file.hpp +++ b/src/duckdb/src/include/duckdb/common/multi_file/table_function_multi_file.hpp @@ -11,6 +11,7 @@ #include "duckdb/common/atomic.hpp" #include "duckdb/common/multi_file/multi_file_function.hpp" #include "duckdb/common/mutex.hpp" +#include "duckdb/common/once_flag.hpp" namespace duckdb { @@ -115,6 +116,9 @@ struct TableFunctionCombineSchemaInput { //! A function that implements this is scanned one batch at a time, rather than being run until it returns an empty //! chunk. This lets the scan tell the batches apart, so that batches scanned in parallel can be put back in order typedef bool (*table_function_claim_batch_t)(ClientContext &context, TableFunctionInput &input); +//! Prepares the batch a local state has just claimed - called without the lock of the scan held, so that preparing +//! batches is done in parallel. Only meaningful together with table_function_claim_batch_t +typedef void (*table_function_prepare_batch_t)(ClientContext &context, TableFunctionInput &input); //! Called when a local state will not scan any more batches - lets the function release the resources of the batch //! it scanned last. The counterpart of table_function_claim_batch_t typedef void (*table_function_finish_batch_t)(ClientContext &context, TableFunctionInput &input); @@ -189,6 +193,7 @@ struct TableFunctionMultiFileSettings { //! How the wrapped function takes part in the scan - all optional, see the typedefs above table_function_claim_batch_t claim_batch = nullptr; + table_function_prepare_batch_t prepare_batch = nullptr; table_function_finish_batch_t finish_batch = nullptr; table_function_supports_read_ahead_t supports_read_ahead = nullptr; table_function_schedule_io_t schedule_io = nullptr; @@ -253,6 +258,8 @@ class TableFunctionFileReader : public BaseFileReader { void FinishFile(ClientContext &context, GlobalTableFunctionState &gstate) override; bool TryInitializeScan(ClientContext &context, GlobalTableFunctionState &gstate, LocalTableFunctionState &lstate) override; + void PrepareScan(ClientContext &context, GlobalTableFunctionState &gstate, + LocalTableFunctionState &lstate) override; AsyncResult ScheduleIO(ClientContext &context, GlobalTableFunctionState &gstate, LocalTableFunctionState &lstate) override; AsyncResult Scan(ClientContext &context, GlobalTableFunctionState &gstate, LocalTableFunctionState &lstate, @@ -319,8 +326,8 @@ class TableFunctionFileReader : public BaseFileReader { named_argument_map_t named_parameters; //! How the wrapped function takes part in the scan TableFunctionMultiFileSettings settings; - //! Guards the initialization of the global state below - mutex lock; + //! Initializes the global state once per file reader + OnceFlag initialize_once; //! The global state of the wrapped function - shared by all threads scanning this file unique_ptr global_state; //! The number of row groups to scan of this file that has been collected into the state of the scan diff --git a/src/duckdb/src/include/duckdb/common/once_flag.hpp b/src/duckdb/src/include/duckdb/common/once_flag.hpp new file mode 100644 index 000000000..0a1d4f356 --- /dev/null +++ b/src/duckdb/src/include/duckdb/common/once_flag.hpp @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// duckdb/common/once_flag.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/common/atomic.hpp" +#include "duckdb/common/mutex.hpp" + +#include + +namespace duckdb { + +//! One-time initialization flag that stays usable when the callable throws. +//! std::call_once is built on pthread_once with libstdc++, which never resets the flag after an exception on +//! MinGW and under ThreadSanitizer, so every other caller then waits forever. +class OnceFlag { +public: + //! Runs func unless an earlier call completed; a throwing func leaves the flag unset so the next caller retries + template + void Call(FUNC &&func) { + if (done.load(std::memory_order_acquire)) { + return; + } + lock_guard guard(lock); + if (done.load(std::memory_order_relaxed)) { + return; + } + func(); + done.store(true, std::memory_order_release); + } + +private: + atomic done {false}; + mutex lock; +}; + +//! Drop-in replacement for std::call_once +template +void CallOnce(OnceFlag &flag, FUNC &&func) { + flag.Call(std::forward(func)); +} + +} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/operator/numeric_binary_operators.hpp b/src/duckdb/src/include/duckdb/common/operator/numeric_binary_operators.hpp index 380e667b6..a65cf052f 100644 --- a/src/duckdb/src/include/duckdb/common/operator/numeric_binary_operators.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/numeric_binary_operators.hpp @@ -27,8 +27,10 @@ struct TryDivideOperator { static inline bool Operation(T left, T right, T &result) { D_ASSERT(right != 0); // this should be checked before! // with a non-zero divisor, division only overflows for the minimum value divided by -1 - if (left == NumericLimits::Minimum() && right == T(-1)) { - return false; + if constexpr (NumericLimits::IsSigned()) { + if (left == NumericLimits::Minimum() && right == T(-1)) { + return false; + } } result = DivideOperator::Operation(left, right); return true; diff --git a/src/duckdb/src/include/duckdb/common/optional_idx.hpp b/src/duckdb/src/include/duckdb/common/optional_idx.hpp index 48a6c165a..540897cfe 100644 --- a/src/duckdb/src/include/duckdb/common/optional_idx.hpp +++ b/src/duckdb/src/include/duckdb/common/optional_idx.hpp @@ -9,18 +9,16 @@ #pragma once #include "duckdb/common/exception.hpp" +#include "duckdb/common/optional.hpp" namespace duckdb { class optional_idx { - static constexpr const idx_t INVALID_INDEX = idx_t(-1); - public: - constexpr optional_idx() : index(INVALID_INDEX) { + constexpr optional_idx() { } // NOLINTNEXTLINE: allow implicit conversion from idx_t - constexpr optional_idx(idx_t index_p) - : index(index_p == INVALID_INDEX ? (ThrowInvalidInitialization(), index_p) : index_p) { + constexpr optional_idx(idx_t index_p) : index(index_p) { } static optional_idx Invalid() { @@ -28,18 +26,18 @@ class optional_idx { } bool IsValid() const { - return index != INVALID_INDEX; + return index.has_value(); } void SetInvalid() { - index = INVALID_INDEX; + index.reset(); } idx_t GetIndex() const { - if (index == INVALID_INDEX) { + if (!index.has_value()) { ThrowNotSet(); } - return index; + return *index; } inline bool operator==(const optional_idx &rhs) const { @@ -51,12 +49,11 @@ class optional_idx { } private: - //! Kept out-of-line so that the throwing paths do not block inlining of the accessors - [[noreturn]] DUCKDB_API static void ThrowInvalidInitialization(); + //! Kept out-of-line so that the throwing path does not block inlining of the accessors [[noreturn]] DUCKDB_API static void ThrowNotSet(); private: - idx_t index; + optional index; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/serializer/async_memory_governor.hpp b/src/duckdb/src/include/duckdb/common/serializer/async_memory_governor.hpp index fd4174356..44f31da40 100644 --- a/src/duckdb/src/include/duckdb/common/serializer/async_memory_governor.hpp +++ b/src/duckdb/src/include/duckdb/common/serializer/async_memory_governor.hpp @@ -21,6 +21,10 @@ struct ManagedAsyncMemoryConfig { static constexpr idx_t MAX_PENDING_BYTES_PER_THREAD = 64ULL * 1024ULL * 1024ULL; //! Minimum async reservation requested per regular execution thread. static constexpr idx_t MIN_PENDING_BYTES_PER_THREAD = 8ULL * 1024ULL * 1024ULL; + //! Maximum fraction of the memory limit the async backlog may claim. + static constexpr idx_t MAX_PENDING_MEMORY_LIMIT_DIVISOR = 8ULL; + //! Maximum fraction of the memory limit the minimum async reservation may claim. + static constexpr idx_t MIN_PENDING_MEMORY_LIMIT_DIVISOR = 16ULL; //! Below this reservation, do not retain an async backlog (behave close to synchronous draining). static constexpr idx_t MIN_RESERVATION_FOR_BACKLOG = 8ULL * 1024ULL * 1024ULL; }; diff --git a/src/duckdb/src/include/duckdb/common/serializer/serializer.hpp b/src/duckdb/src/include/duckdb/common/serializer/serializer.hpp index 28bfafd84..ab043aa4f 100644 --- a/src/duckdb/src/include/duckdb/common/serializer/serializer.hpp +++ b/src/duckdb/src/include/duckdb/common/serializer/serializer.hpp @@ -406,7 +406,15 @@ class Serializer { WriteValue(value.GetIndexUnsafe()); } void WriteValue(optional_idx value) { - WriteValue(value.IsValid() ? value.GetIndex() : DConstants::INVALID_INDEX); + if (!value.IsValid()) { + WriteValue(DConstants::INVALID_INDEX); + return; + } + // INVALID_INDEX marks an unset optional_idx in the serialized format + if (value.GetIndex() == DConstants::INVALID_INDEX) { + throw InternalException("Cannot serialize an optional_idx that is set to INVALID_INDEX"); + } + WriteValue(value.GetIndex()); } void WriteValue(PerColumnMetadataBlock value) { WriteValue(value.GetPacked()); diff --git a/src/duckdb/src/include/duckdb/common/stl_allocator.hpp b/src/duckdb/src/include/duckdb/common/stl_allocator.hpp new file mode 100644 index 000000000..a11937e8f --- /dev/null +++ b/src/duckdb/src/include/duckdb/common/stl_allocator.hpp @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// duckdb/common/stl_allocator.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/common/allocator.hpp" +#include "duckdb/common/helper.hpp" + +#include + +namespace duckdb { + +//! An STL-compatible allocator that forwards to a DuckDB Allocator, e.g. the BufferAllocator so that the memory of a +//! standard container is tracked by the buffer manager. +template +class stl_allocator { // NOLINT: match stl case +public: + //! Typedefs + typedef T value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef value_type &reference; + typedef value_type const &const_reference; + typedef value_type *pointer; + typedef value_type const *const_pointer; + + //! Propagation traits + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + using is_always_equal = std::false_type; + + //! Rebind + template + struct rebind { + using other = stl_allocator; + }; + +public: + stl_allocator(Allocator &allocator_p) noexcept // NOLINT: allow implicit conversion + : allocator(allocator_p) { + } + template + stl_allocator(const stl_allocator &other) noexcept // NOLINT: allow implicit conversion + : allocator(other.GetAllocator()) { + } + +public: + pointer allocate(size_type n) { // NOLINT: match stl case + return reinterpret_cast(allocator.get().AllocateData(n * sizeof(T))); + } + + void deallocate(pointer p, size_type n) noexcept { // NOLINT: match stl case + allocator.get().FreeData(reinterpret_cast(p), n * sizeof(T)); + } + + template + void construct(U *p, Args &&...args) { // NOLINT: match stl case + ::new (p) U(std::forward(args)...); + } + + template + void destroy(U *p) noexcept { // NOLINT: match stl case + p->~U(); + } + + size_type max_size() const noexcept { // NOLINT: match stl case + return std::numeric_limits::max() / sizeof(T); + } + +public: + Allocator &GetAllocator() const noexcept { + return allocator.get(); + } + + bool operator==(const stl_allocator &other) const noexcept { + return RefersToSameObject(allocator.get(), other.allocator.get()); + } + bool operator!=(const stl_allocator &other) const noexcept { + return !(*this == other); + } + +private: + //! Need to use std::reference_wrapper because "reference" is already a typedef + std::reference_wrapper allocator; +}; + +} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/type_visitor.hpp b/src/duckdb/src/include/duckdb/common/type_visitor.hpp index 6f5623af0..7d1e5329f 100644 --- a/src/duckdb/src/include/duckdb/common/type_visitor.hpp +++ b/src/duckdb/src/include/duckdb/common/type_visitor.hpp @@ -8,8 +8,12 @@ #pragma once +#include "duckdb/common/numeric_utils.hpp" +#include "duckdb/common/optional_idx.hpp" #include "duckdb/common/types.hpp" +#include + namespace duckdb { struct TypeVisitor { @@ -20,114 +24,135 @@ struct TypeVisitor { template static LogicalType VisitReplace(const LogicalType &type, F &&func); + +private: + struct VisitorFrame { + LogicalType original_type; + idx_t expected_children_count; + }; + + static idx_t PushChildren(const LogicalType &type, vector> &worklist); + static LogicalType PopType(vector &result_stack); + static LogicalType RebuildType(const VisitorFrame &frame, vector &result_stack); }; -template -inline LogicalType TypeVisitor::VisitReplace(const LogicalType &type, F &&func) { - switch (type.id()) { - case LogicalTypeId::STRUCT: { - if (!type.HasParameters()) { - return func(type); - } - auto children = StructType::GetChildTypes(type); - for (auto &child : children) { - child.second = VisitReplace(child.second, func); - } - return func(LogicalType::STRUCT(children)); +inline idx_t TypeVisitor::PushChildren(const LogicalType &type, vector> &worklist) { + if (!type.HasParameters()) { + return 0; } - case LogicalTypeId::TUPLE: { - if (!type.HasParameters()) { - return func(type); + + auto initial_size = worklist.size(); + switch (type.id()) { + case LogicalTypeId::STRUCT: + case LogicalTypeId::TUPLE: + for (const auto &child : StructType::GetChildTypes(type)) { + worklist.push_back(child.second); } - auto children = StructType::GetChildTypes(type); - for (auto &child : children) { - child.second = VisitReplace(child.second, func); + break; + case LogicalTypeId::UNION: + for (idx_t i = 0; i < UnionType::GetMemberCount(type); i++) { + worklist.push_back(UnionType::GetMemberType(type, i)); } - return func(LogicalType::TUPLE(children)); + break; + case LogicalTypeId::MAP: + worklist.push_back(MapType::KeyType(type)); + worklist.push_back(MapType::ValueType(type)); + break; + case LogicalTypeId::LIST: + worklist.push_back(ListType::GetChildType(type)); + break; + case LogicalTypeId::ARRAY: + worklist.push_back(ArrayType::GetChildType(type)); + break; + default: + break; } + return worklist.size() - initial_size; +} + +inline LogicalType TypeVisitor::PopType(vector &result_stack) { + auto type = std::move(result_stack.back()); + result_stack.pop_back(); + return type; +} + +inline LogicalType TypeVisitor::RebuildType(const VisitorFrame &frame, vector &result_stack) { + D_ASSERT(frame.expected_children_count > 0); + D_ASSERT(result_stack.size() >= frame.expected_children_count); + const auto &type = frame.original_type; + switch (type.id()) { + case LogicalTypeId::STRUCT: + case LogicalTypeId::TUPLE: case LogicalTypeId::UNION: { - if (!type.HasParameters()) { - return func(type); + auto children = + type.id() == LogicalTypeId::UNION ? UnionType::CopyMemberTypes(type) : StructType::GetChildTypes(type); + for (idx_t i = children.size(); i > 0; i--) { + children[i - 1].second = PopType(result_stack); } - auto children = UnionType::CopyMemberTypes(type); - for (auto &child : children) { - child.second = VisitReplace(child.second, func); + if (type.id() == LogicalTypeId::STRUCT) { + return LogicalType::STRUCT(std::move(children)); } - return func(LogicalType::UNION(children)); - } - case LogicalTypeId::LIST: { - if (!type.HasParameters()) { - return func(type); + if (type.id() == LogicalTypeId::TUPLE) { + return LogicalType::TUPLE(std::move(children)); } - const auto &child = ListType::GetChildType(type); - return func(LogicalType::LIST(VisitReplace(child, func))); - } - case LogicalTypeId::ARRAY: { - if (!type.HasParameters()) { - return func(type); - } - const auto &child = ArrayType::GetChildType(type); - return func(LogicalType::ARRAY(VisitReplace(child, func), ArrayType::GetSize(type))); + return LogicalType::UNION(std::move(children)); } case LogicalTypeId::MAP: { - if (!type.HasParameters()) { - return func(type); - } - const auto &key = MapType::KeyType(type); - const auto &value = MapType::ValueType(type); - return func(LogicalType::MAP(VisitReplace(key, func), VisitReplace(value, func))); + auto value_type = PopType(result_stack); + auto key_type = PopType(result_stack); + return LogicalType::MAP(std::move(key_type), std::move(value_type)); } + case LogicalTypeId::LIST: + return LogicalType::LIST(PopType(result_stack)); + case LogicalTypeId::ARRAY: + return LogicalType::ARRAY(PopType(result_stack), ArrayType::GetSize(type)); default: - return func(type); + throw InternalException("Cannot rebuild children of a non-nested type"); } } template -inline bool TypeVisitor::Contains(const LogicalType &type, F &&predicate) { - if (predicate(type)) { - return true; +inline LogicalType TypeVisitor::VisitReplace(const LogicalType &type, F &&func) { + vector postfix_stack; + vector> worklist {type}; + + while (!worklist.empty()) { + const auto ¤t = worklist.back().get(); + worklist.pop_back(); + auto child_count = PushChildren(current, worklist); + postfix_stack.push_back({current, child_count}); } - switch (type.id()) { - case LogicalTypeId::STRUCT: - case LogicalTypeId::TUPLE: { - if (!type.HasParameters()) { - return false; - } - for (const auto &child : StructType::GetChildTypes(type)) { - if (Contains(child.second, predicate)) { - return true; - } + + vector result_stack; + while (!postfix_stack.empty()) { + auto frame = std::move(postfix_stack.back()); + postfix_stack.pop_back(); + + if (frame.expected_children_count == 0) { + result_stack.push_back(func(frame.original_type)); + } else { + result_stack.push_back(func(RebuildType(frame, result_stack))); } - return false; } - case LogicalTypeId::UNION: - if (!type.HasParameters()) { - return false; - } - for (idx_t i = 0; i < UnionType::GetMemberCount(type); i++) { - if (Contains(UnionType::GetMemberType(type, i), predicate)) { - return true; - } - } - return false; - case LogicalTypeId::LIST: - if (!type.HasParameters()) { - return false; - } - return Contains(ListType::GetChildType(type), predicate); - case LogicalTypeId::ARRAY: - if (!type.HasParameters()) { - return false; - } - return Contains(ArrayType::GetChildType(type), predicate); - case LogicalTypeId::MAP: - if (!type.HasParameters()) { - return false; + D_ASSERT(result_stack.size() == 1); + return result_stack.back(); +} + +template +inline bool TypeVisitor::Contains(const LogicalType &type, F &&predicate) { + vector> worklist {type}; + + while (!worklist.empty()) { + const auto ¤t = worklist.back().get(); + worklist.pop_back(); + + if (predicate(current)) { + return true; } - return Contains(MapType::KeyType(type), predicate) || Contains(MapType::ValueType(type), predicate); - default: - return false; + auto child_count = PushChildren(current, worklist); + std::reverse(worklist.end() - NumericCast(child_count), worklist.end()); } + return false; } inline bool TypeVisitor::Contains(const LogicalType &type, LogicalTypeId type_id) { diff --git a/src/duckdb/src/include/duckdb/common/typed_index.hpp b/src/duckdb/src/include/duckdb/common/typed_index.hpp index 7f4dc1668..55e96138d 100644 --- a/src/duckdb/src/include/duckdb/common/typed_index.hpp +++ b/src/duckdb/src/include/duckdb/common/typed_index.hpp @@ -15,9 +15,10 @@ namespace duckdb { //! CRTP base for strongly typed index wrappers template struct TypedIndex { - TypedIndex() : index(DConstants::INVALID_INDEX) { + //! Public so derived types can inherit these via `using TypedIndex::TypedIndex` + TypedIndex() : index(DConstants::INVALID_INDEX) { // NOLINT(bugprone-crtp-constructor-accessibility) } - explicit TypedIndex(idx_t index) : index(index) { + explicit TypedIndex(idx_t index) : index(index) { // NOLINT(bugprone-crtp-constructor-accessibility) } idx_t index; diff --git a/src/duckdb/src/include/duckdb/common/types/column/column_data_allocator.hpp b/src/duckdb/src/include/duckdb/common/types/column/column_data_allocator.hpp index 8ae321eb6..ef64a0df7 100644 --- a/src/duckdb/src/include/duckdb/common/types/column/column_data_allocator.hpp +++ b/src/duckdb/src/include/duckdb/common/types/column/column_data_allocator.hpp @@ -32,6 +32,7 @@ struct BlockMetaData { public: shared_ptr GetHandle() const; + BufferHandle Pin(BufferManager &buffer_manager); void SetHandle(ManagedResultSet &managed_result_set, shared_ptr handle); uint32_t Capacity(); }; diff --git a/src/duckdb/src/include/duckdb/common/types/column/column_data_collection_segment.hpp b/src/duckdb/src/include/duckdb/common/types/column/column_data_collection_segment.hpp index ee000c023..22a52980a 100644 --- a/src/duckdb/src/include/duckdb/common/types/column/column_data_collection_segment.hpp +++ b/src/duckdb/src/include/duckdb/common/types/column/column_data_collection_segment.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/atomic.hpp" #include "duckdb/common/types/column/column_data_allocator.hpp" #include "duckdb/common/types/column/column_data_collection.hpp" @@ -39,10 +40,13 @@ struct SwizzleMetaData { SwizzleMetaData(VectorDataIndex child_index_p, data_ptr_t ptr_p, uint16_t offset_p, uint16_t count_p) : child_index(child_index_p), ptr(ptr_p), offset(offset_p), count(count_p) { } + SwizzleMetaData(const SwizzleMetaData &other) noexcept + : SwizzleMetaData(other.child_index, other.ptr.load(std::memory_order_relaxed), other.offset, other.count) { + } //! Index of block storing heap VectorDataIndex child_index; //! Last-known pointer - data_ptr_t ptr; + atomic ptr; //! Offset into the string_t vector uint16_t offset; //! Number of strings starting at 'offset' that have strings stored in the block with index 'child_index' diff --git a/src/duckdb/src/include/duckdb/common/types/column/column_data_consumer.hpp b/src/duckdb/src/include/duckdb/common/types/column/column_data_consumer.hpp index d056cf085..8946815b7 100644 --- a/src/duckdb/src/include/duckdb/common/types/column/column_data_consumer.hpp +++ b/src/duckdb/src/include/duckdb/common/types/column/column_data_consumer.hpp @@ -55,7 +55,7 @@ class ColumnDataConsumer { public: //! Initialize the scan of the ColumnDataCollection void InitializeScan(); - //! Assign a chunk to the scan state + //! Assign a chunk to the scan state; the caller must release its handles if no chunk is assigned. bool AssignChunk(ColumnDataConsumerScanState &state); //! Scan the assigned chunk void ScanChunk(ColumnDataConsumerScanState &state, DataChunk &chunk) const; diff --git a/src/duckdb/src/include/duckdb/common/types/date.hpp b/src/duckdb/src/include/duckdb/common/types/date.hpp index 92eee3cc4..412291757 100644 --- a/src/duckdb/src/include/duckdb/common/types/date.hpp +++ b/src/duckdb/src/include/duckdb/common/types/date.hpp @@ -213,6 +213,8 @@ class Date { DUCKDB_API static int32_t ExtractWeekNumberRegular(date_t date, bool monday_first = true); //! Returns the date of the monday of the current week. DUCKDB_API static date_t GetMondayOfCurrentWeek(date_t date); + //! Subtract a number of days from a (finite) date, throwing if the result is out of range + DUCKDB_API static date_t SubtractDays(date_t date, int64_t days); //! Helper function to parse two digits from a string (e.g. "30" -> 30, "03" -> 3, "3" -> 3) DUCKDB_API static bool ParseDoubleDigit(const char *buf, idx_t len, idx_t &pos, int32_t &result); diff --git a/src/duckdb/src/include/duckdb/common/types/double_na_equal.hpp b/src/duckdb/src/include/duckdb/common/types/double_na_equal.hpp index b18c34497..1c40e02f1 100644 --- a/src/duckdb/src/include/duckdb/common/types/double_na_equal.hpp +++ b/src/duckdb/src/include/duckdb/common/types/double_na_equal.hpp @@ -28,7 +28,7 @@ struct double_na_equal { if (std::isnan(val) && std::isnan(right)) { return true; } - return val == right; + return val == right && std::signbit(val) == std::signbit(right); } bool operator!=(const double &right) const { @@ -52,7 +52,7 @@ struct float_na_equal { if (std::isnan(val) && std::isnan(right)) { return true; } - return val == right; + return val == right && std::signbit(val) == std::signbit(right); } bool operator!=(const float &right) const { diff --git a/src/duckdb/src/include/duckdb/common/types/timestamp.hpp b/src/duckdb/src/include/duckdb/common/types/timestamp.hpp index fbe421c4f..4865158bc 100644 --- a/src/duckdb/src/include/duckdb/common/types/timestamp.hpp +++ b/src/duckdb/src/include/duckdb/common/types/timestamp.hpp @@ -70,6 +70,16 @@ class Timestamp { DUCKDB_API static date_t GetDate(timestamp_t timestamp); DUCKDB_API static date_t GetDateNS(timestamp_ns_t timestamp); + // Split a time at some precision, producing positive values. + static inline void SplitTime(const int64_t value, const int64_t precision, int64_t &whole, int64_t &frac) { + whole = value / precision; + frac = value % precision; + if (frac < 0) { + frac += precision; + --whole; + } + } + DUCKDB_API static dtime_t GetTime(timestamp_t timestamp); DUCKDB_API static dtime_ns_t GetTimeNs(timestamp_ns_t timestamp); //! Create a Timestamp object from a specified (date, time) combination diff --git a/src/duckdb/src/include/duckdb/common/types/type_manager.hpp b/src/duckdb/src/include/duckdb/common/types/type_manager.hpp index e38def071..401df3371 100644 --- a/src/duckdb/src/include/duckdb/common/types/type_manager.hpp +++ b/src/duckdb/src/include/duckdb/common/types/type_manager.hpp @@ -22,6 +22,8 @@ class TypeManager { //! Try to parse and bind a logical type from a string. Throws an exception if the type could not be parsed. LogicalType ParseLogicalType(const string &type_str, ClientContext &context) const; + //! Parse and bind a logical type from a string with the default parser, resolving the built-in types only. + LogicalType ParseLogicalType(const string &type_str) const; //! Get the TypeManager from the DatabaseInstance static TypeManager &Get(DatabaseInstance &db); diff --git a/src/duckdb/src/include/duckdb/common/vector/dictionary_vector.hpp b/src/duckdb/src/include/duckdb/common/vector/dictionary_vector.hpp index 0a181002f..6190cc143 100644 --- a/src/duckdb/src/include/duckdb/common/vector/dictionary_vector.hpp +++ b/src/duckdb/src/include/duckdb/common/vector/dictionary_vector.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/once_flag.hpp" #include "duckdb/common/types/vector.hpp" namespace duckdb { @@ -26,7 +27,7 @@ class DictionaryEntry { //! fall-through), making it a global dictionary. Set only via CreateReusableGlobalDictionary. bool global_dictionary = false; //! For caching the hashes of a child buffer (mutable: cache is logically const) - mutable mutex cached_hashes_lock; + mutable OnceFlag cached_hashes_once; mutable unique_ptr cached_hashes; }; diff --git a/src/duckdb/src/include/duckdb/common/vector_operations/aggregate_executor.hpp b/src/duckdb/src/include/duckdb/common/vector_operations/aggregate_executor.hpp index 82375f252..618bc14ff 100644 --- a/src/duckdb/src/include/duckdb/common/vector_operations/aggregate_executor.hpp +++ b/src/duckdb/src/include/duckdb/common/vector_operations/aggregate_executor.hpp @@ -8,6 +8,8 @@ #pragma once +#include "duckdb/common/bit_utils.hpp" +#include "duckdb/common/bitset.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/common/smaller_binary.hpp" #include "duckdb/common/types/vector.hpp" @@ -443,7 +445,35 @@ class AggregateExecutor { } } - template + template + static inline bool UpdateUnaryClusteredRange(const INPUT_TYPE *__restrict vals, LOCAL_TYPE &local, idx_t start, + idx_t count, const ValidityMask &mask) { + bool saw_value = false; + const auto end = start + count; + while (start < end) { + const auto offset = start % ValidityMask::BITS_PER_VALUE; + const auto n = MinValue(end - start, ValidityMask::BITS_PER_VALUE - offset); + const auto range_mask = ValidityMask::EntryWithValidBits(n); + auto bits = (mask.GetValidityEntry(start / ValidityMask::BITS_PER_VALUE) >> offset) & range_mask; + if (bits == range_mask) { + saw_value = true; + for (idx_t i = start; i < start + n; i++) { + OP::template UpdateClusteredLocal(local, vals[i]); + } + } else { + while (bits) { + const auto idx = start + CountZeros::Trailing(bits); + OP::template UpdateClusteredLocal(local, vals[idx]); + bits &= bits - 1; + saw_value = true; + } + } + start += n; + } + return saw_value; + } + + template static void ExecuteUnaryClusteredOpt(const INPUT_TYPE *vals, const ClusteredAggr &clustered, const ValidityMask &validity, const SelectionVector *isel = nullptr, const sel_t *cluster_iter = nullptr) { @@ -476,6 +506,15 @@ class AggregateExecutor { local_type local; OP::template InitializeClusteredLocal(local, state); auto run_count = run.count; + if constexpr (SPARSE) { + if (run_count && (!run.sel || run.sel[run_count - 1] - run.sel[0] == run_count - 1)) { + const auto start = run.sel ? run.sel[0] : 0; + auto saw_value = UpdateUnaryClusteredRange(vals, local, start, + run_count, validity); + OP::template FlushClusteredLocal(state, local, saw_value); + continue; + } + } auto saw_value = UpdateUnaryClusteredOpt( vals, local, run_count, validity, run.sel); OP::template FlushClusteredLocal(state, local, saw_value); @@ -519,6 +558,24 @@ class AggregateExecutor { } } + static inline bool IsSparseClusteredInput(const ValidityMask &mask, idx_t count) { + const auto max_valid = count / 4; + idx_t valid_count = 0; + const auto full_entries = count / ValidityMask::BITS_PER_VALUE; + for (idx_t entry_idx = 0; entry_idx < full_entries; entry_idx++) { + valid_count += bitset(mask.GetValidityEntry(entry_idx)).count(); + if (valid_count > max_valid) { + return false; + } + } + const auto tail_count = count % ValidityMask::BITS_PER_VALUE; + if (tail_count) { + const auto entry = mask.GetValidityEntry(full_entries) & ValidityMask::EntryWithValidBits(tail_count); + valid_count += bitset(entry).count(); + } + return valid_count <= max_valid; + } + template static void ExecuteUnaryClusteredOpt(Vector &input, const ClusteredAggr &clustered, idx_t count) { auto vals = FlatVector::GetData(input); @@ -533,6 +590,10 @@ class AggregateExecutor { OP::template FlushClusteredLocal(state, local, saw_value); return; } + if (OP::IgnoreNull() && validity.CanHaveNull() && IsSparseClusteredInput(validity, count)) { + ExecuteUnaryClusteredOpt(vals, clustered, validity); + return; + } ExecuteUnaryClusteredDispatch(vals, clustered, validity); } diff --git a/src/duckdb/src/include/duckdb/execution/expression_executor.hpp b/src/duckdb/src/include/duckdb/execution/expression_executor.hpp index 98440b84d..6fda56208 100644 --- a/src/duckdb/src/include/duckdb/execution/expression_executor.hpp +++ b/src/duckdb/src/include/duckdb/execution/expression_executor.hpp @@ -144,6 +144,8 @@ class ExpressionExecutor { idx_t count, SelectionVector *true_sel, SelectionVector *false_sel); idx_t Select(const BoundFunctionExpression &expr, ExpressionState *state, const SelectionVector *sel, idx_t count, SelectionVector *true_sel, SelectionVector *false_sel); + idx_t Select(const BoundOperatorExpression &expr, ExpressionState &state, optional_ptr sel, + idx_t count, optional_ptr true_sel, optional_ptr false_sel); //! Verify that the output of a step in the ExpressionExecutor is correct void Verify(const Expression &expr, Vector &result, idx_t count); diff --git a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp index c376a91b8..1fe4827a9 100644 --- a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp +++ b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp @@ -65,10 +65,55 @@ struct JoinHTScanState { Two-phase lifecycle: the constructor populates only layout-INDEPENDENT state; all layout-DEPENDENT state (layout_ptr, row matchers, tuple_size/pointer_offset/entry_size, data_collection, sink_collection, dead_end, - dict_registry) is published by FinishInitWithLayout on the first build chunk, so slot widths can be chosen from - the data's actual runtime encoding. Until then the JHT is unusable except for the null-safe Count() / - SizeInBytes() accessors; the layout-dependent accessors assert IsLayoutFinalized(). + dict_info) is published by FinishInitWithLayout on the first build chunk, so slot widths (and the dictionary + a narrowed slot indexes into) can be chosen from the data's actual runtime encoding. Until then the JHT is + unusable except for the null-safe Count() / SizeInBytes() accessors; the layout-dependent accessors assert + IsLayoutFinalized(). ResetForNewIterationSinglePartition returns the JHT to the unpublished state so a recursive + CTE iteration publishes afresh. */ +//! One build payload column of the dict-surviving optimisation: the row store carries a narrow index into `entry` +//! instead of the native value. +struct DictSurvivingColumn { + //! Byte width of the narrowed dict-index slot (1/2/4), 0 when the column is stored at native width + uint8_t index_width = 0; + //! Self-owned copy of the upstream dictionary plus one trailing NULL slot; set iff narrowed + buffer_ptr entry; + //! Id of the upstream dictionary the column was narrowed against; later chunks must carry the same id + string upstream_id; + + //! The reserved trailing slot that encodes a NULL-only chunk + idx_t NullIndex() const { + return entry->data.size() - 1; + } +}; + +//! Descriptor of the dict-surviving optimisation for one hash join. Chosen by the layout publisher from the first +//! build chunk together with the layout and shared read-only by every per-thread JHT. Re-chosen whenever the layout +//! is (e.g. per recursive CTE iteration), since a rerun build may deliver a different dictionary or none at all. +struct DictSurvivingInfo { + //! One record per build payload column (parallel to build_types); index_width 0 = stored at native width + vector columns; + + //! True iff any column is narrowed + bool Any() const { + for (const auto &column : columns) { + if (column.index_width != 0) { + return true; + } + } + return false; + } + //! The record of build payload column build_col_idx, or nullptr when it is stored at native width + optional_ptr Get(idx_t build_col_idx) const { + if (build_col_idx >= columns.size() || columns[build_col_idx].index_width == 0) { + return nullptr; + } + return &columns[build_col_idx]; + } + //! Row-store slot type of a narrowed column of the given index width + static LogicalType IndexType(uint8_t index_width); +}; + class JoinHashTable { public: using ValidityBytes = TemplatedValidityMask; @@ -240,16 +285,31 @@ class JoinHashTable { optional_ptr predicate_ptr = nullptr, const vector &output_in_probe = {}); ~JoinHashTable(); - //! Initialize layout-dependent state from a layout shared across all per-thread JHTs (deferred ctor body) - void FinishInitWithLayout(shared_ptr published_layout, vector dict_index_width_p = {}); + //! Initialize layout-dependent state from a layout (and dict-surviving descriptor, nullptr = no narrowed column) + //! shared across all per-thread JHTs (deferred ctor body) + void FinishInitWithLayout(shared_ptr published_layout, + shared_ptr dict_info_p = nullptr); //! True iff FinishInitWithLayout has populated layout-dependent state bool IsLayoutFinalized() const { return layout_ptr.get() != nullptr; } //! Per-column index-width decision for the dict-surviving optimisation, consulted by the layout publisher on - //! the first build chunk. Returns the narrowed index byte width (1/2/4), or 0 to keep native width. + //! the first build chunk. Returns the narrowed index byte width (1/2/4), or 0 to keep native width. The width + //! leaves room for one extra slot: the reserved NULL index that encodes NULL-only chunks. uint8_t GetDictSurvivingIndexWidth(idx_t build_col_idx, const Vector &incoming) const; + //! Create the record of a narrowed column: a self-owned, global copy of the incoming global dictionary with one + //! trailing NULL slot (index = upstream size) and a fresh id, plus the width and the upstream id to check later + //! chunks against. + static DictSurvivingColumn CreateDictSurvivingColumn(const Vector &incoming, uint8_t index_width); + //! True iff any build payload column is stored as a narrow dictionary index + bool HasDictSurvivingColumns() const { + return dict_info && dict_info->Any(); + } + //! The dict-surviving record of build payload column build_col_idx, or nullptr when it is stored natively + optional_ptr GetDictSurvivingColumn(idx_t build_col_idx) const { + return dict_info ? dict_info->Get(build_col_idx) : nullptr; + } //! Add the given data to the HT void Build(PartitionedTupleDataAppendState &append_state, DataChunk &keys, DataChunk &input); @@ -410,12 +470,9 @@ class JoinHashTable { bool use_dict_emission = false; //! Pre-materialized columnar data, one entry per RHS output column vector> dict_arrays; - //! Per build payload column: pinned upstream dict entry. Non-null means the row store carries a narrow dict - //! index for this column instead of the native value. - vector> dict_registry; - //! Per build payload column: byte width of the narrowed dict-index slot (0 = native, else 1/2/4). Parallel to - //! build_types; set by FinishInitWithLayout. - vector dict_index_width; + //! Dict-surviving descriptor shared with every other per-thread JHT of the same join; set by + //! FinishInitWithLayout, nullptr when no build payload column is narrowed + shared_ptr dict_info; //! Saved NEXT_PTR values, indexed by dict index; only allocated when chains_longer_than_one AllocatedData aux_next_ptrs; //! Typed pointer into aux_next_ptrs; set by BuildDictionaryArrays alongside the allocation @@ -651,9 +708,11 @@ class JoinHashTable { private: //! True iff the residual predicate (if any) reads build payload column build_col_idx from its row slot bool ColumnReferencedByResidual(idx_t build_col_idx) const; - //! Validate the incoming dict chunk and pin a self-owned copy of its dictionary into dict_registry on the first - //! chunk; on later chunks assert id continuity. Called per narrowed column from Build. - void PinDictSurvivingColumn(idx_t build_col_idx, const Vector &incoming, uint8_t index_width); + //! Materialise the narrow index vector for a narrowed column: the dictionary indices of a chunk that wraps the + //! pinned upstream dictionary, or the reserved NULL index for a NULL-only chunk (e.g. the unmatched-build scan of + //! a FULL/RIGHT join upstream). Any other shape means the build pipeline is not single-source and throws. + void EncodeDictSurvivingColumn(const DictSurvivingColumn &column, idx_t build_col_idx, const Vector &incoming, + idx_t count, vector &buffers, vector &vectors); private: //! The current number of radix bits used to partition diff --git a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp index 8cb9823d0..4bb415cb7 100644 --- a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp +++ b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp @@ -14,6 +14,7 @@ #include "duckdb/common/mutex.hpp" #include "duckdb/common/pair.hpp" #include "duckdb/common/printer.hpp" +#include "duckdb/common/stl_allocator.hpp" #include "duckdb/common/typedefs.hpp" #include "duckdb/common/vector.hpp" #include "duckdb/common/vector_operations/aggregate_executor.hpp" @@ -77,8 +78,9 @@ template , u struct MergeSortTree { using ElementType = E; using OffsetType = O; - using Elements = vector; - using Offsets = vector; + //! The levels are allocated through the tree's allocator so that their memory can be tracked + using Elements = vector>; + using Offsets = vector>; using Level = pair; using Tree = vector; @@ -105,7 +107,10 @@ struct MergeSortTree { CMP cmp; }; - explicit MergeSortTree(const CMP &cmp = CMP()) : cmp(cmp) { + explicit MergeSortTree(Allocator &allocator = Allocator::DefaultAllocator(), const CMP &cmp = CMP()) + : allocator(allocator), cmp(cmp) { + } + explicit MergeSortTree(const CMP &cmp) : MergeSortTree(Allocator::DefaultAllocator(), cmp) { } inline Elements &LowestLevel() { @@ -133,6 +138,8 @@ struct MergeSortTree { template void AggregateLowerBound(const idx_t lower, const idx_t upper, const E needle, L aggregate) const; + //! The allocator used for the levels of the tree + Allocator &allocator; Tree tree; CompareElements cmp; @@ -312,21 +319,22 @@ struct MergeSortTree { }; template -vector &MergeSortTree::Allocate(idx_t count) { +typename MergeSortTree::Elements &MergeSortTree::Allocate(idx_t count) { const auto fanout = F; const auto cascading = C; - Elements lowest_level(count); - tree.emplace_back(Level(std::move(lowest_level), Offsets())); + Elements lowest_level(allocator); + lowest_level.resize(count); + tree.emplace_back(Level(std::move(lowest_level), Offsets(allocator))); for (idx_t child_run_length = 1; child_run_length < count;) { const auto run_length = child_run_length * fanout; const auto num_runs = (count + run_length - 1) / run_length; - Elements elements; + Elements elements(allocator); elements.resize(count); // Allocate cascading pointers only if there is room - Offsets cascades; + Offsets cascades(allocator); if (cascading > 0 && run_length > cascading) { const auto num_cascades = fanout * num_runs * (run_length / cascading + 2); cascades.resize(num_cascades); diff --git a/src/duckdb/src/include/duckdb/execution/operator/csv_scanner/csv_error.hpp b/src/duckdb/src/include/duckdb/execution/operator/csv_scanner/csv_error.hpp index 765421621..fbc2cb086 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/csv_scanner/csv_error.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/csv_scanner/csv_error.hpp @@ -118,7 +118,8 @@ class CSVError { class CSVErrorHandler { public: - explicit CSVErrorHandler(bool ignore_errors = false); + //! If store_ignored_errors is false, ignored errors are dropped instead of kept for the rejects table + explicit CSVErrorHandler(bool ignore_errors = false, bool store_ignored_errors = true); //! Throws the error void Error(const CSVError &csv_error, bool force_error = false); //! If we have a cached error, and we can now error, we error. @@ -161,6 +162,8 @@ class CSVErrorHandler { unordered_map lines_per_batch_map; idx_t max_line_length = 0; bool ignore_errors = false; + //! Whether ignored errors have to be kept, e.g. to fill the rejects table + bool store_ignored_errors = true; bool print_line = true; //! Set of errors vector errors; diff --git a/src/duckdb/src/include/duckdb/execution/operator/helper/physical_limit.hpp b/src/duckdb/src/include/duckdb/execution/operator/helper/physical_limit.hpp index 2cad1e185..ba626b0aa 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/helper/physical_limit.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/helper/physical_limit.hpp @@ -74,6 +74,9 @@ class PhysicalLimit : public PhysicalOperator { const BoundLimitNode &offset_val); static bool HandleOffset(DataChunk &input, idx_t ¤t_offset, idx_t offset, idx_t limit); static Value GetDelimiter(ExecutionContext &context, DataChunk &input, const Expression &expr); + //! Reads the value of a non-constant LIMIT or OFFSET (null_value if it is NULL), which must be at most + //! MAX_LIMIT_VALUE + static idx_t GetLimitValue(const Value &val, idx_t null_value); InsertionOrderPreservingMap ParamsToString() const override; }; diff --git a/src/duckdb/src/include/duckdb/execution/operator/helper/physical_set.hpp b/src/duckdb/src/include/duckdb/execution/operator/helper/physical_set.hpp index e931dd5e2..a261281d1 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/helper/physical_set.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/helper/physical_set.hpp @@ -43,6 +43,8 @@ class PhysicalSet : public PhysicalOperator { static void SetExtensionVariable(ClientContext &context, ExtensionOption &extension_option, SetScope scope, const Value &value); + static void SetExtensionVariable(ClientContext &context, const Identifier &name, ExtensionOption &extension_option, + SetScope scope, const Value &value); static void SetVariable(ClientContext &context, const Identifier &name, SetScope scope, const Value &value); static void SetGenericVariable(ClientContext &context, idx_t setting_index, SetScope scope, Value target_value); diff --git a/src/duckdb/src/include/duckdb/execution/operator/join/join_filter_pushdown.hpp b/src/duckdb/src/include/duckdb/execution/operator/join/join_filter_pushdown.hpp index 02013abb6..72d511a22 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/join/join_filter_pushdown.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/join/join_filter_pushdown.hpp @@ -77,6 +77,8 @@ struct JoinFilterPushdownFilter { shared_ptr dynamic_filters; //! The columns for which we should generate filters vector columns; + //! Target scan estimate for filter costing when intervening operators reduce the probe cardinality. + idx_t estimated_scan_cardinality = 0; }; struct PushdownFilterTarget { @@ -132,10 +134,12 @@ struct JoinFilterPushdownInfo { idx_t max_bits, JoinFilterGlobalState &gstate) const; bool CanUseInFilter(const ClientContext &context, optional_ptr ht, const ExpressionType &cmp) const; - bool CanUseBloomFilter(const ClientContext &context, const PhysicalComparisonJoin &op, const ExpressionType &cmp, + bool CanUseBloomFilter(const JoinFilterPushdownFilter &info, const ClientContext &context, + const PhysicalComparisonJoin &op, const ExpressionType &cmp, optional_ptr ht = nullptr) const; - bool CanUsePrefixRangeFilter(const ClientContext &context, const PhysicalComparisonJoin &op, - optional_ptr ht, const ExpressionType &cmp) const; + bool CanUsePrefixRangeFilter(const JoinFilterPushdownFilter &info, const ClientContext &context, + const PhysicalComparisonJoin &op, optional_ptr ht, + const ExpressionType &cmp) const; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/function/builtin_function_lookup.hpp b/src/duckdb/src/include/duckdb/function/builtin_function_lookup.hpp index 64aaf24e3..081dfeec0 100644 --- a/src/duckdb/src/include/duckdb/function/builtin_function_lookup.hpp +++ b/src/duckdb/src/include/duckdb/function/builtin_function_lookup.hpp @@ -38,6 +38,10 @@ shared_ptr GetBuiltinWindowFunction(ClientContext &context const vector &arguments); //! Look up a built-in scalar function as GetBuiltinScalarFunction does, and bind it to the given children +//! The result can be any expression, e.g. a constant if the function was folded during binding +unique_ptr BindBuiltinScalarExpression(ClientContext &context, const Identifier &name, + vector> children); +//! As BindBuiltinScalarExpression, but throws if the result is not a BoundFunctionExpression unique_ptr BindBuiltinScalarFunction(ClientContext &context, const Identifier &name, vector> children); diff --git a/src/duckdb/src/include/duckdb/function/function.hpp b/src/duckdb/src/include/duckdb/function/function.hpp index 170464199..aab9fa6b5 100644 --- a/src/duckdb/src/include/duckdb/function/function.hpp +++ b/src/duckdb/src/include/duckdb/function/function.hpp @@ -116,6 +116,8 @@ struct FunctionLocalState { struct FunctionParameters { vector values; named_argument_map_t named_parameters; + //! The pragma function the parameters were bound for + optional_ptr function; }; //! How a parameter receives its arguments, mirroring Python's parameter kinds, in the order a signature declares them @@ -654,6 +656,10 @@ class BoundSimpleFunction { DUCKDB_API auto GetKeywordOnlyArgumentCount(const FunctionSignature &signature) const -> idx_t; DUCKDB_API auto GetKwargsCount(const FunctionSignature &signature) const -> idx_t; + //! Records the arguments of a call with constant arguments, as the function binder placed them for `signature` + DUCKDB_API void SetCallArguments(const FunctionSignature &signature, const vector ¶meters, + const named_argument_map_t &named_parameters); + protected: DUCKDB_API auto GetArgumentParameterKind(const FunctionSignature &signature, idx_t argument_index) const -> FunctionParameterKind; diff --git a/src/duckdb/src/include/duckdb/function/pragma_function.hpp b/src/duckdb/src/include/duckdb/function/pragma_function.hpp index 200f7a8f6..d6b245404 100644 --- a/src/duckdb/src/include/duckdb/function/pragma_function.hpp +++ b/src/duckdb/src/include/duckdb/function/pragma_function.hpp @@ -20,6 +20,21 @@ typedef string (*pragma_query_t)(ClientContext &context, const FunctionParameter //! Execute the main pragma function typedef void (*pragma_function_t)(ClientContext &context, const FunctionParameters ¶meters); +struct PragmaFunctionInfo { + DUCKDB_API virtual ~PragmaFunctionInfo(); + + template + TARGET &Cast() { + DynamicCastCheck(this); + return reinterpret_cast(*this); + } + template + const TARGET &Cast() const { + DynamicCastCheck(this); + return reinterpret_cast(*this); + } +}; + //! Pragma functions are invoked by calling PRAGMA x //! Pragma functions come in three types: //! * Call: function call, e.g. PRAGMA table_info('tbl') @@ -65,6 +80,8 @@ class PragmaFunction : public SimpleFunction { // NOLINT: work-around bug in cla pragma_query_t query; pragma_function_t function; + //! Additional function info, passed to the callbacks through FunctionParameters::function + shared_ptr function_info; private: PragmaFunction(Identifier name, PragmaType pragma_type, pragma_query_t query, pragma_function_t function, diff --git a/src/duckdb/src/include/duckdb/function/scalar/struct_utils.hpp b/src/duckdb/src/include/duckdb/function/scalar/struct_utils.hpp index 7f9b609c7..998f39b1c 100644 --- a/src/duckdb/src/include/duckdb/function/scalar/struct_utils.hpp +++ b/src/duckdb/src/include/duckdb/function/scalar/struct_utils.hpp @@ -92,4 +92,6 @@ inline const Expression &PeelStructExtractPath(const Expression &expr, vector &path) = delete; + } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/function/window/window_aggregate_states.hpp b/src/duckdb/src/include/duckdb/function/window/window_aggregate_states.hpp index 59d64793f..afefe112f 100644 --- a/src/duckdb/src/include/duckdb/function/window/window_aggregate_states.hpp +++ b/src/duckdb/src/include/duckdb/function/window/window_aggregate_states.hpp @@ -20,16 +20,16 @@ struct WindowAggregateStates { //! The number of states idx_t GetCount() const { - return states.size() / state_size; + return count; } data_ptr_t *GetData() { return FlatVector::GetDataMutable(*statef); } data_ptr_t GetStatePtr(idx_t idx) { - return states.data() + idx * state_size; + return states.get() + idx * state_size; } const_data_ptr_t GetStatePtr(idx_t idx) const { - return states.data() + idx * state_size; + return states.get() + idx * state_size; } //! Initialise all the states void Initialize(idx_t count); @@ -48,8 +48,10 @@ struct WindowAggregateStates { const idx_t state_size; //! The allocator to use ArenaAllocator allocator; - //! Data pointer that contains the state data - vector states; + //! The number of states + idx_t count = 0; + //! Buffer that contains the state data, allocated through the buffer allocator + AllocatedData states; //! Reused result state container for the window functions unique_ptr statef; }; diff --git a/src/duckdb/src/include/duckdb/function/window/window_collection.hpp b/src/duckdb/src/include/duckdb/function/window/window_collection.hpp index 234ed1617..3ec325801 100644 --- a/src/duckdb/src/include/duckdb/function/window/window_collection.hpp +++ b/src/duckdb/src/include/duckdb/function/window/window_collection.hpp @@ -9,10 +9,14 @@ #pragma once #include "duckdb/common/atomic.hpp" +#include "duckdb/common/mutex.hpp" +#include "duckdb/common/once_flag.hpp" #include "duckdb/common/types/column/column_data_collection.hpp" #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" +#include + namespace duckdb { // A wrapper for building ColumnDataCollections in parallel @@ -38,7 +42,7 @@ class WindowCollection { //! Update a thread-local collection for appending data to a given row void GetCollection(idx_t row_idx, ColumnDataCollectionSpec &spec); - //! Single-threaded, idempotent ordered combining of all the appended data. + //! Combine once in order after all appends have finished. void Combine(const ColumnSet &build_validity); //! The collection data. May be null if the column count is 0. @@ -62,6 +66,14 @@ class WindowCollection { //! The (sorted) collection ranges using Range = pair; vector ranges; + +private: + void CombineInternal(const ColumnSet &validity_cols); + +private: + OnceFlag combine_once; + //! Combining consumes its inputs, so failures must be shared rather than retried. + std::exception_ptr combine_error; }; class WindowBuilder { diff --git a/src/duckdb/src/include/duckdb/main/active_query_context.hpp b/src/duckdb/src/include/duckdb/main/active_query_context.hpp index 6c2c0eef1..4aad2a1c9 100644 --- a/src/duckdb/src/include/duckdb/main/active_query_context.hpp +++ b/src/duckdb/src/include/duckdb/main/active_query_context.hpp @@ -26,6 +26,8 @@ struct ActiveQueryContext { unique_ptr executor; //! The progress bar unique_ptr progress_bar; + //! Whether a collector built the open result, which has no way to end the query + bool collector_built_result = false; public: void SetOpenResult(BaseQueryResult &result) { diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_function_internal.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_function_internal.hpp index 82bef921e..4deccc93f 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_function_internal.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_function_internal.hpp @@ -12,16 +12,71 @@ #include "duckdb/common/named_parameter_map.hpp" #include "duckdb/function/function.hpp" #include "duckdb/function/table_function.hpp" +#include "duckdb/parser/parsed_data/create_function_info.hpp" namespace duckdb::capiv2 { +//! An example of calling a function, with an optional title. +struct CV2FunctionExample { + string title; + string example; +}; + +//! The documentation of one overload, as `duckdb_functions()` reports it. +class CV2FunctionDocs { +public: + //! Adds this overload's description to `info`, matched to the overload by its parameter types; the parameter names + //! come from the signature itself. Adds nothing when no documentation was set. + void AddTo(const FunctionSignature &signature, CreateFunctionInfo &info) const { + vector parameter_types; + for (idx_t i = 0; i < signature.GetParameterCount(); i++) { + auto ¶meter = signature.GetParameter(i); + if (parameter.IsVariadic()) { + continue; + } + parameter_types.push_back(parameter.GetType()); + } + AddTo(std::move(parameter_types), info); + } + //! As above, matched to the overload by the given parameter types + void AddTo(vector parameter_types, CreateFunctionInfo &info) const { + if (description.empty() && examples.empty() && categories.empty()) { + return; + } + FunctionDescription result; + result.parameter_types = std::move(parameter_types); + result.description = description; + for (auto &entry : examples) { + result.examples.push_back(entry.example); + } + result.categories = categories; + info.descriptions.push_back(std::move(result)); + } + +public: + string description; + vector examples; + vector categories; +}; + +inline auto Convert(duckdb_v2_function_docs_handle docs) -> CV2FunctionDocs * { + return reinterpret_cast(docs); +} + +inline auto Convert(CV2FunctionDocs *docs) -> duckdb_v2_function_docs_handle { + return reinterpret_cast(docs); +} + //! The part of a bind callback every function family shares: the arguments of the call site, the user data and the //! bind data. The arguments are one list: the positional arguments, then the named arguments class CV2FunctionBindInfo { public: template CV2FunctionBindInfo(const FUNC &function, void *user_data) - : in_user_data(user_data), function(function), signature(function.GetDefinition()->GetSignature()) { + : CV2FunctionBindInfo(function, function.GetDefinition()->GetSignature(), user_data) { + } + CV2FunctionBindInfo(const BoundSimpleFunction &function, const FunctionSignature &signature, void *user_data) + : in_user_data(user_data), function(function), signature(signature) { } virtual ~CV2FunctionBindInfo() = default; @@ -38,8 +93,8 @@ class CV2FunctionBindInfo { public: void *in_user_data; - //! Whether the bind data is set through this handle - scalar functions set it through their own bind info - bool can_set_bind_data = true; + //! Why the bind data cannot be set through this handle, empty when it can + string bind_data_error; //! The bind data the callback set, or nullptr shared_ptr out_bind_data; @@ -63,11 +118,11 @@ class CV2ExpressionBindInfo final : public CV2FunctionBindInfo { const BindFunctionInput &input; }; -//! The arguments of a table function call: constants, the positional arguments and the named ones held apart +//! The arguments of a table or pragma function call: constants, the positional arguments and the named ones held apart class CV2ConstantBindInfo final : public CV2FunctionBindInfo { public: - CV2ConstantBindInfo(const BoundTableFunction &function, void *user_data, const vector &positional, - const named_argument_map_t &named); + CV2ConstantBindInfo(const BoundSimpleFunction &function, const FunctionSignature &signature, void *user_data, + const vector &positional, const named_argument_map_t &named); LogicalType GetArgType(idx_t index) const override; Value GetArgValue(idx_t index) const override; diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp index c2ce49518..b90f20fce 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp @@ -32,6 +32,8 @@ #include "duckdb/main/setting_info.hpp" #include "duckdb/execution/operator/helper/physical_set.hpp" #include "duckdb/main/database.hpp" +#include "duckdb/common/query_context.hpp" +#include "duckdb/common/file_system.hpp" // The engine implements the whole V2 C API, including the unstable surface #ifndef DUCKDB_V2_API_ALLOW_UNSTABLE @@ -211,6 +213,87 @@ inline auto Convert(duckdb_v2_environment_handle env) -> CV2Environment * { class CV2Option; class CV2Instance; +class CV2OptionSource; + +//! A config handle: the scope config options are read from and written at. +class CV2Config { +public: + virtual ~CV2Config() = default; + + //! The effective value of `name`, and the scope DuckDB attributes it to. Throws for an unknown option. + virtual DUCKDB_V2_SETTING_SCOPE ReadValue(std::string_view name, Value &result) = 0; + //! Writes `name` at `scope`, which the option must support. + virtual void Write(const Identifier &name, const Value &value, DUCKDB_V2_SETTING_SCOPE scope) = 0; + //! Where option descriptors are read from. The same from every config of one instance. + virtual unique_ptr GetOption(std::string_view name) = 0; + virtual unique_ptr GetOptionByIndex(idx_t index) = 0; + virtual idx_t GetOptionCount() = 0; +}; + +inline auto Convert(duckdb_v2_config_handle config) -> CV2Config * { + return reinterpret_cast(config); +} + +inline auto Convert(CV2Config *config) -> duckdb_v2_config_handle { + return reinterpret_cast(config); +} + +//! An instance's config: GLOBAL only. +class CV2InstanceConfig final : public CV2Config { +public: + explicit CV2InstanceConfig(CV2Instance &instance) : instance(instance) { + } + + DUCKDB_V2_SETTING_SCOPE ReadValue(std::string_view name, Value &result) override; + void Write(const Identifier &name, const Value &value, DUCKDB_V2_SETTING_SCOPE scope) override; + unique_ptr GetOption(std::string_view name) override; + unique_ptr GetOptionByIndex(idx_t index) override; + idx_t GetOptionCount() override; + +private: + CV2Instance &instance; +}; + +//! A factory handle: the scope values, types and data chunks are created in. +class CV2Factory { +public: + virtual ~CV2Factory() = default; + + //! The database whose allocator and buffer manager back what is created. + virtual DatabaseInstance &GetDatabase() = 0; + //! The client whose catalog and settings apply, or nullptr when only the built-ins do. + virtual optional_ptr TryGetClientContext() = 0; + //! Runs `fn` with a transaction active on the client, for catalog lookups. + virtual void WithTransaction(const std::function &fn) = 0; +}; + +inline auto Convert(duckdb_v2_factory_handle factory) -> CV2Factory * { + return reinterpret_cast(factory); +} + +inline auto Convert(CV2Factory *factory) -> duckdb_v2_factory_handle { + return reinterpret_cast(factory); +} + +//! An instance's factory: built-in types and casts only, no catalog. +class CV2InstanceFactory final : public CV2Factory { +public: + explicit CV2InstanceFactory(CV2Instance &instance) : instance(instance) { + } + + DatabaseInstance &GetDatabase() override; + optional_ptr TryGetClientContext() override { + return nullptr; + } + void WithTransaction(const std::function &fn) override; + +private: + CV2Instance &instance; +}; + +//! The common type of a non-empty set of types, folded left to right in the factory's scope. Throws +//! NotImplementedException when there is none. Defined in capi_v2_logical_type.cpp. +auto ResolveCommonType(CV2Factory &factory, const vector &types) -> LogicalType; //! The SQL ATTACH `(KEY value)` options of one attach, as the text values a quoted literal produces. Bound to the //! instance handle it was created from, which is what future per-instance resources (an allocator, say) would be @@ -232,44 +315,50 @@ inline auto Convert(CV2AttachOptions *options) -> duckdb_v2_attach_options_handl return reinterpret_cast(options); } -//! An instance handle: a DuckDB instance plus the configuration it starts with. The instance starts on first use -//! (instance_attach or connection_create); until then options are staged in the startup config. Every entry point -//! holds `lock`, which also serializes use of the internal connection. +//! Throws unless `target` is the database `what` was created for, through its factory. +inline void CheckRegistrationTarget(const DatabaseInstance &created_for, const DatabaseInstance &target, + const char *what) { + if (&created_for != &target) { + throw InvalidInputException("the %s was created through a factory of a different database", what); + } +} + +//! A file system handle: the file system plus the query its reads and writes are attributed to, if any. +class CV2FileSystem { +public: + CV2FileSystem(FileSystem &fs, QueryContext query) : fs(fs), query(query) { + } + + FileSystem &fs; + QueryContext query; +}; + +//! An instance handle: a DuckDB instance, built from its startup options when the handle is created. class CV2Instance { public: - explicit CV2Instance(CV2Environment &env); + CV2Instance(CV2Environment &env, DBConfig &startup_config); - bool IsStarted() const { - return database != nullptr; - } - //! Starts the instance if it has not started yet, consuming the staged config. - void Start(); //! Attaches the database at `path` under `name` (derived from the path when empty), like ATTACH, optionally as - //! the default for new connections; starts the instance first if needed. + //! the default for new connections. void Attach(const string &path, const Identifier &name, optional_ptr options, bool make_default); - //! Detaches the database attached from `path`, or attached under that name. - void Detach(const string &path); - //! Makes the database attached from `path`, or attached under that name, the default for new connections. - void SetDefault(const string &path); - //! Stages a startup option, or SET GLOBAL once started. - void SetOption(const Identifier &name, const string &setting); - unique_ptr GetOption(std::string_view name); - idx_t GetOptionCount(); - unique_ptr GetOptionByIndex(idx_t index); - //! The started instance; starts it if needed. - DuckDB &GetDatabase(); - - CV2Environment &env; - mutex lock; + //! Detaches the database attached under `name`. + void Detach(const Identifier &name); + //! Makes the database attached under `name` the default for new connections. + void SetDefault(const Identifier &name); + DuckDB &GetDatabase() { + return *database; + } private: - //! Staged until Start consumes it. - unique_ptr config; - //! The staged settings as written, by canonical name: legacy options cannot be read back from a DBConfig. - identifier_map_t staged_settings; + //! Declared first: the members below are built from it. shared_ptr database; - unique_ptr internal_connection; + +public: + CV2Environment &env; + CV2InstanceFactory factory; + CV2InstanceConfig config_handle; + CV2FileSystem file_system; }; inline auto Convert(duckdb_v2_instance_handle instance) -> CV2Instance * { @@ -280,7 +369,106 @@ inline auto Convert(CV2Instance *instance) -> duckdb_v2_instance_handle { return reinterpret_cast(instance); } -using CV2Connection = duckdb::Connection; +class CV2Context; + +//! A client's config: the connection's cascade (SESSION, then GLOBAL, then the default), written like SQL `SET`. +class CV2ClientConfig final : public CV2Config { +public: + explicit CV2ClientConfig(ClientContext &context) : context(context) { + } + + DUCKDB_V2_SETTING_SCOPE ReadValue(std::string_view name, Value &result) override; + void Write(const Identifier &name, const Value &value, DUCKDB_V2_SETTING_SCOPE scope) override; + unique_ptr GetOption(std::string_view name) override; + unique_ptr GetOptionByIndex(idx_t index) override; + idx_t GetOptionCount() override; + +private: + ClientContext &context; +}; + +//! A context's factory: the context's catalog and settings. +class CV2ContextFactory final : public CV2Factory { +public: + explicit CV2ContextFactory(CV2Context &context) : context(context) { + } + + DatabaseInstance &GetDatabase() override; + optional_ptr TryGetClientContext() override; + void WithTransaction(const std::function &fn) override; + +private: + CV2Context &context; +}; + +//! A context handle: the client context plus how a call through it obtains a transaction. +class CV2Context { +public: + explicit CV2Context(ClientContext &context) + : context(context), factory(*this), config_handle(context), + file_system(FileSystem::GetFileSystem(context), QueryContext(context)) { + } + virtual ~CV2Context() = default; + + //! Runs `fn` with a transaction active on the context. + virtual void WithContext(const std::function &fn) = 0; + + ClientContext &context; + CV2ContextFactory factory; + CV2ClientConfig config_handle; + CV2FileSystem file_system; +}; + +inline DatabaseInstance &CV2ContextFactory::GetDatabase() { + return DatabaseInstance::GetDatabase(context.context); +} + +inline optional_ptr CV2ContextFactory::TryGetClientContext() { + return &context.context; +} + +inline void CV2ContextFactory::WithTransaction(const std::function &fn) { + context.WithContext(fn); +} + +//! A context handed to a callback: the context lock is held and a transaction is already active. +class CV2CallbackContext final : public CV2Context { +public: + explicit CV2CallbackContext(ClientContext &context) : CV2Context(context) { + } + + void WithContext(const std::function &fn) override { + fn(context); + } +}; + +//! A context taken from a connection: each call joins the connection's transaction, or runs in one of its own. +class CV2ConnectionContext final : public CV2Context { +public: + explicit CV2ConnectionContext(ClientContext &context) : CV2Context(context) { + } + + void WithContext(const std::function &fn) override { + context.RunFunctionInTransaction([&]() { fn(context); }); + } +}; + +inline auto Convert(duckdb_v2_context_handle ctx) -> CV2Context * { + return reinterpret_cast(ctx); +} + +inline auto Convert(CV2Context *ctx) -> duckdb_v2_context_handle { + return reinterpret_cast(ctx); +} + +//! A connection handle: the connection plus the context handle it lends out. +class CV2Connection : public Connection { +public: + explicit CV2Connection(DuckDB &database) : Connection(database), context_handle(*context) { + } + + CV2ConnectionContext context_handle; +}; inline auto Convert(duckdb_v2_connection_handle conn) -> CV2Connection * { return reinterpret_cast(conn); @@ -299,16 +487,6 @@ inline auto Convert(CV2SQLStatement *stmt) -> duckdb_v2_sql_statement_handle { return reinterpret_cast(stmt); } -using CV2Context = duckdb::ClientContext; - -inline auto Convert(duckdb_v2_context_handle ctx) -> CV2Context * { - return reinterpret_cast(ctx); -} - -inline auto Convert(CV2Context *ctx) -> duckdb_v2_context_handle { - return reinterpret_cast(ctx); -} - //! The extension handle's backing struct is the load state in capi_v2_extension.cpp, not an ExtensionLoader, so it //! cannot be Convert'ed with a cast: the loader is resolved through the state instead. Valid only while the //! extension's entrypoint is running. @@ -331,35 +509,34 @@ inline auto Convert(CV2FunctionSignature *func) -> duckdb_v2_function_signature_ return reinterpret_cast(func); } -//! Where an option's current setting is read from: a started instance's context (LOCAL -> GLOBAL -> default), or the -//! startup config of an instance that has not started (staged GLOBAL -> default). +//! Where an option's current value is read from: a client context's cascade (SESSION -> GLOBAL -> default). class CV2OptionSource { public: explicit CV2OptionSource(ClientContext &context) : context(&context), config(DBConfig::GetConfig(context)) { } - CV2OptionSource(const DBConfig &config, const identifier_map_t &staged_settings) - : config(config), staged_settings(&staged_settings) { + //! GLOBAL -> default only. + explicit CV2OptionSource(DatabaseInstance &db) : config(DBConfig::GetConfig(db)) { } const DBConfig &GetConfig() const { return config; } - //! The effective setting of `name`, or `fallback` when the cascade yields NULL. - string ReadSetting(const Identifier &name, const string &fallback) const; + //! The effective value of `name`, and the scope DuckDB attributes it to. Throws for an unknown option. + DUCKDB_V2_SETTING_SCOPE ReadValue(std::string_view name, Value &result) const; private: optional_ptr context; const DBConfig &config; - optional_ptr> staged_settings; }; class CV2Option { public: Identifier name; - string setting; - string default_setting; + Value default_value; string description; - DUCKDB_V2_OPTION_TARGET_SCOPE target_scope = DUCKDB_V2_OPTION_TARGET_SCOPE_UNKNOWN; + bool supports_global = false; + bool supports_session = false; + DUCKDB_V2_SETTING_SCOPE default_scope = DUCKDB_V2_SETTING_SCOPE_GLOBAL; vector aliases; static unique_ptr FromIndex(const CV2OptionSource &source, idx_t index); @@ -367,6 +544,18 @@ class CV2Option { static idx_t Count(const CV2OptionSource &source); }; +//! The engine's write scope for a V2 scope. +inline SetScope ConvertSetScope(DUCKDB_V2_SETTING_SCOPE scope) { + switch (scope) { + case DUCKDB_V2_SETTING_SCOPE_GLOBAL: + return SetScope::GLOBAL; + case DUCKDB_V2_SETTING_SCOPE_SESSION: + return SetScope::SESSION; + default: + return SetScope::AUTOMATIC; + } +} + inline auto Convert(duckdb_v2_option_handle opt) -> CV2Option * { return reinterpret_cast(opt); } @@ -645,13 +834,14 @@ DUCKDB_V2_ERROR WithErrorHandler(duckdb_v2_error_info_handle *err, T callback) n //---------------------------------------------------------------------------------------------------------------------- struct ConnectionBusySlotV2 : public ClientContextState { + //! The result of a statement that expands into a group, until it finishes. Between two statements of a group + //! the engine has no statement open, but the group's transaction is still running std::atomic owner {nullptr}; - // True once the consumer called `connection_interrupt` for the active result. - // This flag is how we distinguish a consumer cancellation (-> CANCELLED status) from an DuckDB-initiated interrupt - // that shares the INTERRUPT exception type, e.g. a `max_execution_time` timeout (-> error). - // The DuckDB's internal interrupt_state is not suitable for this, it is set to stop sibling tasks on any error. - // Reset when a new query claims the slot. - std::atomic cancel_requested {false}; + // The number of `connection_interrupt` calls. A result records it when its statement starts, and an INTERRUPT + // error is a consumer cancellation (-> CANCELLED status) only if it moved since: a DuckDB-initiated interrupt, + // e.g. a `max_execution_time` timeout, shares the INTERRUPT exception type (-> error). The DuckDB's internal + // interrupt_state is not suitable for this, it is set to stop sibling tasks on any error. + std::atomic cancel_requests {0}; }; inline shared_ptr GetBusySlot(ClientContext &context) { @@ -659,6 +849,17 @@ inline shared_ptr GetBusySlot(ClientContext &context) { return context.registered_state->GetOrCreate(BUSY_SLOT_STATE_KEY); } +//! The engine's refusal, raised for a group that holds the connection between two of its statements +inline void ThrowConnectionInUse() { + throw ResourceInUseException(ClientContext::OPEN_RESULT_ERROR); +} + +inline void ThrowIfGroupRunning(ConnectionBusySlotV2 &slot) { + if (slot.owner.load() != nullptr) { + ThrowConnectionInUse(); + } +} + //---------------------------------------------------------------------------------------------------------------------- // Text sinks //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp index 21456c240..7dc14c68b 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp @@ -10,6 +10,7 @@ #include "duckdb/common/arrow/arrow_wrapper.hpp" #include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/main/valid_checker.hpp" #include "duckdb/main/query_result_stream.hpp" #include "duckdb/main/result_format.hpp" @@ -37,6 +38,9 @@ struct ResultWrapperV2 { unique_ptr handle; //! Live while state == STREAMING, for a statement whose result can be streamed. A QueryResultStream in `format`. unique_ptr stream; + //! Decided when the handle is adopted, since a prepared execution's handle reports the EXECUTE that runs it + //! rather than the statement as written + bool stream_when_ready = false; //! Every fragment is submitted in this format; null is the chunk format. shared_ptr format; @@ -72,9 +76,11 @@ struct ResultWrapperV2 { //! fragment execution path. bool owns_wrapping_transaction = false; - //! The owning connection's busy slot; released on terminal transition - //! or destroy, whichever comes first. + //! The connection's busy slot, kept for the result's lifetime for its cancel count. A group claims the slot + //! until a terminal transition or destroy, whichever comes first. shared_ptr busy_slot; + //! The slot's cancel count when this result's statement started + idx_t cancel_requests_at_start = 0; //! Principal fragment's metadata, valid once metadata_available. vector types; @@ -88,44 +94,50 @@ struct ResultWrapperV2 { ErrorData error; //! Mirrors ClientContext::Query's error handling for expanded groups - //! (client_context.cpp, chain-append loop): when the group cannot - //! complete, roll back the transaction statement_execute injected to wrap - //! it. A no-op for non-expanded statements, for groups that ran inside a - //! user-managed transaction (which the bridge never wraps), and when the - //! transaction is already gone. - void RollbackIncompleteGroup() { - if (!owns_wrapping_transaction || !context) { + //! (client_context.cpp, chain-append loop): a group that cannot complete + //! is undone as a whole. The transaction statement_execute injected to wrap + //! it is rolled back; a user-managed transaction it ran inside (which the + //! bridge never wraps) is invalidated, since part of the group may already + //! have applied. Only a group still in progress is undone: once its result + //! has ended, the active transaction may be a later one. + void AbandonIncompleteGroup() { + if (fragment_count < 2 || !context || (state != State::PENDING && state != State::STREAMING)) { return; } - if (context->transaction.HasActiveTransaction()) { - // Mirrors Connection::Rollback (Query("ROLLBACK") + throw on error), - // driven through the retained context so it works after disconnect. - auto result = context->Query("ROLLBACK", QueryParameters()); - result->ThrowIfError(); + if (!context->transaction.HasActiveTransaction()) { + return; + } + if (!owns_wrapping_transaction) { + ValidChecker::Invalidate(context->ActiveTransaction(), + "a statement that expands into several was abandoned before it completed"); + return; } + // Mirrors Connection::Rollback (Query("ROLLBACK") + throw on error), + // driven through the retained context so it works after disconnect. + auto result = context->Query("ROLLBACK", QueryParameters()); + result->ThrowIfError(); } //! Close() the live engine result so an abandoned active query is cleaned //! up (freeing the executor, which breaks the ClientContext ref cycle), - //! then roll back an injected group transaction. May throw; the terminal - //! states leave pending/result null, so this is then a no-op. + //! then undo a group that is still in progress. May throw; the terminal + //! states leave pending/result null and the group settled, so this is then a no-op. void Finalize() { if (stream) { stream->Close(); } else if (handle) { handle->Close(); } - RollbackIncompleteGroup(); + AbandonIncompleteGroup(); } - //! Frees the connection for its next query. Only the current owner can + //! Releases a group's claim on the connection. Only the current owner can //! release the slot, so a release after the connection moved on is a //! no-op. void ReleaseBusySlot() { if (busy_slot) { void *expected = this; busy_slot->owner.compare_exchange_strong(expected, nullptr); - busy_slot.reset(); } } @@ -135,8 +147,9 @@ struct ResultWrapperV2 { //! Adopts an already-submitted query into the state machine: the single seam both the //! stateless (fragment) and prepared paths reach. When is_principal, captures its metadata and - //! surfaces its chunks. Throws on a submission error. - void BeginPending(unique_ptr handle, bool is_principal); + //! surfaces its chunks. written_type is the type of the statement as written, since a prepared execution's + //! handle reports the EXECUTE that runs it. Throws on a submission error. + void BeginPending(unique_ptr handle, bool is_principal, StatementType written_type); //! Starts the pending query for the next fragment, selecting it as //! principal per the engine-mirrored rule and adopting it via BeginPending. //! Throws on prepare errors. @@ -173,15 +186,14 @@ auto Convert(duckdb_v2_result_handle handle) -> ResultWrapperV2 *; auto ConvertArrowResult(ResultWrapperV2 *wrapper) -> duckdb_v2_arrow_result_handle; auto Convert(duckdb_v2_arrow_result_handle handle) -> ResultWrapperV2 *; -//! Preprocesses and submits a borrowed statement in `format`, claiming the connection's live-result slot first. -//! Throws ResourceInUseException when the connection already has a live result. +//! Preprocesses and submits a borrowed statement in `format`; a statement that expands into a group claims the +//! connection's busy slot. Throws ResourceInUseException while a statement on the connection is still running. auto ExecuteStatementV2(const shared_ptr &context, const SQLStatement &statement, const duckdb_v2_identifier_t *parameter_names, const duckdb_v2_value_handle *parameter_values, idx_t parameter_count, const char *function_name, shared_ptr format) -> unique_ptr; -//! Runs a prepared statement as a single-statement result, claiming the connection's -//! live-result slot first. `context` is the session the result holds on to, so it survives -//! disconnect. Throws ResourceInUseException when the connection already has a live result. +//! Runs a prepared statement as a single-statement result. `context` is the session the result holds on to, so it +//! survives disconnect. Throws ResourceInUseException while a statement on the connection is still running. auto ExecutePreparedStatementV2(const shared_ptr &context, PreparedStatement &prepared, identifier_map_t &values, shared_ptr format) -> unique_ptr; diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/extension_api_v2.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/extension_api_v2.hpp index ad9d1ead1..f749148a5 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/extension_api_v2.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/extension_api_v2.hpp @@ -26,11 +26,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_combine) (duckdb_v2_column_data_collection_handle target, duckdb_v2_column_data_collection_handle *source, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, - duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create_with_context) - (duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, + DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create) + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_column_data_collection_destroy)(duckdb_v2_column_data_collection_handle *collection); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_reset) @@ -52,68 +49,27 @@ typedef struct { duckdb_v2_column_data_collection_worker_scan_state_handle *out_state, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_worker_scan_state_destroy) (duckdb_v2_column_data_collection_worker_scan_state_handle *state); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create) - (duckdb_v2_instance_handle instance, duckdb_v2_connection_handle *out_conn, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_id) - (duckdb_v2_connection_handle conn, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_name) - (duckdb_v2_connection_handle conn, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_by_index) + (duckdb_v2_config_handle config, idx_t index, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_text) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_with_alias) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle base_type, - const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_by_name) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_count) + (duckdb_v2_config_handle config, idx_t *out_count, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_create) + (duckdb_v2_instance_handle instance, duckdb_v2_connection_handle *out_conn, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_connection_destroy)(duckdb_v2_connection_handle *conn); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_by_index) - (duckdb_v2_connection_handle conn, idx_t index, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_by_name) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_count) - (duckdb_v2_connection_handle conn, idx_t *out_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_connection_interrupt) (duckdb_v2_connection_handle conn, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_connection_progress_get) (duckdb_v2_connection_handle conn, double *out_percentage, uint64_t *out_rows_processed, uint64_t *out_total_rows_to_process, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_set_option) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, - DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_id) - (duckdb_v2_context_handle ctx, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_name) - (duckdb_v2_context_handle ctx, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_text) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_with_alias) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle base_type, const duckdb_v2_identifier_t *alias_name, - duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy_with_context) - (duckdb_v2_context_handle context, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, + DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy) + (duckdb_v2_factory_handle factory, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create) - (const duckdb_v2_logical_type_handle *types, idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types, idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create_with_context) - (duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types, idx_t column_count, + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types, idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_data_chunk_destroy)(duckdb_v2_data_chunk_handle *chunk); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_get_size) @@ -134,25 +90,32 @@ typedef struct { DUCKDB_V2_ERROR (*duckdb_v2_error_info_get_text)(duckdb_v2_error_info_handle info, duckdb_v2_str *out_text); DUCKDB_V2_ERROR (*duckdb_v2_error_info_set_code)(duckdb_v2_error_info_handle info, DUCKDB_V2_ERROR code); DUCKDB_V2_ERROR (*duckdb_v2_error_info_set_text)(duckdb_v2_error_info_handle info, const duckdb_v2_str *text); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_id) + (duckdb_v2_factory_handle factory, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, + const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_name) + (duckdb_v2_factory_handle factory, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, + const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_text) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_with_alias) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle base_type, + const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_attach) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, const duckdb_v2_identifier_t *name, + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, const duckdb_v2_str *path, duckdb_v2_attach_options_handle options, bool make_default, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_create) (duckdb_v2_environment_handle env, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_create_with_options) + (duckdb_v2_environment_handle env, const duckdb_v2_str *option_names, const duckdb_v2_str *option_values, + idx_t option_count, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_instance_destroy)(duckdb_v2_instance_handle *instance); DUCKDB_V2_ERROR(*duckdb_v2_instance_detach) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_by_index) - (duckdb_v2_instance_handle instance, idx_t index, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_by_name) - (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_count) - (duckdb_v2_instance_handle instance, idx_t *out_count, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_set_option) - (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, - duckdb_v2_error_info_handle *err); + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_library_version)(duckdb_v2_str *out_version, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_logical_type_copy) (duckdb_v2_logical_type_handle type, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); @@ -177,16 +140,12 @@ typedef struct { (duckdb_v2_option_handle option, idx_t index, duckdb_v2_identifier_t *out_alias, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_alias_count) (duckdb_v2_option_handle option, idx_t *out_count, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_setting) - (duckdb_v2_option_handle option, duckdb_v2_str *out_default_setting, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_value) + (duckdb_v2_option_handle option, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_description) (duckdb_v2_option_handle option, duckdb_v2_str *out_description, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_name) (duckdb_v2_option_handle option, duckdb_v2_identifier_t *out_name, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_setting) - (duckdb_v2_option_handle option, duckdb_v2_str *out_setting, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_target_scope) - (duckdb_v2_option_handle option, DUCKDB_V2_OPTION_TARGET_SCOPE *out_target_scope, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_parse_sql) (duckdb_v2_connection_handle conn, const char *sql, duckdb_v2_statement_iterator_handle *out_iterator, duckdb_v2_error_info_handle *err); @@ -227,235 +186,120 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_statement_iterator_next) (duckdb_v2_statement_iterator_handle iterator, duckdb_v2_sql_statement_handle *out_statement, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_cast_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_cast_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, + DUCKDB_V2_ERROR(*duckdb_v2_value_cast) + (duckdb_v2_factory_handle factory, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_array_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_array) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_array_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool_with_connection) - (duckdb_v2_connection_handle conn, bool in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool) + (duckdb_v2_factory_handle factory, bool in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool_with_context) - (duckdb_v2_context_handle ctx, bool in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_date_with_connection) - (duckdb_v2_connection_handle conn, int32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_date) + (duckdb_v2_factory_handle factory, int32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_date_with_context) - (duckdb_v2_context_handle ctx, int32_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_double_with_connection) - (duckdb_v2_connection_handle conn, double in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_double_with_context) - (duckdb_v2_context_handle ctx, double in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_float_with_connection) - (duckdb_v2_connection_handle conn, float in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_float_with_context) - (duckdb_v2_context_handle ctx, float in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_double) + (duckdb_v2_factory_handle factory, double in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_float) + (duckdb_v2_factory_handle factory, float in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_int_with_connection) - (duckdb_v2_connection_handle conn, int32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_int_with_context) - (duckdb_v2_context_handle ctx, int32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_int) + (duckdb_v2_factory_handle factory, int32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval) + (duckdb_v2_factory_handle factory, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_list_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_list_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_list) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_map_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, - const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_map_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_map) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_value_create_null) - (duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_null_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_null_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint_with_connection) - (duckdb_v2_connection_handle conn, int16_t in_value, duckdb_v2_value_handle *out_value, + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint_with_context) - (duckdb_v2_context_handle ctx, int16_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint) + (duckdb_v2_factory_handle factory, int16_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct) + (duckdb_v2_factory_handle factory, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, - idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz_with_connection) - (duckdb_v2_connection_handle conn, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz_with_context) - (duckdb_v2_context_handle ctx, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz) + (duckdb_v2_factory_handle factory, uint64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint) + (duckdb_v2_factory_handle factory, int8_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint_with_connection) - (duckdb_v2_connection_handle conn, int8_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint_with_context) - (duckdb_v2_context_handle ctx, int8_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_value_handle *children, idx_t field_count, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple) + (duckdb_v2_factory_handle factory, const duckdb_v2_value_handle *children, idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_type_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_type_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle in_type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint_with_connection) - (duckdb_v2_connection_handle conn, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint_with_context) - (duckdb_v2_context_handle ctx, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_type) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle in_type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint) + (duckdb_v2_factory_handle factory, uint64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint_with_connection) - (duckdb_v2_connection_handle conn, uint32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint) + (duckdb_v2_factory_handle factory, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint_with_context) - (duckdb_v2_context_handle ctx, uint32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint) + (duckdb_v2_factory_handle factory, uint32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint_with_connection) - (duckdb_v2_connection_handle conn, uint16_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint) + (duckdb_v2_factory_handle factory, uint16_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint_with_context) - (duckdb_v2_context_handle ctx, uint16_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint) + (duckdb_v2_factory_handle factory, uint8_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint_with_connection) - (duckdb_v2_connection_handle conn, uint8_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint_with_context) - (duckdb_v2_context_handle ctx, uint8_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_value_destroy)(duckdb_v2_value_handle *value); DUCKDB_V2_ERROR(*duckdb_v2_value_get_bigint) @@ -563,20 +407,20 @@ typedef struct { (duckdb_v2_vector_handle vector, idx_t size, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_vector_set_value) (duckdb_v2_vector_handle vector, idx_t row, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_scalar_function) + (duckdb_v2_connection_handle conn, duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_context_log) (duckdb_v2_context_handle ctx, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_scalar_function) + (duckdb_v2_extension_handle extension, duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_function_signature_add_parameter) (duckdb_v2_function_signature_handle sig, const duckdb_v2_identifier_t *name, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle value, DUCKDB_V2_FUNCTION_PARAMETER_KIND kind, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_function_signature_set_return_type) (duckdb_v2_function_signature_handle sig, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_scalar_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_scalar_function_destroy)(duckdb_v2_scalar_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_exec_get_arg) (duckdb_v2_scalar_function_exec_info_handle info, idx_t index, duckdb_v2_vector_handle *vector, @@ -604,8 +448,6 @@ typedef struct { (duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_init_set_init_data) (duckdb_v2_scalar_function_init_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_register) - (duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_bind_callback) (duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -632,12 +474,8 @@ typedef struct { (duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_combine_get_user_data) (duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_aggregate_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_aggregate_function_destroy)(duckdb_v2_aggregate_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_destroy_get_bind_data) (duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); @@ -671,8 +509,6 @@ typedef struct { (duckdb_v2_aggregate_function_init_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_init_get_user_data) (duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_register) - (duckdb_v2_aggregate_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_set_bind_callback) (duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -719,23 +555,29 @@ typedef struct { (duckdb_v2_aggregate_function_update_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_update_get_user_data) (duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_aggregate_function) + (duckdb_v2_connection_handle conn, duckdb_v2_aggregate_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_aggregate_function) + (duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle function, + duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_set_property) (duckdb_v2_aggregate_function_handle function, DUCKDB_V2_FUNCTION_PROPERTY_KEY key, DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_property) (duckdb_v2_scalar_function_handle function, DUCKDB_V2_FUNCTION_PROPERTY_KEY key, DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_table_function) + (duckdb_v2_connection_handle conn, duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_table_function) + (duckdb_v2_extension_handle extension, duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_bind_add_result_column) (duckdb_v2_table_function_bind_info_handle info, const duckdb_v2_identifier_t *name, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_bind_set_cardinality) (duckdb_v2_table_function_bind_info_handle info, idx_t cardinality, bool is_exact, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_table_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_table_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_table_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_table_function_destroy)(duckdb_v2_table_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_table_function_exec_get_bind_data) (duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); @@ -775,8 +617,6 @@ typedef struct { (duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_progress_set_progress) (duckdb_v2_table_function_progress_info_handle info, double progress, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_register) - (duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_bind_callback) (duckdb_v2_table_function_handle function, duckdb_v2_table_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -796,11 +636,8 @@ typedef struct { duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_user_data) (duckdb_v2_table_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_cast_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_cast_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_cast_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_cast_function_destroy)(duckdb_v2_cast_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_exec_get_input) (duckdb_v2_cast_function_exec_info_handle info, duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); @@ -812,8 +649,6 @@ typedef struct { (duckdb_v2_cast_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_exec_get_user_data) (duckdb_v2_cast_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_register) - (duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_set_exec_callback) (duckdb_v2_cast_function_handle function, duckdb_v2_cast_function_exec_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -829,17 +664,27 @@ typedef struct { (duckdb_v2_cast_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_clear) (duckdb_v2_column_data_collection_handle collection, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_cast_function) + (duckdb_v2_connection_handle conn, duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_custom_type) + (duckdb_v2_connection_handle conn, duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_replacement_scan) + (duckdb_v2_connection_handle conn, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_file_system) + (duckdb_v2_context_handle context, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create) + (duckdb_v2_factory_handle factory, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_custom_type_destroy)(duckdb_v2_custom_type_handle *type); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_register) - (duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_custom_type_set_base_type) (duckdb_v2_custom_type_handle type, duckdb_v2_logical_type_handle base_type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_custom_type_set_name) (duckdb_v2_custom_type_handle type, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_cast_function) + (duckdb_v2_extension_handle extension, duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_custom_type) + (duckdb_v2_extension_handle extension, duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_replacement_scan) + (duckdb_v2_extension_handle extension, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_close)(duckdb_v2_file_handle file, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_destroy)(duckdb_v2_file_handle *file); DUCKDB_V2_ERROR(*duckdb_v2_file_open_options_create) @@ -859,11 +704,6 @@ typedef struct { (duckdb_v2_file_handle file, idx_t position, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_size)(duckdb_v2_file_handle file, idx_t *size, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_sync)(duckdb_v2_file_handle file, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_file_system_get_from_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_file_system_handle *file_system, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_file_system_get_from_context) - (duckdb_v2_context_handle context, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_file_system_open) (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *file_path, duckdb_v2_file_open_options_handle options, duckdb_v2_file_handle *file, duckdb_v2_error_info_handle *err); @@ -875,6 +715,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_file_write_at) (duckdb_v2_file_handle file, const void *buffer, idx_t buffer_size, idx_t location, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_register_replacement_scan) + (duckdb_v2_instance_handle instance, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_qname_create) (const duckdb_v2_identifier_t *parts, idx_t part_count, duckdb_v2_qname_handle *name, duckdb_v2_error_info_handle *err); @@ -897,19 +739,13 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_add_named_argument) (duckdb_v2_replacement_scan_info_handle info, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_instance) - (duckdb_v2_instance_handle instance, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create) + (duckdb_v2_factory_handle factory, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_replacement_scan_destroy)(duckdb_v2_replacement_scan_handle *scan); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_get_name) (duckdb_v2_replacement_scan_info_handle info, duckdb_v2_qname_handle *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_get_user_data) (duckdb_v2_replacement_scan_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_register) - (duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_set_alias) (duckdb_v2_replacement_scan_info_handle info, const duckdb_v2_identifier_t *alias, duckdb_v2_error_info_handle *err); @@ -960,6 +796,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_connection_describe_table) (duckdb_v2_connection_handle conn, duckdb_v2_qname_handle name, duckdb_v2_table_description_handle *desc, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_copy_function) + (duckdb_v2_connection_handle conn, duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_from_bind_get_column_count) (duckdb_v2_copy_from_bind_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_from_bind_get_column_name) @@ -1033,14 +871,9 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_copy_from_set_progress_callback) (duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_progress_callback_fn callback, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_copy_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_copy_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_copy_function_destroy)(duckdb_v2_copy_function_handle *function); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_register) - (duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_name) (duckdb_v2_copy_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_user_data) @@ -1143,6 +976,8 @@ typedef struct { (duckdb_v2_expression_handle expression, duckdb_v2_logical_type_handle *type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_expression_get_type) (duckdb_v2_expression_handle expression, DUCKDB_V2_EXPRESSION_TYPE *type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_copy_function) + (duckdb_v2_extension_handle extension, duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_identifier_render_quoted) (const duckdb_v2_identifier_t *name, char *out_text, idx_t out_capacity, idx_t *out_length, duckdb_v2_error_info_handle *err); @@ -1227,15 +1062,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_attach_options_set) (duckdb_v2_attach_options_handle options, const duckdb_v2_identifier_t *key, const duckdb_v2_str *setting, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_by_index) - (duckdb_v2_context_handle ctx, idx_t index, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_by_name) - (duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_count) - (duckdb_v2_context_handle ctx, idx_t *out_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_set_default) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, duckdb_v2_error_info_handle *err); + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_partition_data_get_bind_data) (duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_partition_data_get_global_state) @@ -1332,8 +1160,186 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_resolve_types_callback) (duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_resolve_types_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_factory) + (duckdb_v2_connection_handle conn, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_factory) + (duckdb_v2_context_handle ctx, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_factory) + (duckdb_v2_instance_handle instance, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_bind_set_bind_data) (duckdb_v2_scalar_function_bind_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_value) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle *out_value, + DUCKDB_V2_SETTING_SCOPE *out_scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_set_option_text) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, + DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_set_option_value) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle value, + DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_config) + (duckdb_v2_connection_handle conn, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_file_system) + (duckdb_v2_connection_handle conn, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_log) + (duckdb_v2_connection_handle conn, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, + const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_config) + (duckdb_v2_context_handle ctx, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_config) + (duckdb_v2_instance_handle instance, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_file_system) + (duckdb_v2_instance_handle instance, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_log) + (duckdb_v2_instance_handle instance, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, + const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_scope) + (duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE *out_scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_supports_scope) + (duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, bool *out_supported, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_get_docs) + (duckdb_v2_aggregate_function_handle function, duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_pragma_function) + (duckdb_v2_connection_handle conn, duckdb_v2_pragma_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_pragma_function) + (duckdb_v2_extension_handle extension, duckdb_v2_pragma_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_add_category) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *category, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_add_example) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *title, const duckdb_v2_str *example, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_set_description) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *description, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_pragma_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_pragma_function_destroy)(duckdb_v2_pragma_function_handle *function); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_get_docs) + (duckdb_v2_pragma_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_get_signature) + (duckdb_v2_pragma_function_handle function, duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_query_set_query) + (duckdb_v2_pragma_function_query_info_handle info, const duckdb_v2_str *query, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_exec_callback) + (duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_name) + (duckdb_v2_pragma_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_query_callback) + (duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_query_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_user_data) + (duckdb_v2_pragma_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_get_docs) + (duckdb_v2_scalar_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_table_function_get_docs) + (duckdb_v2_table_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_custom_option) + (duckdb_v2_connection_handle conn, duckdb_v2_custom_option_handle option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_create) + (duckdb_v2_factory_handle factory, duckdb_v2_custom_option_handle *option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_custom_option_destroy)(duckdb_v2_custom_option_handle *option); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_default_scope) + (duckdb_v2_custom_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_default_value) + (duckdb_v2_custom_option_handle option, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_description) + (duckdb_v2_custom_option_handle option, const duckdb_v2_str *description, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_name) + (duckdb_v2_custom_option_handle option, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_supports_scope) + (duckdb_v2_custom_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, bool supported, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_type) + (duckdb_v2_custom_option_handle option, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_update_callback) + (duckdb_v2_custom_option_handle option, duckdb_v2_custom_option_update_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_user_data) + (duckdb_v2_custom_option_handle option, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_scope) + (duckdb_v2_custom_option_update_info_handle info, DUCKDB_V2_SETTING_SCOPE *scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_user_data) + (duckdb_v2_custom_option_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_value) + (duckdb_v2_custom_option_update_info_handle info, duckdb_v2_value_handle *value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_is_reset) + (duckdb_v2_custom_option_update_info_handle info, bool *is_reset, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_set_value) + (duckdb_v2_custom_option_update_info_handle info, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_custom_option) + (duckdb_v2_extension_handle extension, duckdb_v2_custom_option_handle option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_log_sink) + (duckdb_v2_extension_handle extension, duckdb_v2_log_sink_handle sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_macro_function) + (duckdb_v2_extension_handle extension, duckdb_v2_macro_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_exists) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_exists, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_expand) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, duckdb_v2_text_sink_fn sink, void *user_data, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_is_remote) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_is_remote, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_register_log_sink) + (duckdb_v2_instance_handle instance, duckdb_v2_log_sink_handle sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_create) + (duckdb_v2_factory_handle factory, duckdb_v2_log_sink_handle *sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_log_sink_destroy)(duckdb_v2_log_sink_handle *sink); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_flush_callback) + (duckdb_v2_log_sink_handle sink, duckdb_v2_log_sink_flush_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_name) + (duckdb_v2_log_sink_handle sink, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_user_data) + (duckdb_v2_log_sink_handle sink, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_write_callback) + (duckdb_v2_log_sink_handle sink, duckdb_v2_log_sink_write_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_connection_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_query_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_thread_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_transaction_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_create) + (duckdb_v2_factory_handle factory, DUCKDB_V2_MACRO_FUNCTION_TYPE type, duckdb_v2_macro_function_handle *function, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_macro_function_destroy)(duckdb_v2_macro_function_handle *function); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_get_docs) + (duckdb_v2_macro_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_get_signature) + (duckdb_v2_macro_function_handle function, duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_set_body) + (duckdb_v2_macro_function_handle function, const duckdb_v2_str *body, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_set_name) + (duckdb_v2_macro_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_vector_cast) + (duckdb_v2_factory_handle factory, duckdb_v2_vector_handle source, duckdb_v2_vector_handle target, + DUCKDB_V2_CAST_MODE mode, bool *out_converted, duckdb_v2_error_info_handle *cast_err, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_file_extension) + (duckdb_v2_copy_function_handle function, const duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_data_chunk_clear)(duckdb_v2_data_chunk_handle chunk, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_glob) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *pattern, + duckdb_v2_file_system_path_glob_callback_fn callback, void *user_data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_move) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *source, const duckdb_v2_str *target, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_remove) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_removed, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_statement_add_collection) + (duckdb_v2_sql_statement_handle statement, const duckdb_v2_identifier_t *name, + duckdb_v2_column_data_collection_handle collection, const duckdb_v2_identifier_t *column_names, idx_t column_count, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_multi_file_function) + (duckdb_v2_connection_handle conn, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_to_set_statistics_callback) (duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_statistics_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -1349,15 +1355,13 @@ typedef struct { (duckdb_v2_copy_to_statistics_info_handle info, idx_t row_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_get_capacity) (duckdb_v2_data_chunk_handle chunk, idx_t *out_capacity, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_multi_file_function_handle *function, + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_multi_file_function) + (duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle *function, + DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_multi_file_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_multi_file_function_destroy)(duckdb_v2_multi_file_function_handle *function); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_register) - (duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_set_file_extension) (duckdb_v2_multi_file_function_handle function, duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_set_name) @@ -1392,6 +1396,9 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_get_bind_info_callback) (duckdb_v2_table_function_handle function, duckdb_v2_table_function_get_bind_info_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_common_type) + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types, idx_t type_count, + duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); } duckdb_ext_api_v2; //===--------------------------------------------------------------------===// @@ -1407,9 +1414,7 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_column_data_collection_append_state_destroy = duckdb_v2_column_data_collection_append_state_destroy; result.duckdb_v2_column_data_collection_combine = duckdb_v2_column_data_collection_combine; - result.duckdb_v2_column_data_collection_create_with_connection = - duckdb_v2_column_data_collection_create_with_connection; - result.duckdb_v2_column_data_collection_create_with_context = duckdb_v2_column_data_collection_create_with_context; + result.duckdb_v2_column_data_collection_create = duckdb_v2_column_data_collection_create; result.duckdb_v2_column_data_collection_destroy = duckdb_v2_column_data_collection_destroy; result.duckdb_v2_column_data_collection_reset = duckdb_v2_column_data_collection_reset; result.duckdb_v2_column_data_collection_row_count = duckdb_v2_column_data_collection_row_count; @@ -1422,27 +1427,15 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { duckdb_v2_column_data_collection_worker_scan_state_create; result.duckdb_v2_column_data_collection_worker_scan_state_destroy = duckdb_v2_column_data_collection_worker_scan_state_destroy; + result.duckdb_v2_config_get_option_by_index = duckdb_v2_config_get_option_by_index; + result.duckdb_v2_config_get_option_by_name = duckdb_v2_config_get_option_by_name; + result.duckdb_v2_config_get_option_count = duckdb_v2_config_get_option_count; result.duckdb_v2_connection_create = duckdb_v2_connection_create; - result.duckdb_v2_connection_create_type_from_id = duckdb_v2_connection_create_type_from_id; - result.duckdb_v2_connection_create_type_from_name = duckdb_v2_connection_create_type_from_name; - result.duckdb_v2_connection_create_type_from_text = duckdb_v2_connection_create_type_from_text; - result.duckdb_v2_connection_create_type_with_alias = duckdb_v2_connection_create_type_with_alias; result.duckdb_v2_connection_destroy = duckdb_v2_connection_destroy; - result.duckdb_v2_connection_get_option_by_index = duckdb_v2_connection_get_option_by_index; - result.duckdb_v2_connection_get_option_by_name = duckdb_v2_connection_get_option_by_name; - result.duckdb_v2_connection_get_option_count = duckdb_v2_connection_get_option_count; result.duckdb_v2_connection_interrupt = duckdb_v2_connection_interrupt; result.duckdb_v2_connection_progress_get = duckdb_v2_connection_progress_get; - result.duckdb_v2_connection_set_option = duckdb_v2_connection_set_option; - result.duckdb_v2_context_create_type_from_id = duckdb_v2_context_create_type_from_id; - result.duckdb_v2_context_create_type_from_name = duckdb_v2_context_create_type_from_name; - result.duckdb_v2_context_create_type_from_text = duckdb_v2_context_create_type_from_text; - result.duckdb_v2_context_create_type_with_alias = duckdb_v2_context_create_type_with_alias; - result.duckdb_v2_data_chunk_copy_with_connection = duckdb_v2_data_chunk_copy_with_connection; - result.duckdb_v2_data_chunk_copy_with_context = duckdb_v2_data_chunk_copy_with_context; + result.duckdb_v2_data_chunk_copy = duckdb_v2_data_chunk_copy; result.duckdb_v2_data_chunk_create = duckdb_v2_data_chunk_create; - result.duckdb_v2_data_chunk_create_with_connection = duckdb_v2_data_chunk_create_with_connection; - result.duckdb_v2_data_chunk_create_with_context = duckdb_v2_data_chunk_create_with_context; result.duckdb_v2_data_chunk_destroy = duckdb_v2_data_chunk_destroy; result.duckdb_v2_data_chunk_get_size = duckdb_v2_data_chunk_get_size; result.duckdb_v2_data_chunk_get_vector = duckdb_v2_data_chunk_get_vector; @@ -1456,14 +1449,15 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_error_info_get_text = duckdb_v2_error_info_get_text; result.duckdb_v2_error_info_set_code = duckdb_v2_error_info_set_code; result.duckdb_v2_error_info_set_text = duckdb_v2_error_info_set_text; + result.duckdb_v2_factory_create_type_from_id = duckdb_v2_factory_create_type_from_id; + result.duckdb_v2_factory_create_type_from_name = duckdb_v2_factory_create_type_from_name; + result.duckdb_v2_factory_create_type_from_text = duckdb_v2_factory_create_type_from_text; + result.duckdb_v2_factory_create_type_with_alias = duckdb_v2_factory_create_type_with_alias; result.duckdb_v2_instance_attach = duckdb_v2_instance_attach; result.duckdb_v2_instance_create = duckdb_v2_instance_create; + result.duckdb_v2_instance_create_with_options = duckdb_v2_instance_create_with_options; result.duckdb_v2_instance_destroy = duckdb_v2_instance_destroy; result.duckdb_v2_instance_detach = duckdb_v2_instance_detach; - result.duckdb_v2_instance_get_option_by_index = duckdb_v2_instance_get_option_by_index; - result.duckdb_v2_instance_get_option_by_name = duckdb_v2_instance_get_option_by_name; - result.duckdb_v2_instance_get_option_count = duckdb_v2_instance_get_option_count; - result.duckdb_v2_instance_set_option = duckdb_v2_instance_set_option; result.duckdb_v2_library_version = duckdb_v2_library_version; result.duckdb_v2_logical_type_copy = duckdb_v2_logical_type_copy; result.duckdb_v2_logical_type_destroy = duckdb_v2_logical_type_destroy; @@ -1476,11 +1470,9 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_option_destroy = duckdb_v2_option_destroy; result.duckdb_v2_option_get_alias = duckdb_v2_option_get_alias; result.duckdb_v2_option_get_alias_count = duckdb_v2_option_get_alias_count; - result.duckdb_v2_option_get_default_setting = duckdb_v2_option_get_default_setting; + result.duckdb_v2_option_get_default_value = duckdb_v2_option_get_default_value; result.duckdb_v2_option_get_description = duckdb_v2_option_get_description; result.duckdb_v2_option_get_name = duckdb_v2_option_get_name; - result.duckdb_v2_option_get_setting = duckdb_v2_option_get_setting; - result.duckdb_v2_option_get_target_scope = duckdb_v2_option_get_target_scope; result.duckdb_v2_parse_sql = duckdb_v2_parse_sql; result.duckdb_v2_result_destroy = duckdb_v2_result_destroy; result.duckdb_v2_result_drain = duckdb_v2_result_drain; @@ -1499,84 +1491,44 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_statement_execute = duckdb_v2_statement_execute; result.duckdb_v2_statement_iterator_destroy = duckdb_v2_statement_iterator_destroy; result.duckdb_v2_statement_iterator_next = duckdb_v2_statement_iterator_next; - result.duckdb_v2_value_cast_with_connection = duckdb_v2_value_cast_with_connection; - result.duckdb_v2_value_cast_with_context = duckdb_v2_value_cast_with_context; - result.duckdb_v2_value_create_array_with_connection = duckdb_v2_value_create_array_with_connection; - result.duckdb_v2_value_create_array_with_context = duckdb_v2_value_create_array_with_context; - result.duckdb_v2_value_create_bigint_with_connection = duckdb_v2_value_create_bigint_with_connection; - result.duckdb_v2_value_create_bigint_with_context = duckdb_v2_value_create_bigint_with_context; - result.duckdb_v2_value_create_bignum_with_connection = duckdb_v2_value_create_bignum_with_connection; - result.duckdb_v2_value_create_bignum_with_context = duckdb_v2_value_create_bignum_with_context; - result.duckdb_v2_value_create_bit_with_connection = duckdb_v2_value_create_bit_with_connection; - result.duckdb_v2_value_create_bit_with_context = duckdb_v2_value_create_bit_with_context; - result.duckdb_v2_value_create_blob_with_connection = duckdb_v2_value_create_blob_with_connection; - result.duckdb_v2_value_create_blob_with_context = duckdb_v2_value_create_blob_with_context; - result.duckdb_v2_value_create_bool_with_connection = duckdb_v2_value_create_bool_with_connection; - result.duckdb_v2_value_create_bool_with_context = duckdb_v2_value_create_bool_with_context; - result.duckdb_v2_value_create_date_with_connection = duckdb_v2_value_create_date_with_connection; - result.duckdb_v2_value_create_date_with_context = duckdb_v2_value_create_date_with_context; - result.duckdb_v2_value_create_decimal_with_connection = duckdb_v2_value_create_decimal_with_connection; - result.duckdb_v2_value_create_decimal_with_context = duckdb_v2_value_create_decimal_with_context; - result.duckdb_v2_value_create_double_with_connection = duckdb_v2_value_create_double_with_connection; - result.duckdb_v2_value_create_double_with_context = duckdb_v2_value_create_double_with_context; - result.duckdb_v2_value_create_float_with_connection = duckdb_v2_value_create_float_with_connection; - result.duckdb_v2_value_create_float_with_context = duckdb_v2_value_create_float_with_context; - result.duckdb_v2_value_create_hugeint_with_connection = duckdb_v2_value_create_hugeint_with_connection; - result.duckdb_v2_value_create_hugeint_with_context = duckdb_v2_value_create_hugeint_with_context; - result.duckdb_v2_value_create_int_with_connection = duckdb_v2_value_create_int_with_connection; - result.duckdb_v2_value_create_int_with_context = duckdb_v2_value_create_int_with_context; - result.duckdb_v2_value_create_interval_with_connection = duckdb_v2_value_create_interval_with_connection; - result.duckdb_v2_value_create_interval_with_context = duckdb_v2_value_create_interval_with_context; - result.duckdb_v2_value_create_list_with_connection = duckdb_v2_value_create_list_with_connection; - result.duckdb_v2_value_create_list_with_context = duckdb_v2_value_create_list_with_context; - result.duckdb_v2_value_create_map_with_connection = duckdb_v2_value_create_map_with_connection; - result.duckdb_v2_value_create_map_with_context = duckdb_v2_value_create_map_with_context; + result.duckdb_v2_value_cast = duckdb_v2_value_cast; + result.duckdb_v2_value_create_array = duckdb_v2_value_create_array; + result.duckdb_v2_value_create_bigint = duckdb_v2_value_create_bigint; + result.duckdb_v2_value_create_bignum = duckdb_v2_value_create_bignum; + result.duckdb_v2_value_create_bit = duckdb_v2_value_create_bit; + result.duckdb_v2_value_create_blob = duckdb_v2_value_create_blob; + result.duckdb_v2_value_create_bool = duckdb_v2_value_create_bool; + result.duckdb_v2_value_create_date = duckdb_v2_value_create_date; + result.duckdb_v2_value_create_decimal = duckdb_v2_value_create_decimal; + result.duckdb_v2_value_create_double = duckdb_v2_value_create_double; + result.duckdb_v2_value_create_float = duckdb_v2_value_create_float; + result.duckdb_v2_value_create_hugeint = duckdb_v2_value_create_hugeint; + result.duckdb_v2_value_create_int = duckdb_v2_value_create_int; + result.duckdb_v2_value_create_interval = duckdb_v2_value_create_interval; + result.duckdb_v2_value_create_list = duckdb_v2_value_create_list; + result.duckdb_v2_value_create_map = duckdb_v2_value_create_map; result.duckdb_v2_value_create_null = duckdb_v2_value_create_null; - result.duckdb_v2_value_create_null_with_connection = duckdb_v2_value_create_null_with_connection; - result.duckdb_v2_value_create_null_with_context = duckdb_v2_value_create_null_with_context; - result.duckdb_v2_value_create_smallint_with_connection = duckdb_v2_value_create_smallint_with_connection; - result.duckdb_v2_value_create_smallint_with_context = duckdb_v2_value_create_smallint_with_context; - result.duckdb_v2_value_create_struct_with_connection = duckdb_v2_value_create_struct_with_connection; - result.duckdb_v2_value_create_struct_with_context = duckdb_v2_value_create_struct_with_context; - result.duckdb_v2_value_create_time_ns_with_connection = duckdb_v2_value_create_time_ns_with_connection; - result.duckdb_v2_value_create_time_ns_with_context = duckdb_v2_value_create_time_ns_with_context; - result.duckdb_v2_value_create_time_tz_with_connection = duckdb_v2_value_create_time_tz_with_connection; - result.duckdb_v2_value_create_time_tz_with_context = duckdb_v2_value_create_time_tz_with_context; - result.duckdb_v2_value_create_time_with_connection = duckdb_v2_value_create_time_with_connection; - result.duckdb_v2_value_create_time_with_context = duckdb_v2_value_create_time_with_context; - result.duckdb_v2_value_create_timestamp_ms_with_connection = duckdb_v2_value_create_timestamp_ms_with_connection; - result.duckdb_v2_value_create_timestamp_ms_with_context = duckdb_v2_value_create_timestamp_ms_with_context; - result.duckdb_v2_value_create_timestamp_ns_with_connection = duckdb_v2_value_create_timestamp_ns_with_connection; - result.duckdb_v2_value_create_timestamp_ns_with_context = duckdb_v2_value_create_timestamp_ns_with_context; - result.duckdb_v2_value_create_timestamp_sec_with_connection = duckdb_v2_value_create_timestamp_sec_with_connection; - result.duckdb_v2_value_create_timestamp_sec_with_context = duckdb_v2_value_create_timestamp_sec_with_context; - result.duckdb_v2_value_create_timestamp_tz_ns_with_connection = - duckdb_v2_value_create_timestamp_tz_ns_with_connection; - result.duckdb_v2_value_create_timestamp_tz_ns_with_context = duckdb_v2_value_create_timestamp_tz_ns_with_context; - result.duckdb_v2_value_create_timestamp_tz_with_connection = duckdb_v2_value_create_timestamp_tz_with_connection; - result.duckdb_v2_value_create_timestamp_tz_with_context = duckdb_v2_value_create_timestamp_tz_with_context; - result.duckdb_v2_value_create_timestamp_with_connection = duckdb_v2_value_create_timestamp_with_connection; - result.duckdb_v2_value_create_timestamp_with_context = duckdb_v2_value_create_timestamp_with_context; - result.duckdb_v2_value_create_tinyint_with_connection = duckdb_v2_value_create_tinyint_with_connection; - result.duckdb_v2_value_create_tinyint_with_context = duckdb_v2_value_create_tinyint_with_context; - result.duckdb_v2_value_create_tuple_with_connection = duckdb_v2_value_create_tuple_with_connection; - result.duckdb_v2_value_create_tuple_with_context = duckdb_v2_value_create_tuple_with_context; - result.duckdb_v2_value_create_type_with_connection = duckdb_v2_value_create_type_with_connection; - result.duckdb_v2_value_create_type_with_context = duckdb_v2_value_create_type_with_context; - result.duckdb_v2_value_create_ubigint_with_connection = duckdb_v2_value_create_ubigint_with_connection; - result.duckdb_v2_value_create_ubigint_with_context = duckdb_v2_value_create_ubigint_with_context; - result.duckdb_v2_value_create_uhugeint_with_connection = duckdb_v2_value_create_uhugeint_with_connection; - result.duckdb_v2_value_create_uhugeint_with_context = duckdb_v2_value_create_uhugeint_with_context; - result.duckdb_v2_value_create_uint_with_connection = duckdb_v2_value_create_uint_with_connection; - result.duckdb_v2_value_create_uint_with_context = duckdb_v2_value_create_uint_with_context; - result.duckdb_v2_value_create_usmallint_with_connection = duckdb_v2_value_create_usmallint_with_connection; - result.duckdb_v2_value_create_usmallint_with_context = duckdb_v2_value_create_usmallint_with_context; - result.duckdb_v2_value_create_utinyint_with_connection = duckdb_v2_value_create_utinyint_with_connection; - result.duckdb_v2_value_create_utinyint_with_context = duckdb_v2_value_create_utinyint_with_context; - result.duckdb_v2_value_create_uuid_with_connection = duckdb_v2_value_create_uuid_with_connection; - result.duckdb_v2_value_create_uuid_with_context = duckdb_v2_value_create_uuid_with_context; - result.duckdb_v2_value_create_varchar_with_connection = duckdb_v2_value_create_varchar_with_connection; - result.duckdb_v2_value_create_varchar_with_context = duckdb_v2_value_create_varchar_with_context; + result.duckdb_v2_value_create_smallint = duckdb_v2_value_create_smallint; + result.duckdb_v2_value_create_struct = duckdb_v2_value_create_struct; + result.duckdb_v2_value_create_time = duckdb_v2_value_create_time; + result.duckdb_v2_value_create_time_ns = duckdb_v2_value_create_time_ns; + result.duckdb_v2_value_create_time_tz = duckdb_v2_value_create_time_tz; + result.duckdb_v2_value_create_timestamp = duckdb_v2_value_create_timestamp; + result.duckdb_v2_value_create_timestamp_ms = duckdb_v2_value_create_timestamp_ms; + result.duckdb_v2_value_create_timestamp_ns = duckdb_v2_value_create_timestamp_ns; + result.duckdb_v2_value_create_timestamp_sec = duckdb_v2_value_create_timestamp_sec; + result.duckdb_v2_value_create_timestamp_tz = duckdb_v2_value_create_timestamp_tz; + result.duckdb_v2_value_create_timestamp_tz_ns = duckdb_v2_value_create_timestamp_tz_ns; + result.duckdb_v2_value_create_tinyint = duckdb_v2_value_create_tinyint; + result.duckdb_v2_value_create_tuple = duckdb_v2_value_create_tuple; + result.duckdb_v2_value_create_type = duckdb_v2_value_create_type; + result.duckdb_v2_value_create_ubigint = duckdb_v2_value_create_ubigint; + result.duckdb_v2_value_create_uhugeint = duckdb_v2_value_create_uhugeint; + result.duckdb_v2_value_create_uint = duckdb_v2_value_create_uint; + result.duckdb_v2_value_create_usmallint = duckdb_v2_value_create_usmallint; + result.duckdb_v2_value_create_utinyint = duckdb_v2_value_create_utinyint; + result.duckdb_v2_value_create_uuid = duckdb_v2_value_create_uuid; + result.duckdb_v2_value_create_varchar = duckdb_v2_value_create_varchar; result.duckdb_v2_value_destroy = duckdb_v2_value_destroy; result.duckdb_v2_value_get_bigint = duckdb_v2_value_get_bigint; result.duckdb_v2_value_get_blob = duckdb_v2_value_get_blob; @@ -1630,11 +1582,12 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_vector_set_null = duckdb_v2_vector_set_null; result.duckdb_v2_vector_set_size = duckdb_v2_vector_set_size; result.duckdb_v2_vector_set_value = duckdb_v2_vector_set_value; + result.duckdb_v2_connection_register_scalar_function = duckdb_v2_connection_register_scalar_function; result.duckdb_v2_context_log = duckdb_v2_context_log; + result.duckdb_v2_extension_register_scalar_function = duckdb_v2_extension_register_scalar_function; result.duckdb_v2_function_signature_add_parameter = duckdb_v2_function_signature_add_parameter; result.duckdb_v2_function_signature_set_return_type = duckdb_v2_function_signature_set_return_type; - result.duckdb_v2_scalar_function_create_with_connection = duckdb_v2_scalar_function_create_with_connection; - result.duckdb_v2_scalar_function_create_with_extension = duckdb_v2_scalar_function_create_with_extension; + result.duckdb_v2_scalar_function_create = duckdb_v2_scalar_function_create; result.duckdb_v2_scalar_function_destroy = duckdb_v2_scalar_function_destroy; result.duckdb_v2_scalar_function_exec_get_arg = duckdb_v2_scalar_function_exec_get_arg; result.duckdb_v2_scalar_function_exec_get_arg_count = duckdb_v2_scalar_function_exec_get_arg_count; @@ -1647,7 +1600,6 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_scalar_function_init_get_bind_data = duckdb_v2_scalar_function_init_get_bind_data; result.duckdb_v2_scalar_function_init_get_user_data = duckdb_v2_scalar_function_init_get_user_data; result.duckdb_v2_scalar_function_init_set_init_data = duckdb_v2_scalar_function_init_set_init_data; - result.duckdb_v2_scalar_function_register = duckdb_v2_scalar_function_register; result.duckdb_v2_scalar_function_set_bind_callback = duckdb_v2_scalar_function_set_bind_callback; result.duckdb_v2_scalar_function_set_exec_callback = duckdb_v2_scalar_function_set_exec_callback; result.duckdb_v2_scalar_function_set_init_callback = duckdb_v2_scalar_function_set_init_callback; @@ -1659,8 +1611,7 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_aggregate_function_combine_get_state_count = duckdb_v2_aggregate_function_combine_get_state_count; result.duckdb_v2_aggregate_function_combine_get_targets = duckdb_v2_aggregate_function_combine_get_targets; result.duckdb_v2_aggregate_function_combine_get_user_data = duckdb_v2_aggregate_function_combine_get_user_data; - result.duckdb_v2_aggregate_function_create_with_connection = duckdb_v2_aggregate_function_create_with_connection; - result.duckdb_v2_aggregate_function_create_with_extension = duckdb_v2_aggregate_function_create_with_extension; + result.duckdb_v2_aggregate_function_create = duckdb_v2_aggregate_function_create; result.duckdb_v2_aggregate_function_destroy = duckdb_v2_aggregate_function_destroy; result.duckdb_v2_aggregate_function_destroy_get_bind_data = duckdb_v2_aggregate_function_destroy_get_bind_data; result.duckdb_v2_aggregate_function_destroy_get_state_count = duckdb_v2_aggregate_function_destroy_get_state_count; @@ -1679,7 +1630,6 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_aggregate_function_init_get_state_count = duckdb_v2_aggregate_function_init_get_state_count; result.duckdb_v2_aggregate_function_init_get_states = duckdb_v2_aggregate_function_init_get_states; result.duckdb_v2_aggregate_function_init_get_user_data = duckdb_v2_aggregate_function_init_get_user_data; - result.duckdb_v2_aggregate_function_register = duckdb_v2_aggregate_function_register; result.duckdb_v2_aggregate_function_set_bind_callback = duckdb_v2_aggregate_function_set_bind_callback; result.duckdb_v2_aggregate_function_set_combine_callback = duckdb_v2_aggregate_function_set_combine_callback; result.duckdb_v2_aggregate_function_set_destroy_callback = duckdb_v2_aggregate_function_set_destroy_callback; @@ -1698,12 +1648,15 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_aggregate_function_update_get_row_count = duckdb_v2_aggregate_function_update_get_row_count; result.duckdb_v2_aggregate_function_update_get_states = duckdb_v2_aggregate_function_update_get_states; result.duckdb_v2_aggregate_function_update_get_user_data = duckdb_v2_aggregate_function_update_get_user_data; + result.duckdb_v2_connection_register_aggregate_function = duckdb_v2_connection_register_aggregate_function; + result.duckdb_v2_extension_register_aggregate_function = duckdb_v2_extension_register_aggregate_function; result.duckdb_v2_aggregate_function_set_property = duckdb_v2_aggregate_function_set_property; result.duckdb_v2_scalar_function_set_property = duckdb_v2_scalar_function_set_property; + result.duckdb_v2_connection_register_table_function = duckdb_v2_connection_register_table_function; + result.duckdb_v2_extension_register_table_function = duckdb_v2_extension_register_table_function; result.duckdb_v2_table_function_bind_add_result_column = duckdb_v2_table_function_bind_add_result_column; result.duckdb_v2_table_function_bind_set_cardinality = duckdb_v2_table_function_bind_set_cardinality; - result.duckdb_v2_table_function_create_with_connection = duckdb_v2_table_function_create_with_connection; - result.duckdb_v2_table_function_create_with_extension = duckdb_v2_table_function_create_with_extension; + result.duckdb_v2_table_function_create = duckdb_v2_table_function_create; result.duckdb_v2_table_function_destroy = duckdb_v2_table_function_destroy; result.duckdb_v2_table_function_exec_get_bind_data = duckdb_v2_table_function_exec_get_bind_data; result.duckdb_v2_table_function_exec_get_global_state = duckdb_v2_table_function_exec_get_global_state; @@ -1724,7 +1677,6 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_table_function_progress_get_global_state = duckdb_v2_table_function_progress_get_global_state; result.duckdb_v2_table_function_progress_get_user_data = duckdb_v2_table_function_progress_get_user_data; result.duckdb_v2_table_function_progress_set_progress = duckdb_v2_table_function_progress_set_progress; - result.duckdb_v2_table_function_register = duckdb_v2_table_function_register; result.duckdb_v2_table_function_set_bind_callback = duckdb_v2_table_function_set_bind_callback; result.duckdb_v2_table_function_set_exec_callback = duckdb_v2_table_function_set_exec_callback; result.duckdb_v2_table_function_set_init_global_callback = duckdb_v2_table_function_set_init_global_callback; @@ -1732,27 +1684,30 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_table_function_set_name = duckdb_v2_table_function_set_name; result.duckdb_v2_table_function_set_progress_callback = duckdb_v2_table_function_set_progress_callback; result.duckdb_v2_table_function_set_user_data = duckdb_v2_table_function_set_user_data; - result.duckdb_v2_cast_function_create_with_connection = duckdb_v2_cast_function_create_with_connection; - result.duckdb_v2_cast_function_create_with_extension = duckdb_v2_cast_function_create_with_extension; + result.duckdb_v2_cast_function_create = duckdb_v2_cast_function_create; result.duckdb_v2_cast_function_destroy = duckdb_v2_cast_function_destroy; result.duckdb_v2_cast_function_exec_get_input = duckdb_v2_cast_function_exec_get_input; result.duckdb_v2_cast_function_exec_get_mode = duckdb_v2_cast_function_exec_get_mode; result.duckdb_v2_cast_function_exec_get_output = duckdb_v2_cast_function_exec_get_output; result.duckdb_v2_cast_function_exec_get_row_count = duckdb_v2_cast_function_exec_get_row_count; result.duckdb_v2_cast_function_exec_get_user_data = duckdb_v2_cast_function_exec_get_user_data; - result.duckdb_v2_cast_function_register = duckdb_v2_cast_function_register; result.duckdb_v2_cast_function_set_exec_callback = duckdb_v2_cast_function_set_exec_callback; result.duckdb_v2_cast_function_set_implicit_cast_cost = duckdb_v2_cast_function_set_implicit_cast_cost; result.duckdb_v2_cast_function_set_source_type = duckdb_v2_cast_function_set_source_type; result.duckdb_v2_cast_function_set_target_type = duckdb_v2_cast_function_set_target_type; result.duckdb_v2_cast_function_set_user_data = duckdb_v2_cast_function_set_user_data; result.duckdb_v2_column_data_collection_clear = duckdb_v2_column_data_collection_clear; - result.duckdb_v2_custom_type_create_with_connection = duckdb_v2_custom_type_create_with_connection; - result.duckdb_v2_custom_type_create_with_extension = duckdb_v2_custom_type_create_with_extension; + result.duckdb_v2_connection_register_cast_function = duckdb_v2_connection_register_cast_function; + result.duckdb_v2_connection_register_custom_type = duckdb_v2_connection_register_custom_type; + result.duckdb_v2_connection_register_replacement_scan = duckdb_v2_connection_register_replacement_scan; + result.duckdb_v2_context_get_file_system = duckdb_v2_context_get_file_system; + result.duckdb_v2_custom_type_create = duckdb_v2_custom_type_create; result.duckdb_v2_custom_type_destroy = duckdb_v2_custom_type_destroy; - result.duckdb_v2_custom_type_register = duckdb_v2_custom_type_register; result.duckdb_v2_custom_type_set_base_type = duckdb_v2_custom_type_set_base_type; result.duckdb_v2_custom_type_set_name = duckdb_v2_custom_type_set_name; + result.duckdb_v2_extension_register_cast_function = duckdb_v2_extension_register_cast_function; + result.duckdb_v2_extension_register_custom_type = duckdb_v2_extension_register_custom_type; + result.duckdb_v2_extension_register_replacement_scan = duckdb_v2_extension_register_replacement_scan; result.duckdb_v2_file_close = duckdb_v2_file_close; result.duckdb_v2_file_destroy = duckdb_v2_file_destroy; result.duckdb_v2_file_open_options_create = duckdb_v2_file_open_options_create; @@ -1764,12 +1719,11 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_file_seek = duckdb_v2_file_seek; result.duckdb_v2_file_size = duckdb_v2_file_size; result.duckdb_v2_file_sync = duckdb_v2_file_sync; - result.duckdb_v2_file_system_get_from_connection = duckdb_v2_file_system_get_from_connection; - result.duckdb_v2_file_system_get_from_context = duckdb_v2_file_system_get_from_context; result.duckdb_v2_file_system_open = duckdb_v2_file_system_open; result.duckdb_v2_file_tell = duckdb_v2_file_tell; result.duckdb_v2_file_write = duckdb_v2_file_write; result.duckdb_v2_file_write_at = duckdb_v2_file_write_at; + result.duckdb_v2_instance_register_replacement_scan = duckdb_v2_instance_register_replacement_scan; result.duckdb_v2_qname_create = duckdb_v2_qname_create; result.duckdb_v2_qname_destroy = duckdb_v2_qname_destroy; result.duckdb_v2_qname_equals = duckdb_v2_qname_equals; @@ -1780,13 +1734,10 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_qname_render = duckdb_v2_qname_render; result.duckdb_v2_replacement_scan_add_argument = duckdb_v2_replacement_scan_add_argument; result.duckdb_v2_replacement_scan_add_named_argument = duckdb_v2_replacement_scan_add_named_argument; - result.duckdb_v2_replacement_scan_create_with_connection = duckdb_v2_replacement_scan_create_with_connection; - result.duckdb_v2_replacement_scan_create_with_extension = duckdb_v2_replacement_scan_create_with_extension; - result.duckdb_v2_replacement_scan_create_with_instance = duckdb_v2_replacement_scan_create_with_instance; + result.duckdb_v2_replacement_scan_create = duckdb_v2_replacement_scan_create; result.duckdb_v2_replacement_scan_destroy = duckdb_v2_replacement_scan_destroy; result.duckdb_v2_replacement_scan_get_name = duckdb_v2_replacement_scan_get_name; result.duckdb_v2_replacement_scan_get_user_data = duckdb_v2_replacement_scan_get_user_data; - result.duckdb_v2_replacement_scan_register = duckdb_v2_replacement_scan_register; result.duckdb_v2_replacement_scan_set_alias = duckdb_v2_replacement_scan_set_alias; result.duckdb_v2_replacement_scan_set_callback = duckdb_v2_replacement_scan_set_callback; result.duckdb_v2_replacement_scan_set_collection = duckdb_v2_replacement_scan_set_collection; @@ -1809,6 +1760,7 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_column_description_has_default = duckdb_v2_column_description_has_default; result.duckdb_v2_column_description_has_generated = duckdb_v2_column_description_has_generated; result.duckdb_v2_connection_describe_table = duckdb_v2_connection_describe_table; + result.duckdb_v2_connection_register_copy_function = duckdb_v2_connection_register_copy_function; result.duckdb_v2_copy_from_bind_get_column_count = duckdb_v2_copy_from_bind_get_column_count; result.duckdb_v2_copy_from_bind_get_column_name = duckdb_v2_copy_from_bind_get_column_name; result.duckdb_v2_copy_from_bind_get_column_type = duckdb_v2_copy_from_bind_get_column_type; @@ -1841,10 +1793,8 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_copy_from_set_init_global_callback = duckdb_v2_copy_from_set_init_global_callback; result.duckdb_v2_copy_from_set_init_local_callback = duckdb_v2_copy_from_set_init_local_callback; result.duckdb_v2_copy_from_set_progress_callback = duckdb_v2_copy_from_set_progress_callback; - result.duckdb_v2_copy_function_create_with_connection = duckdb_v2_copy_function_create_with_connection; - result.duckdb_v2_copy_function_create_with_extension = duckdb_v2_copy_function_create_with_extension; + result.duckdb_v2_copy_function_create = duckdb_v2_copy_function_create; result.duckdb_v2_copy_function_destroy = duckdb_v2_copy_function_destroy; - result.duckdb_v2_copy_function_register = duckdb_v2_copy_function_register; result.duckdb_v2_copy_function_set_name = duckdb_v2_copy_function_set_name; result.duckdb_v2_copy_function_set_user_data = duckdb_v2_copy_function_set_user_data; result.duckdb_v2_copy_to_batch_get_bind_data = duckdb_v2_copy_to_batch_get_bind_data; @@ -1890,6 +1840,7 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_expression_get_child_count = duckdb_v2_expression_get_child_count; result.duckdb_v2_expression_get_return_type = duckdb_v2_expression_get_return_type; result.duckdb_v2_expression_get_type = duckdb_v2_expression_get_type; + result.duckdb_v2_extension_register_copy_function = duckdb_v2_extension_register_copy_function; result.duckdb_v2_identifier_render_quoted = duckdb_v2_identifier_render_quoted; result.duckdb_v2_prepared_statement_create = duckdb_v2_prepared_statement_create; result.duckdb_v2_prepared_statement_destroy = duckdb_v2_prepared_statement_destroy; @@ -1934,9 +1885,6 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_attach_options_create = duckdb_v2_attach_options_create; result.duckdb_v2_attach_options_destroy = duckdb_v2_attach_options_destroy; result.duckdb_v2_attach_options_set = duckdb_v2_attach_options_set; - result.duckdb_v2_context_get_option_by_index = duckdb_v2_context_get_option_by_index; - result.duckdb_v2_context_get_option_by_name = duckdb_v2_context_get_option_by_name; - result.duckdb_v2_context_get_option_count = duckdb_v2_context_get_option_count; result.duckdb_v2_instance_set_default = duckdb_v2_instance_set_default; result.duckdb_v2_table_function_partition_data_get_bind_data = duckdb_v2_table_function_partition_data_get_bind_data; @@ -1991,7 +1939,86 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_scalar_function_resolve_types_set_return_type = duckdb_v2_scalar_function_resolve_types_set_return_type; result.duckdb_v2_scalar_function_set_resolve_types_callback = duckdb_v2_scalar_function_set_resolve_types_callback; + result.duckdb_v2_connection_get_factory = duckdb_v2_connection_get_factory; + result.duckdb_v2_context_get_factory = duckdb_v2_context_get_factory; + result.duckdb_v2_instance_get_factory = duckdb_v2_instance_get_factory; result.duckdb_v2_scalar_function_bind_set_bind_data = duckdb_v2_scalar_function_bind_set_bind_data; + result.duckdb_v2_config_get_option_value = duckdb_v2_config_get_option_value; + result.duckdb_v2_config_set_option_text = duckdb_v2_config_set_option_text; + result.duckdb_v2_config_set_option_value = duckdb_v2_config_set_option_value; + result.duckdb_v2_connection_get_config = duckdb_v2_connection_get_config; + result.duckdb_v2_connection_get_file_system = duckdb_v2_connection_get_file_system; + result.duckdb_v2_connection_log = duckdb_v2_connection_log; + result.duckdb_v2_context_get_config = duckdb_v2_context_get_config; + result.duckdb_v2_instance_get_config = duckdb_v2_instance_get_config; + result.duckdb_v2_instance_get_file_system = duckdb_v2_instance_get_file_system; + result.duckdb_v2_instance_log = duckdb_v2_instance_log; + result.duckdb_v2_option_get_default_scope = duckdb_v2_option_get_default_scope; + result.duckdb_v2_option_supports_scope = duckdb_v2_option_supports_scope; + result.duckdb_v2_aggregate_function_get_docs = duckdb_v2_aggregate_function_get_docs; + result.duckdb_v2_connection_register_pragma_function = duckdb_v2_connection_register_pragma_function; + result.duckdb_v2_extension_register_pragma_function = duckdb_v2_extension_register_pragma_function; + result.duckdb_v2_function_docs_add_category = duckdb_v2_function_docs_add_category; + result.duckdb_v2_function_docs_add_example = duckdb_v2_function_docs_add_example; + result.duckdb_v2_function_docs_set_description = duckdb_v2_function_docs_set_description; + result.duckdb_v2_pragma_function_create = duckdb_v2_pragma_function_create; + result.duckdb_v2_pragma_function_destroy = duckdb_v2_pragma_function_destroy; + result.duckdb_v2_pragma_function_get_docs = duckdb_v2_pragma_function_get_docs; + result.duckdb_v2_pragma_function_get_signature = duckdb_v2_pragma_function_get_signature; + result.duckdb_v2_pragma_function_query_set_query = duckdb_v2_pragma_function_query_set_query; + result.duckdb_v2_pragma_function_set_exec_callback = duckdb_v2_pragma_function_set_exec_callback; + result.duckdb_v2_pragma_function_set_name = duckdb_v2_pragma_function_set_name; + result.duckdb_v2_pragma_function_set_query_callback = duckdb_v2_pragma_function_set_query_callback; + result.duckdb_v2_pragma_function_set_user_data = duckdb_v2_pragma_function_set_user_data; + result.duckdb_v2_scalar_function_get_docs = duckdb_v2_scalar_function_get_docs; + result.duckdb_v2_table_function_get_docs = duckdb_v2_table_function_get_docs; + result.duckdb_v2_connection_register_custom_option = duckdb_v2_connection_register_custom_option; + result.duckdb_v2_custom_option_create = duckdb_v2_custom_option_create; + result.duckdb_v2_custom_option_destroy = duckdb_v2_custom_option_destroy; + result.duckdb_v2_custom_option_set_default_scope = duckdb_v2_custom_option_set_default_scope; + result.duckdb_v2_custom_option_set_default_value = duckdb_v2_custom_option_set_default_value; + result.duckdb_v2_custom_option_set_description = duckdb_v2_custom_option_set_description; + result.duckdb_v2_custom_option_set_name = duckdb_v2_custom_option_set_name; + result.duckdb_v2_custom_option_set_supports_scope = duckdb_v2_custom_option_set_supports_scope; + result.duckdb_v2_custom_option_set_type = duckdb_v2_custom_option_set_type; + result.duckdb_v2_custom_option_set_update_callback = duckdb_v2_custom_option_set_update_callback; + result.duckdb_v2_custom_option_set_user_data = duckdb_v2_custom_option_set_user_data; + result.duckdb_v2_custom_option_update_get_scope = duckdb_v2_custom_option_update_get_scope; + result.duckdb_v2_custom_option_update_get_user_data = duckdb_v2_custom_option_update_get_user_data; + result.duckdb_v2_custom_option_update_get_value = duckdb_v2_custom_option_update_get_value; + result.duckdb_v2_custom_option_update_is_reset = duckdb_v2_custom_option_update_is_reset; + result.duckdb_v2_custom_option_update_set_value = duckdb_v2_custom_option_update_set_value; + result.duckdb_v2_extension_register_custom_option = duckdb_v2_extension_register_custom_option; + result.duckdb_v2_extension_register_log_sink = duckdb_v2_extension_register_log_sink; + result.duckdb_v2_extension_register_macro_function = duckdb_v2_extension_register_macro_function; + result.duckdb_v2_file_system_path_exists = duckdb_v2_file_system_path_exists; + result.duckdb_v2_file_system_path_expand = duckdb_v2_file_system_path_expand; + result.duckdb_v2_file_system_path_is_remote = duckdb_v2_file_system_path_is_remote; + result.duckdb_v2_instance_register_log_sink = duckdb_v2_instance_register_log_sink; + result.duckdb_v2_log_sink_create = duckdb_v2_log_sink_create; + result.duckdb_v2_log_sink_destroy = duckdb_v2_log_sink_destroy; + result.duckdb_v2_log_sink_set_flush_callback = duckdb_v2_log_sink_set_flush_callback; + result.duckdb_v2_log_sink_set_name = duckdb_v2_log_sink_set_name; + result.duckdb_v2_log_sink_set_user_data = duckdb_v2_log_sink_set_user_data; + result.duckdb_v2_log_sink_set_write_callback = duckdb_v2_log_sink_set_write_callback; + result.duckdb_v2_log_sink_write_info_get_connection_id = duckdb_v2_log_sink_write_info_get_connection_id; + result.duckdb_v2_log_sink_write_info_get_query_id = duckdb_v2_log_sink_write_info_get_query_id; + result.duckdb_v2_log_sink_write_info_get_thread_id = duckdb_v2_log_sink_write_info_get_thread_id; + result.duckdb_v2_log_sink_write_info_get_transaction_id = duckdb_v2_log_sink_write_info_get_transaction_id; + result.duckdb_v2_macro_function_create = duckdb_v2_macro_function_create; + result.duckdb_v2_macro_function_destroy = duckdb_v2_macro_function_destroy; + result.duckdb_v2_macro_function_get_docs = duckdb_v2_macro_function_get_docs; + result.duckdb_v2_macro_function_get_signature = duckdb_v2_macro_function_get_signature; + result.duckdb_v2_macro_function_set_body = duckdb_v2_macro_function_set_body; + result.duckdb_v2_macro_function_set_name = duckdb_v2_macro_function_set_name; + result.duckdb_v2_vector_cast = duckdb_v2_vector_cast; + result.duckdb_v2_copy_function_set_file_extension = duckdb_v2_copy_function_set_file_extension; + result.duckdb_v2_data_chunk_clear = duckdb_v2_data_chunk_clear; + result.duckdb_v2_file_system_path_glob = duckdb_v2_file_system_path_glob; + result.duckdb_v2_file_system_path_move = duckdb_v2_file_system_path_move; + result.duckdb_v2_file_system_path_remove = duckdb_v2_file_system_path_remove; + result.duckdb_v2_statement_add_collection = duckdb_v2_statement_add_collection; + result.duckdb_v2_connection_register_multi_file_function = duckdb_v2_connection_register_multi_file_function; result.duckdb_v2_copy_to_set_statistics_callback = duckdb_v2_copy_to_set_statistics_callback; result.duckdb_v2_copy_to_statistics_get_bind_data = duckdb_v2_copy_to_statistics_get_bind_data; result.duckdb_v2_copy_to_statistics_get_init_data = duckdb_v2_copy_to_statistics_get_init_data; @@ -1999,10 +2026,9 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { result.duckdb_v2_copy_to_statistics_set_file_size = duckdb_v2_copy_to_statistics_set_file_size; result.duckdb_v2_copy_to_statistics_set_row_count = duckdb_v2_copy_to_statistics_set_row_count; result.duckdb_v2_data_chunk_get_capacity = duckdb_v2_data_chunk_get_capacity; - result.duckdb_v2_multi_file_function_create_with_connection = duckdb_v2_multi_file_function_create_with_connection; - result.duckdb_v2_multi_file_function_create_with_extension = duckdb_v2_multi_file_function_create_with_extension; + result.duckdb_v2_extension_register_multi_file_function = duckdb_v2_extension_register_multi_file_function; + result.duckdb_v2_multi_file_function_create = duckdb_v2_multi_file_function_create; result.duckdb_v2_multi_file_function_destroy = duckdb_v2_multi_file_function_destroy; - result.duckdb_v2_multi_file_function_register = duckdb_v2_multi_file_function_register; result.duckdb_v2_multi_file_function_set_file_extension = duckdb_v2_multi_file_function_set_file_extension; result.duckdb_v2_multi_file_function_set_name = duckdb_v2_multi_file_function_set_name; result.duckdb_v2_multi_file_function_set_reader_type = duckdb_v2_multi_file_function_set_reader_type; @@ -2021,6 +2047,7 @@ inline duckdb_ext_api_v2 CreateAPIv2(void) { duckdb_v2_table_function_get_bind_info_set_column_identifier; result.duckdb_v2_table_function_get_bind_info_set_option = duckdb_v2_table_function_get_bind_info_set_option; result.duckdb_v2_table_function_set_get_bind_info_callback = duckdb_v2_table_function_set_get_bind_info_callback; + result.duckdb_v2_factory_create_common_type = duckdb_v2_factory_create_common_type; return result; } diff --git a/src/duckdb/src/include/duckdb/main/client_context.hpp b/src/duckdb/src/include/duckdb/main/client_context.hpp index 9a086dfa2..6d82b01e5 100644 --- a/src/duckdb/src/include/duckdb/main/client_context.hpp +++ b/src/duckdb/src/include/duckdb/main/client_context.hpp @@ -15,6 +15,7 @@ #include "duckdb/common/optional_idx.hpp" #include "duckdb/common/enums/query_result_state.hpp" #include "duckdb/common/enums/prepared_statement_mode.hpp" +#include "duckdb/common/enums/connection_type.hpp" #include "duckdb/common/error_data.hpp" #include "duckdb/common/pair.hpp" #include "duckdb/common/unordered_set.hpp" @@ -92,6 +93,11 @@ class ClientContext : public enable_shared_from_this { friend class Connection; friend class PhysicalTransaction; +public: + //! The error of a statement submitted while a result that no call ended holds the connection + static constexpr const char *OPEN_RESULT_ERROR = + "connection has an open result; drain or destroy it before starting a new query"; + public: DUCKDB_API explicit ClientContext(shared_ptr db); DUCKDB_API ~ClientContext(); @@ -153,7 +159,8 @@ class ClientContext : public enable_shared_from_this { //! Blocking. Runs the query to completion and returns its handle. The result is retained DUCKDB_API unique_ptr Query(const string &query, QueryParameters query_parameters); - DUCKDB_API unique_ptr Query(unique_ptr statement, QueryParameters query_parameters); + DUCKDB_API unique_ptr Query(unique_ptr statement, + const QueryParameters &query_parameters); //! As above, in the given result format DUCKDB_API unique_ptr Query(const string &query, shared_ptr format); DUCKDB_API unique_ptr Query(unique_ptr statement, shared_ptr format); @@ -220,6 +227,8 @@ class ClientContext : public enable_shared_from_this { //! Bind a statement and return its signature, without building a PreparedStatement, optimizing, or //! executing. Read-only: binding touches no in-flight query state, so a live result survives. Throws on error. DUCKDB_API StatementSignature BindStatement(unique_ptr statement); + //! Throws the error a new statement gets while a result that no call ended holds the connection + DUCKDB_API void VerifyNoOpenResult(); //! Gets current percentage of the query's progress, returns 0 in case the progress bar is disabled. DUCKDB_API QueryProgress GetQueryProgress(); @@ -280,6 +289,10 @@ class ClientContext : public enable_shared_from_this { connection_t GetConnectionId() const; + ConnectionType GetConnectionType() const { + return connection_type; + } + //! Fetch the set of tables names of the query. //! Returns the fully qualified, escaped table names, if qualified is set to true, //! else returns the not qualified, not escaped table names. @@ -305,9 +318,14 @@ class ClientContext : public enable_shared_from_this { //! Same as RunTransactionStatement, but does not obtain a lock or route CONNECT statements. void RunTransactionStatementInternal(const TransactionInfo &info); + //! Whether a submission settles its result as retained before the first task is scheduled. A caller that + //! completes the query itself settles it, so no producer parks for a decision it has already made + enum class Retention : uint8_t { UNDECIDED, RETAINED_AT_SUBMISSION }; + //! Submits a query to the database and returns its handle unique_ptr SubmitInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, bool verify = true); + const QueryParameters ¶meters, bool verify = true, + Retention retention = Retention::UNDECIDED); //! Drives a submitted query to completion and retains its result unique_ptr CompleteInternal(ClientContextLock &lock, unique_ptr result); //! Drives a query whose collector builds its own result object, and hands that object out. Null @@ -319,17 +337,24 @@ class ClientContext : public enable_shared_from_this { void StatementVerification(ClientContextLock &lock, unique_ptr &statement, QueryParameters query_parameters); - void InitialCleanup(ClientContextLock &lock); + //! Whether a result that no call ended holds the connection, so that a new statement is refused + bool OpenResultHoldsConnection(ClientContextLock &lock); + //! The refusal of a statement submitted while a result holds the connection; otherwise null, after abandoning + //! what is left of the previous query + template + [[nodiscard]] unique_ptr InitialCleanup(ClientContextLock &lock); + //! Abandons the query of an open result that no call ended, as Close would, and resets the interrupted flag + void AbandonActiveQuery(ClientContextLock &lock); //! Internal clean up, does not lock. Caller must hold the context_lock. void CleanupInternal(ClientContextLock &lock, BaseQueryResult *result = nullptr, bool invalidate_transaction = false); //! Ends the active query as abandoned: nothing it did is committed void AbortInternal(ClientContextLock &lock); unique_ptr SubmitStatement(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters); + const QueryParameters ¶meters, Retention retention); unique_ptr SubmitPreparedStatementInternal(ClientContextLock &lock, shared_ptr statement_data_p, - const QueryParameters ¶meters); + const QueryParameters ¶meters, Retention retention); void CheckIfPreparedStatementIsExecutable(PreparedStatementData &statement); //! Internally prepare a SQL statement. Caller must hold the context_lock. @@ -337,7 +362,7 @@ class ClientContext : public enable_shared_from_this { unique_ptr statement, const QueryParameters ¶meters); unique_ptr SubmitStatementInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters); + const QueryParameters ¶meters, Retention retention); unique_ptr RunStatementInternal(ClientContextLock &lock, unique_ptr statement, const QueryParameters ¶meters, bool verify = true); unique_ptr PrepareInternal(ClientContextLock &lock, unique_ptr statement); @@ -380,6 +405,8 @@ class ClientContext : public enable_shared_from_this { QueryProgress query_progress; //! The connection corresponding to this client context connection_t connection_id; + //! Type of connection (USER or INTERNAL) + ConnectionType connection_type = ConnectionType::USER; //! Routing target for SQL execution while CONNECT-ed (CONNECT/DISCONNECT). When is_connected is //! true and connected_to_database can be locked, the chokepoint dispatches non-control SQL via //! `Catalog::RemoteExecute(string)` and wraps the returned TableRef into a SelectStatement. diff --git a/src/duckdb/src/include/duckdb/main/client_context_state.hpp b/src/duckdb/src/include/duckdb/main/client_context_state.hpp index 92dff1106..240dd0a1d 100644 --- a/src/duckdb/src/include/duckdb/main/client_context_state.hpp +++ b/src/duckdb/src/include/duckdb/main/client_context_state.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/atomic.hpp" #include "duckdb/common/enums/prepared_statement_mode.hpp" #include "duckdb/common/exception/transaction_exception.hpp" #include "duckdb/common/optional_ptr.hpp" @@ -100,6 +101,7 @@ class RegisteredStateManager { } auto cache = make_shared_ptr(std::forward(args)...); registered_state[key] = cache; + InvalidateSnapshot(); return cache; } @@ -115,26 +117,74 @@ class RegisteredStateManager { void Insert(const string &key, shared_ptr state_p) { lock_guard l(lock); - registered_state.insert(make_pair(key, std::move(state_p))); + if (registered_state.insert(make_pair(key, std::move(state_p))).second) { + InvalidateSnapshot(); + } } void Remove(const string &key) { lock_guard l(lock); - registered_state.erase(key); + if (registered_state.erase(key)) { + InvalidateSnapshot(); + } } vector> States() { lock_guard l(lock); + return StatesInternal(); + } + + //! Invokes callbacks outside the registry lock, retaining snapshot ownership throughout dispatch. + template + void ForEachState(STATE_CALLBACK &&callback) { + auto states = StatesSnapshot(); + if (!states) { + return; + } + for (auto &state : *states) { + callback(*state); + } + } + +private: + shared_ptr>> StatesSnapshot() { + if (!has_states.load(std::memory_order_acquire)) { + return nullptr; + } + auto snapshot = state_snapshot.atomic_load(); + if (snapshot) { + return snapshot; + } + lock_guard l(lock); + snapshot = state_snapshot; + if (!snapshot && !registered_state.empty()) { + snapshot = make_shared_ptr>>(StatesInternal()); + state_snapshot.atomic_store(snapshot); + } + return snapshot; + } + + vector> StatesInternal() { vector> states; + states.reserve(registered_state.size()); for (auto &entry : registered_state) { states.push_back(entry.second); } return states; } + //! Called with the registry lock held after a successful mutation. + void InvalidateSnapshot() { + state_snapshot.atomic_store(nullptr); + has_states.store(!registered_state.empty(), std::memory_order_release); + } + private: mutex lock; unordered_map> registered_state; + shared_ptr>> state_snapshot; + //! Avoid atomic shared_ptr loads for empty registries. + atomic has_states {false}; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/config.hpp b/src/duckdb/src/include/duckdb/main/config.hpp index 32c3fc6e3..ff6e23f80 100644 --- a/src/duckdb/src/include/duckdb/main/config.hpp +++ b/src/duckdb/src/include/duckdb/main/config.hpp @@ -230,6 +230,7 @@ struct DBConfig { const Value &default_value = Value(), set_option_callback_t function = nullptr, SetScope default_scope = SetScope::SESSION, bool is_debug = false, bool is_deprecated = false); + DUCKDB_API void AddExtensionOption(const Identifier &name, ExtensionOption extension_option); DUCKDB_API bool HasExtensionOption(const Identifier &name) const; DUCKDB_API identifier_map_t GetExtensionSettings() const; DUCKDB_API bool TryGetExtensionOption(const Identifier &name, ExtensionOption &result) const; @@ -254,7 +255,10 @@ struct DBConfig { DUCKDB_API void CheckLock(const Identifier &name); - DUCKDB_API static idx_t ParseMemoryLimit(const string &arg); + //! Parse a memory size - an unlimited size (e.g. "-1" or "none") is returned as an invalid optional_idx + DUCKDB_API static optional_idx ParseMemoryLimit(const string &arg); + //! Parse a memory size, returning NumericLimits::Maximum() for an unlimited size + DUCKDB_API static idx_t ParseMemoryLimitOrMaximum(const string &arg); //! Returns the list of possible compression functions for the physical type. DUCKDB_API vector> diff --git a/src/duckdb/src/include/duckdb/main/connection.hpp b/src/duckdb/src/include/duckdb/main/connection.hpp index a8a2f2a88..53ce22789 100644 --- a/src/duckdb/src/include/duckdb/main/connection.hpp +++ b/src/duckdb/src/include/duckdb/main/connection.hpp @@ -41,6 +41,7 @@ class Connection { public: DUCKDB_API explicit Connection(DuckDB &database); DUCKDB_API explicit Connection(DatabaseInstance &database); + DUCKDB_API static Connection CreateInternal(DatabaseInstance &database); // disable copy constructors Connection(const Connection &other) = delete; Connection &operator=(const Connection &) = delete; @@ -51,6 +52,9 @@ class Connection { shared_ptr context; +private: + Connection(DatabaseInstance &database, ConnectionType connection_type); + public: //! Returns query profiling information for the current query, formatted according to the given ProfilerPrintFormat //! (e.g. ProfilerPrintFormat::JSON()). ProfilerPrintFormat::Default() uses the configured default profiler format. diff --git a/src/duckdb/src/include/duckdb/main/extension/extension_loader.hpp b/src/duckdb/src/include/duckdb/main/extension/extension_loader.hpp index 6eecc3d94..0151b8101 100644 --- a/src/duckdb/src/include/duckdb/main/extension/extension_loader.hpp +++ b/src/duckdb/src/include/duckdb/main/extension/extension_loader.hpp @@ -27,6 +27,7 @@ struct CreateCollationInfo; struct CreateAggregateFunctionInfo; struct CreateScalarFunctionInfo; struct CreateTableFunctionInfo; +struct CreatePragmaFunctionInfo; struct CreateWindowFunctionInfo; struct ExtensionLoaderInfo { @@ -92,6 +93,7 @@ class ExtensionLoader { //! Register a new pragma function set - throw an exception if the function already exists DUCKDB_API void RegisterFunction(PragmaFunctionSet function); + DUCKDB_API void RegisterFunction(CreatePragmaFunctionInfo info); //! Register a CreateSecretFunction DUCKDB_API void RegisterFunction(CreateSecretFunction function); diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index c16da7c56..ceb405b13 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -382,6 +382,7 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"glue_get_table_response", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, {"glue_partitions", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, {"glue_rename_partition", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, + {"glue_replace_columns", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, {"glue_set_partition_location", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, {"glue_set_table_location", "glue", CatalogType::TABLE_FUNCTION_ENTRY}, {"grade_up", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -712,18 +713,15 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"parse_filename", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"parse_formatted_bytes", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"parse_path", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, - {"pg_clear_cache", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"pg_timezone_names", "icu", CatalogType::TABLE_FUNCTION_ENTRY}, {"pi", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"position", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, - {"postgres_attach", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, + {"postgres_clear_cache", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_configure_pool", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_execute", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_hstore_get", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_hstore_to_json", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_query", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, - {"postgres_scan", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, - {"postgres_scan_pushdown", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"pow", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"power", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"pragma_hnsw_index_info", "vss", CatalogType::TABLE_FUNCTION_ENTRY}, @@ -944,7 +942,9 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"st_shortestline", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_simplify", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_simplifypreservetopology", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"st_snap", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_startpoint", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"st_subdivide", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_symdifference", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_tileenvelope", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, {"st_touches", "spatial", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -1357,6 +1357,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"mysql_bit1_as_boolean", "mysql_scanner"}, {"mysql_debug_show_queries", "mysql_scanner"}, {"mysql_enable_filter_pushdown", "mysql_scanner"}, + {"mysql_enable_remote_pushdown_ddl", "mysql_scanner"}, {"mysql_enable_transactions", "mysql_scanner"}, {"mysql_incomplete_dates_as_nulls", "mysql_scanner"}, {"mysql_pool_acquire_mode", "mysql_scanner"}, @@ -1420,6 +1421,8 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"quack_server_keep_alive_timeout", "quack"}, {"quack_server_max_connections", "quack"}, {"quack_target_batch_bytes", "quack"}, + {"rtree_index_scan_min_rows", "spatial"}, + {"rtree_index_scan_ratio", "spatial"}, {"s3_access_key_id", "httpfs"}, {"s3_allow_recursive_globbing", "httpfs"}, {"s3_endpoint", "httpfs"}, diff --git a/src/duckdb/src/include/duckdb/main/extension_install_info.hpp b/src/duckdb/src/include/duckdb/main/extension_install_info.hpp index 57bc8130e..8047e9293 100644 --- a/src/duckdb/src/include/duckdb/main/extension_install_info.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_install_info.hpp @@ -117,6 +117,8 @@ struct ExtensionRepository { static string TryConvertUrlToKnownRepository(const string &url); //! Try to get the backup server url of a repository url, will return empty string if the repository has no backup static string TryGetBackupRepositoryUrl(const string &url); + //! Whether a url points into one of the official DuckDB repositories (core, nightly, community or their backups) + static bool IsOfficialRepositoryUrl(const string &url); //! Get the default repository, optionally passing a config to allow static ExtensionRepository GetDefaultRepository(optional_ptr config); diff --git a/src/duckdb/src/include/duckdb/main/http/http_util.hpp b/src/duckdb/src/include/duckdb/main/http/http_util.hpp index 743c589ad..309dcfe77 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_util.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_util.hpp @@ -46,6 +46,9 @@ struct HTTPParams { bool follow_location = true; bool override_verify_ssl = false; bool verify_ssl = true; + //! When set, the caller has already checked the url against the file access settings, so the central check in + //! HTTPUtil::Request is skipped (used by extension installation, which also allows the official repositories) + bool skip_external_access_check = false; string http_proxy; idx_t http_proxy_port = 0; diff --git a/src/duckdb/src/include/duckdb/main/query_parameters.hpp b/src/duckdb/src/include/duckdb/main/query_parameters.hpp index 295557087..8df4726fc 100644 --- a/src/duckdb/src/include/duckdb/main/query_parameters.hpp +++ b/src/duckdb/src/include/duckdb/main/query_parameters.hpp @@ -8,7 +8,6 @@ #pragma once -#include "duckdb/common/enums/result_eagerness.hpp" #include "duckdb/common/identifier.hpp" #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/shared_ptr.hpp" @@ -26,9 +25,6 @@ struct QueryParameters { //! Arguments for a parameterized statement (may be null) optional_ptr> statement_args; - //! FORCED by Query and Execute. AUTO defers to the statement: a statement whose eagerness is - //! FORCED is still settled at submission - ResultEagerness result_eagerness = ResultEagerness::AUTO; shared_ptr format; }; diff --git a/src/duckdb/src/include/duckdb/main/query_profiler.hpp b/src/duckdb/src/include/duckdb/main/query_profiler.hpp index 483736c12..1ed2c856e 100644 --- a/src/duckdb/src/include/duckdb/main/query_profiler.hpp +++ b/src/duckdb/src/include/duckdb/main/query_profiler.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/atomic.hpp" #include "duckdb/common/common.hpp" #include "duckdb/common/enums/profiling_coverage.hpp" #include "duckdb/common/deque.hpp" @@ -192,8 +193,8 @@ class QueryProfiler { private: ClientContext &context; - //! Whether or not the query profiler is running - bool running; + //! Atomic so inactive flushes can skip acquiring the profiler lock. + atomic running; //! The lock used for accessing the global query profiler or flushing information to it from a thread mutable std::mutex lock; diff --git a/src/duckdb/src/include/duckdb/main/query_result_stream.hpp b/src/duckdb/src/include/duckdb/main/query_result_stream.hpp index e38ed9f8a..95953d773 100644 --- a/src/duckdb/src/include/duckdb/main/query_result_stream.hpp +++ b/src/duckdb/src/include/duckdb/main/query_result_stream.hpp @@ -17,12 +17,19 @@ namespace duckdb { +//! Whether a reader that picks the consumption mode on its consumer's behalf streams a statement: only a SELECT that +//! returns rows does. written_type is the statement as written, since a prepared execution reports the EXECUTE that +//! runs it, and the properties are the running statement's, since an unresolved parameter type leaves the prepared +//! statement's own copy incomplete. A reader that may stop reading also needs a statement that writes nothing, since +//! a statement abandoned before its end is rolled back +DUCKDB_API bool StreamsByDefault(StatementType written_type, const StatementProperties &properties, + bool reads_to_the_end = true); + //! Opening a stream settles retention on draining and consumes the handle: no random access class ResultStreamBase { public: //! Opens a stream on a submitted query. Throws InvalidInputException when the handle carries an - //! error, when its retention is already retained, when the planner marked the statement as - //! completing before its result is returned, or when the result's format is not the expected one. + //! error, when its retention is already retained, or when the result's format is not the expected one. //! A throw consumes the handle: it is destroyed with this object, which ends the query DUCKDB_API ResultStreamBase(unique_ptr result, const char *expected_format); DUCKDB_API virtual ~ResultStreamBase(); diff --git a/src/duckdb/src/include/duckdb/main/setting_info.hpp b/src/duckdb/src/include/duckdb/main/setting_info.hpp index e93ebbdee..ad0d5029d 100644 --- a/src/duckdb/src/include/duckdb/main/setting_info.hpp +++ b/src/duckdb/src/include/duckdb/main/setting_info.hpp @@ -107,6 +107,26 @@ struct ConfigurationAlias { typedef void (*set_option_callback_t)(ClientContext &context, SetScope scope, Value ¶meter); +//! Extra state and behavior of an extension option +struct ExtensionOptionInfo { + DUCKDB_API virtual ~ExtensionOptionInfo(); + + //! Called when the option is set or reset, with the scope resolved to SESSION or GLOBAL, before the value is + //! stored. May replace the value, or throw to refuse it + virtual void OnSet(ClientContext &context, SetScope scope, Value ¶meter, bool is_reset) const = 0; + + template + TARGET &Cast() { + DynamicCastCheck(this); + return reinterpret_cast(*this); + } + template + const TARGET &Cast() const { + DynamicCastCheck(this); + return reinterpret_cast(*this); + } +}; + struct ExtensionOption { ExtensionOption() : set_function(nullptr), default_scope(SetScope::AUTOMATIC) { } @@ -126,6 +146,15 @@ struct ExtensionOption { optional_idx setting_index; bool is_debug = false; bool is_deprecated = false; + //! Whether the option can be set globally, and per session + bool supports_global = true; + bool supports_session = true; + //! Optional extra state and behavior, invoked after set_function + shared_ptr info; + +public: + //! Resolves AUTOMATIC to the default scope, and throws when the option cannot be set at the resulting scope + DUCKDB_API SetScope ResolveScope(const Identifier &name, SetScope scope) const; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/aggregate_function_rewriter.hpp b/src/duckdb/src/include/duckdb/optimizer/aggregate_function_rewriter.hpp index 6cb34ff77..0acb74669 100644 --- a/src/duckdb/src/include/duckdb/optimizer/aggregate_function_rewriter.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/aggregate_function_rewriter.hpp @@ -13,7 +13,6 @@ namespace duckdb { class Optimizer; -class AggregateRewriteRule; //! Rewrites aggregate functions. Currently registered rules: //! AVG(x) -> SUM(x) / COUNT(x) @@ -27,7 +26,6 @@ class AggregateFunctionRewriter { private: Optimizer &optimizer; - vector> rules; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/column_binding_replacer.hpp b/src/duckdb/src/include/duckdb/optimizer/column_binding_replacer.hpp index 73a87b95f..47277c4c4 100644 --- a/src/duckdb/src/include/duckdb/optimizer/column_binding_replacer.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/column_binding_replacer.hpp @@ -63,7 +63,7 @@ class ColumnBindingReplacer : public LogicalOperatorVisitor { ColumnBindingReplacer(); public: - //! Update each operator of the plan + //! Update each operator of the plan, recursing directly into children without resolving projection maps void VisitOperator(LogicalOperator &op) override; //! Update bindings owned by this operator without visiting its children virtual void VisitOperatorBindings(LogicalOperator &op); diff --git a/src/duckdb/src/include/duckdb/optimizer/compressed_materialization.hpp b/src/duckdb/src/include/duckdb/optimizer/compressed_materialization.hpp index 44242c165..b9d04cb0d 100644 --- a/src/duckdb/src/include/duckdb/optimizer/compressed_materialization.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/compressed_materialization.hpp @@ -16,6 +16,7 @@ namespace duckdb { class Optimizer; class ClientContext; +class ColumnBindingReplacer; class LogicalOperator; class Expression; class BoundColumnRefExpression; @@ -104,7 +105,9 @@ class CompressedMaterialization { static constexpr double JOIN_CARDINALITY_RATIO_THRESHOLD = 8; public: - CompressedMaterialization(Optimizer &optimizer, LogicalOperator &root, statistics_map_t &statistics_map); + CompressedMaterialization(Optimizer &optimizer, LogicalOperator &root, + const vector>> &ancestors, + statistics_map_t &statistics_map); void Compress(unique_ptr &op); @@ -137,6 +140,8 @@ class CompressedMaterialization { vector> compress_exprs, CompressedMaterializationInfo &info, CMChildInfo &child_info); void CreateDecompressProjection(unique_ptr &op, CompressedMaterializationInfo &info); + //! Rebind references to compressed bindings, which can only occur in the ancestors of the compressed operator + void RebindAncestors(ColumnBindingReplacer &replacer); unique_ptr CreateRestoreExpression(unique_ptr input, const CMBindingInfo &binding_info, const BaseStatistics &stats); @@ -172,6 +177,8 @@ class CompressedMaterialization { ClientContext &context; //! The root of the query plan optional_ptr root; + //! The chain of operators from the root down to the operator being compressed + const vector>> &ancestors; //! The map of ColumnBinding -> statistics for the various nodes statistics_map_t &statistics_map; }; diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/join_predicate.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/join_predicate.hpp index 7052f7cc2..fea23a458 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/join_predicate.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/join_predicate.hpp @@ -59,6 +59,8 @@ class JoinPredicate { bool IsEquivalencePredicate() const; void SetEqualityClassIndex(optional_idx equality_class_index); optional_idx GetEqualityClassIndex() const; + void SetCompositeJoinPair(JoinRelationSet &pair); + optional_ptr GetCompositeJoinPair() const; private: idx_t index; @@ -68,6 +70,7 @@ class JoinPredicate { ColumnBinding left_equality_binding; ColumnBinding right_equality_binding; optional_idx equality_class_index; + optional_ptr composite_join_pair; }; struct JoinEqualityPredicateEdge { @@ -90,7 +93,22 @@ struct JoinEqualityClass { vector edges; }; +class JoinEqualityGroups { +public: + void AddEquality(const ColumnBinding &left, const ColumnBinding &right); + idx_t GetGroup(const ColumnBinding &binding); + +private: + idx_t GetOrCreateGroup(const ColumnBinding &binding); + idx_t FindRoot(idx_t group); + +private: + column_binding_map_t binding_to_group; + vector parents; +}; + struct RelationPairEqualitySummary { + void AddEquality(idx_t equality_class_index, ColumnBinding first_binding, ColumnBinding second_binding); bool HasDirectCompositeEquality() const; vector direct_equality_class_indices; @@ -116,9 +134,6 @@ class JoinPredicateModel { bool HasDirectCompositeEquality(JoinRelationSet &pair) const; -private: - static bool ContainsClassIndex(const vector &class_indices, idx_t equality_class_index); - private: vector> predicates; vector> all_predicates; diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/join_relation_set.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/join_relation_set.hpp index c138e02f3..60462dfbf 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/join_relation_set.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/join_relation_set.hpp @@ -29,6 +29,8 @@ struct JoinRelationSet { public: unsafe_unique_array relations; idx_t count; + //! Bitmask over the relation indexes for fast subset/intersection checks + vector bitmask; }; //! The JoinRelationTree is a structure holding all the created JoinRelationSet objects and allowing fast lookup on to diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/query_graph_manager.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/query_graph_manager.hpp index 3ae992bd6..55129f156 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/query_graph_manager.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/query_graph_manager.hpp @@ -69,6 +69,7 @@ class QueryGraphManager { //! Build the normalized predicate model after filter endpoints and stats bindings are populated. void BuildPredicateModel(); + void BuildLeftEqualityPairs(); void BuildInnerCompanionSets(); void ClearExtractedExpressions(); idx_t FindGraphComponent(RelationIndex relation); diff --git a/src/duckdb/src/include/duckdb/optimizer/statistics_propagator.hpp b/src/duckdb/src/include/duckdb/optimizer/statistics_propagator.hpp index d18660fa0..04142adc8 100644 --- a/src/duckdb/src/include/duckdb/optimizer/statistics_propagator.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/statistics_propagator.hpp @@ -99,6 +99,8 @@ class StatisticsPropagator { FilterPropagateResult ClassifyFilter(Expression &condition); //! Simplify conjunctions using filter truth semantics bool SimplifyFilter(unique_ptr &condition); + //! Remove constant_or_null(TRUE, ...) conjuncts whose NULL checks are implied by retained predicates + void SimplifyConstantOrNull(vector> &expressions); //! Propagate a filter condition FilterPropagateResult HandleFilter(unique_ptr &condition); //! Rewrite a join whose condition can never match; returns true if the operator was replaced @@ -168,6 +170,8 @@ class StatisticsPropagator { unique_ptr node_stats; //! Whether statistics changed which relations a filter depends on bool filter_bindings_changed = false; + //! The chain of operators from the root down to the operator currently being propagated + vector>> propagation_path; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/unnest_rewriter.hpp b/src/duckdb/src/include/duckdb/optimizer/unnest_rewriter.hpp index 6b05ae024..e50b15993 100644 --- a/src/duckdb/src/include/duckdb/optimizer/unnest_rewriter.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/unnest_rewriter.hpp @@ -54,12 +54,13 @@ class UnnestRewriterPlanUpdater : LogicalOperatorVisitor { //! the SELECT class UnnestRewriter { public: - UnnestRewriter() { + explicit UnnestRewriter(ClientContext &context) : context(context) { } //! Rewrite duplicate eliminated joins with UNNESTs unique_ptr Optimize(unique_ptr op); private: + ClientContext &context; //! Find delim joins that contain an UNNEST void FindCandidates(unique_ptr &root, unique_ptr &op, vector>> &candidates); diff --git a/src/duckdb/src/include/duckdb/parallel/pipeline_broadcast_exchange.hpp b/src/duckdb/src/include/duckdb/parallel/pipeline_broadcast_exchange.hpp index acaf571fb..a31d8c3d6 100644 --- a/src/duckdb/src/include/duckdb/parallel/pipeline_broadcast_exchange.hpp +++ b/src/duckdb/src/include/duckdb/parallel/pipeline_broadcast_exchange.hpp @@ -292,7 +292,7 @@ class PipelineBroadcastExchange { idx_t active_consumers = 0; bool has_batch_scan_consumer DUCKDB_GUARDED_BY(lock) = false; atomic produced_rows {0}; - ProducerState producer_state = ProducerState::ACTIVE; + ProducerState producer_state DUCKDB_GUARDED_BY(lock) = ProducerState::ACTIVE; WatermarkState watermark_state = WatermarkState::BELOW_HIGH_WATERMARK; optional_ptr log_operator; }; diff --git a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp index aafb6afc8..afc6450a5 100644 --- a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp +++ b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp @@ -174,6 +174,7 @@ class ScanReadAhead { ~ProducerReservation() { if (!committed) { read_ahead.ReleaseSlot(); + read_ahead.pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; } --read_ahead.active_producers; } @@ -204,6 +205,10 @@ class ScanReadAhead { //! Release a read-ahead slot void ReleaseSlot(); +private: + //! Minimum budget charge per job, beyond its scheduled I/O a job carries scan-state overhead (decode buffers) + static constexpr idx_t MINIMUM_JOB_IO_CHARGE = 16ULL * 1024 * 1024; + private: ClientContext &context; //! Maximum number of jobs scheduled ahead of decoding, unlimited in the -1 auto mode @@ -223,7 +228,7 @@ class ScanReadAhead { idx_t next_batch_index = 0; //! Jobs scheduled ahead of decoding atomic active_jobs {0}; - //! Bytes of scheduled I/O that has not completed yet, released once the claimed job's I/O finished + //! Budget charge of the reserved producer slots and scheduled jobs, released per job in WaitForJob atomic pending_io_bytes {0}; atomic done {false}; //! Threads that reserved a slot but have not pushed their job yet diff --git a/src/duckdb/src/include/duckdb/parallel/task_executor.hpp b/src/duckdb/src/include/duckdb/parallel/task_executor.hpp index 90e4c46e8..22103abf4 100644 --- a/src/duckdb/src/include/duckdb/parallel/task_executor.hpp +++ b/src/duckdb/src/include/duckdb/parallel/task_executor.hpp @@ -45,7 +45,7 @@ class BaseExecutorTask { } //! Called instead of ExecuteTask when the task is retired without running its work, because another task errored, //! because the executor was cancelled, or because the task could not be queued at all. Exactly one of ExecuteTask - //! or Cancel runs for every task passed to TaskExecutor::ScheduleTask. + //! or Cancel runs for every task passed to TaskExecutor::ScheduleTask or ScheduleTasks. //! Anything a waiter watches must change on every exit path, or that waiter never wakes up. Either settle it here //! as well as in ExecuteTask, or settle it in the destructor, which also covers a task that was constructed but //! never scheduled. A destructor settle runs after the executor has already counted the task as finished, so it @@ -80,6 +80,8 @@ class TaskExecutor { //! Schedule a new task void ScheduleTask(unique_ptr task); + //! Schedule a batch, cancelling unscheduled tasks if submission fails. + void ScheduleTasks(vector> tasks); //! Work on tasks until all tasks are finished. Throws an exception if any error occurred while executing the tasks. void WorkOnTasks(); diff --git a/src/duckdb/src/include/duckdb/parallel/task_scheduler.hpp b/src/duckdb/src/include/duckdb/parallel/task_scheduler.hpp index 17dab3513..cd691967b 100644 --- a/src/duckdb/src/include/duckdb/parallel/task_scheduler.hpp +++ b/src/duckdb/src/include/duckdb/parallel/task_scheduler.hpp @@ -88,9 +88,9 @@ class TaskScheduler { TaskSchedulerQueue &GetQueue(TaskSchedulerType pool_type) const; //! Fetches a task, returns true if successful or false if no tasks were available - bool GetTaskInternal(shared_ptr &task); - bool GetTaskInternal(shared_ptr &task, TaskSchedulerType pool_type); - bool TryDequeueAndProcessTask(const DBConfig &config, TaskSchedulerQueue &queue, shared_ptr &task); + bool GetTaskInternal(ConsumerToken &consumer, shared_ptr &task); + bool TryDequeueAndProcessTask(const DBConfig &config, TaskSchedulerQueue &queue, ConsumerToken &consumer, + shared_ptr &task); void SetThreadsInternal(TaskSchedulerType pool_type, idx_t n); void Signal(TaskSchedulerType pool_type, idx_t n); diff --git a/src/duckdb/src/include/duckdb/parallel/task_scheduler_queue.hpp b/src/duckdb/src/include/duckdb/parallel/task_scheduler_queue.hpp index 36ca7b3d3..87d66cd32 100644 --- a/src/duckdb/src/include/duckdb/parallel/task_scheduler_queue.hpp +++ b/src/duckdb/src/include/duckdb/parallel/task_scheduler_queue.hpp @@ -37,7 +37,7 @@ class TaskSchedulerQueue { void Enqueue(ProducerToken &token, shared_ptr task); void EnqueueBulk(ProducerToken &token, vector> &tasks); bool DequeueFromProducer(ProducerToken &token, shared_ptr &task); - bool Dequeue(shared_ptr &task); + bool Dequeue(ConsumerToken &token, shared_ptr &task); idx_t GetTasksInQueue() const; idx_t GetApproxSize() const; idx_t GetProducerCount() const; diff --git a/src/duckdb/src/include/duckdb/parallel/task_scheduler_token.hpp b/src/duckdb/src/include/duckdb/parallel/task_scheduler_token.hpp index ff249bddd..34296c10f 100644 --- a/src/duckdb/src/include/duckdb/parallel/task_scheduler_token.hpp +++ b/src/duckdb/src/include/duckdb/parallel/task_scheduler_token.hpp @@ -19,6 +19,7 @@ namespace duckdb { // Forward declarations. struct QueueProducerToken; +struct QueueConsumerToken; class TaskSchedulerQueue; struct ProducerToken { @@ -37,4 +38,17 @@ struct ProducerToken { array, TASK_SCHEDULER_TYPE_COUNT> tokens; }; +//! Dequeues tasks from any producer, rotating across producers so that every query gets a turn +struct ConsumerToken { +public: + explicit ConsumerToken(array, TASK_SCHEDULER_TYPE_COUNT> &queues); + ~ConsumerToken(); + +public: + QueueConsumerToken &GetQueueConsumerToken(TaskSchedulerType pool_type); + +private: + array, TASK_SCHEDULER_TYPE_COUNT> tokens; +}; + } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/expression/window_expression.hpp b/src/duckdb/src/include/duckdb/parser/expression/window_expression.hpp index 878c6e0bb..cedda0e33 100644 --- a/src/duckdb/src/include/duckdb/parser/expression/window_expression.hpp +++ b/src/duckdb/src/include/duckdb/parser/expression/window_expression.hpp @@ -73,9 +73,10 @@ class WindowExpression : public ParsedExpression { } void SetQualifiedName(QualifiedName name) { qualified_name = std::move(name); + type = WindowToExpressionType(qualified_name.Name().GetIdentifierName()); } void SetQualifiedName(Identifier catalog, Identifier schema, Identifier name) { - qualified_name = QualifiedName(std::move(catalog), std::move(schema), std::move(name)); + SetQualifiedName(QualifiedName(std::move(catalog), std::move(schema), std::move(name))); } const Identifier &FunctionName() const { return qualified_name.Name(); diff --git a/src/duckdb/src/include/duckdb/parser/grammar_change.hpp b/src/duckdb/src/include/duckdb/parser/grammar_change.hpp index eadc18c2f..298c2820e 100644 --- a/src/duckdb/src/include/duckdb/parser/grammar_change.hpp +++ b/src/duckdb/src/include/duckdb/parser/grammar_change.hpp @@ -27,8 +27,10 @@ enum class GrammarChangeType : uint8_t { //! A structured change to apply while constructing a PEG grammar. class GrammarChange { public: + //! See ParsedGrammar::AddRule for the meaning of suppress_suggestions DUCKDB_API static GrammarChange AddRule(const string &rule_definition, - grammar_transform_process_function_t transform_process = nullptr); + grammar_transform_process_function_t transform_process = nullptr, + bool suppress_suggestions = false); DUCKDB_API static GrammarChange AddChoice(const string &rule_name, const string &choice, grammar_cursor_function_t find_cursor = nullptr); DUCKDB_API static GrammarChange PrependChoice(const string &rule_name, const string &choice, @@ -41,7 +43,7 @@ class GrammarChange { //! See ParsedGrammar::SetTransformProcess for the meaning of collapsible DUCKDB_API static GrammarChange SetTransformProcess(const string &rule_name, grammar_transform_process_function_t transform_process, - bool collapsible = false); + bool collapsible = false, bool suppress_suggestions = false); DUCKDB_API static GrammarChange AddTerminalRuleOverride(const string &rule_name, terminal_rule_matcher_factory_t matcher_factory); @@ -62,14 +64,16 @@ class GrammarChange { static GrammarChange Create(GrammarChangeType type, string rule_name, string definition, grammar_transform_process_function_t transform_process = nullptr, grammar_cursor_function_t find_cursor = nullptr, - terminal_rule_matcher_factory_t matcher_factory = nullptr, bool collapsible = false); + terminal_rule_matcher_factory_t matcher_factory = nullptr, bool collapsible = false, + bool suppress_suggestions = false); GrammarChange(GrammarChangeType type_p, string rule_name_p, string definition_p, grammar_transform_process_function_t transform_process_p, grammar_cursor_function_t find_cursor_p, - terminal_rule_matcher_factory_t matcher_factory_p, bool collapsible_p) + terminal_rule_matcher_factory_t matcher_factory_p, bool collapsible_p, bool suppress_suggestions_p) : type(type_p), rule_name(std::move(rule_name_p)), definition(std::move(definition_p)), transform_process(std::move(transform_process_p)), find_cursor(std::move(find_cursor_p)), - matcher_factory(std::move(matcher_factory_p)), collapsible(collapsible_p) { + matcher_factory(std::move(matcher_factory_p)), collapsible(collapsible_p), + suppress_suggestions(suppress_suggestions_p) { } private: @@ -81,6 +85,7 @@ class GrammarChange { grammar_cursor_function_t find_cursor; terminal_rule_matcher_factory_t matcher_factory; bool collapsible; + bool suppress_suggestions; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/parsed_data/alter_table_function_info.hpp b/src/duckdb/src/include/duckdb/parser/parsed_data/alter_table_function_info.hpp index c087c2594..116e579c6 100644 --- a/src/duckdb/src/include/duckdb/parser/parsed_data/alter_table_function_info.hpp +++ b/src/duckdb/src/include/duckdb/parser/parsed_data/alter_table_function_info.hpp @@ -10,6 +10,7 @@ #include "duckdb/function/function_set.hpp" #include "duckdb/parser/parsed_data/alter_info.hpp" +#include "duckdb/parser/parsed_data/create_function_info.hpp" namespace duckdb { @@ -32,10 +33,13 @@ struct AlterTableFunctionInfo : public AlterInfo { // AddTableFunctionOverloadInfo //===--------------------------------------------------------------------===// struct AddTableFunctionOverloadInfo : public AlterTableFunctionInfo { - AddTableFunctionOverloadInfo(const AlterEntryData &data, TableFunctionSet new_overloads); + AddTableFunctionOverloadInfo(const AlterEntryData &data, TableFunctionSet new_overloads, + vector new_descriptions = {}); ~AddTableFunctionOverloadInfo() override; TableFunctionSet new_overloads; + //! The descriptions of the new overloads + vector new_descriptions; public: unique_ptr Copy() const override; diff --git a/src/duckdb/src/include/duckdb/parser/parsed_expression.hpp b/src/duckdb/src/include/duckdb/parser/parsed_expression.hpp index 3398ab01b..d5af41829 100644 --- a/src/duckdb/src/include/duckdb/parser/parsed_expression.hpp +++ b/src/duckdb/src/include/duckdb/parser/parsed_expression.hpp @@ -195,6 +195,9 @@ class ParsedExpression : public BaseExpression { virtual void Serialize(Serializer &serializer) const; static unique_ptr Deserialize(Deserializer &deserializer); + //! Throws a SerializationException if a deserialized expression does not have a valid shape + //! Only this expression is verified - its children are verified when they are deserialized + void VerifyDeserialized(); //! Copies the base class properties (type, expression_class, alias, query_location) from other into this void CopyBase(const ParsedExpression &other); diff --git a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp index 50403b5a4..5032d4713 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp @@ -42,11 +42,8 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 9, 65, 110, 97, 108, 121, 122, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 47, 10, 9, 77, 101, 114, 103, 101, 73, 110, 116, 111, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 47, 10, 9, 67, 111, 110, 110, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 47, 10, - 9, 68, 105, 115, 99, 111, 110, 110, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 47, 10, - 9, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 83, 116, 97, 116, 101, 109, 101, 110, 116, 10, + 9, 68, 105, 115, 99, 111, 110, 110, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 10, 65, 110, 97, 108, 121, 122, 101, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 65, 78, 65, 76, 89, 90, 69, 39, 32, 47, 32, 39, 65, 78, 65, 76, 89, 83, 69, 39, 10, - 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 108, 105, 97, 115, 41, 10, - 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 108, 105, 97, 115, 32, 60, 45, 32, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, @@ -941,8 +938,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 114, 101, 97, 116, 101, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 69, 108, 101, 109, 101, 110, 116, 32, 60, 45, 32, 67, 114, 101, 97, 116, 101, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 32, 47, 32, 67, 114, 101, 97, 116, 101, 84, 97, 98, 108, 101, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, 67, 114, 101, 97, 116, 101, 84, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 10, 67, 114, 101, 97, 116, 101, 84, 97, 98, 108, 101, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 84, 111, 112, 76, 101, 118, 101, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, - 67, 111, 108, 117, 109, 110, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 84, 121, 112, 101, 63, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 63, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 67, 108, 97, 117, 115, 101, 63, 32, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 42, 10, - 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 78, 111, 116, 78, 117, 108, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 85, 110, 105, 113, 117, 101, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 80, 114, 105, 109, 97, 114, 121, 75, 101, 121, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 101, 102, 97, 117, 108, 116, 86, 97, 108, 117, 101, 32, 47, 32, 67, 104, 101, 99, 107, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 70, 111, 114, 101, 105, 103, 110, 75, 101, 121, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 111, 108, 117, 109, 110, 67, 111, 108, 108, 97, 116, 105, 111, 110, 32, 47, 32, 67, 111, 108, 117, 109, 110, 67, 111, 109, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 67, 111, 108, 117, 109, 110, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 84, 121, 112, 101, 63, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 63, 32, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 42, 10, + 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 111, 108, 117, 109, 110, 67, 111, 108, 108, 97, 116, 105, 111, 110, 10, + 78, 97, 109, 101, 97, 98, 108, 101, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 67, 108, 97, 117, 115, 101, 63, 32, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 69, 108, 101, 109, 101, 110, 116, 10, + 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 69, 108, 101, 109, 101, 110, 116, 32, 60, 45, 32, 78, 111, 116, 78, 117, 108, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 85, 110, 105, 113, 117, 101, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 80, 114, 105, 109, 97, 114, 121, 75, 101, 121, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 101, 102, 97, 117, 108, 116, 86, 97, 108, 117, 101, 32, 47, 32, 67, 104, 101, 99, 107, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 70, 111, 114, 101, 105, 103, 110, 75, 101, 121, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 111, 108, 117, 109, 110, 67, 111, 109, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 111, 116, 78, 117, 108, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 78, 117, 108, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 78, 111, 116, 78, 117, 108, 108, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, 78, 117, 108, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 78, 85, 76, 76, 39, 10, 78, 111, 116, 78, 117, 108, 108, 67, 111, 108, 117, 109, 110, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 78, 79, 84, 39, 32, 39, 78, 85, 76, 76, 39, 10, diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher_stack.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher_stack.hpp index 57f7d30a9..d9b87cafa 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher_stack.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher_stack.hpp @@ -40,6 +40,9 @@ struct MatchStackFrame { optional child_result; optional result; PackratMatchState packrat_state; + //! The position of the process arena when the frame was pushed - the arena is rewound to it when the frame is + //! popped + ArenaAllocator::Position process_arena_position; }; class MatchStack { diff --git a/src/duckdb/src/include/duckdb/parser/peg/parsed_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/parsed_grammar.hpp index c01a903b7..76c22f1f2 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/parsed_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/parsed_grammar.hpp @@ -32,6 +32,15 @@ using terminal_rule_overrides_t = case_insensitive_map_t>; using terminal_rule_matcher_factory_t = std::function(const PEGKeywordHelper &)>; using terminal_rule_override_callback_t = std::function; +//! How a rule contributes to the expression depth while it is transformed (see ParsedGrammar::SetExpressionDepth) +enum class ExpressionDepthKind : uint8_t { + NONE, + //! one level: the rule nests an expression or type + NESTING, + //! one level per repeated element: the rule's repetitions nest its result, like operator chains + CHAIN +}; + struct ParsedGrammarRule { ParsedGrammarRule(string name_p, PEGRule recipe_p) : name(std::move(name_p)), recipe(std::move(recipe_p)) { } @@ -41,6 +50,10 @@ struct ParsedGrammarRule { grammar_transform_process_function_t transform_process; //! See ParsedGrammar::SetTransformProcess bool collapsible = false; + //! See ParsedGrammar::AddRule + bool suppress_suggestions = false; + //! See ParsedGrammar::SetExpressionDepth + ExpressionDepthKind expression_depth = ExpressionDepthKind::NONE; }; //! Mutable, owning representation of a PEG grammar before matcher compilation. @@ -56,8 +69,11 @@ class ParsedGrammar { DUCKDB_API static ParsedGrammar CreateDefault(); DUCKDB_API optional_ptr GetRule(const string &rule_name) const; + //! A rule with suppress_suggestions set contributes no autocomplete suggestions. This keeps catch-all rules, such + //! as a bare expression accepted as a statement, from polluting completion at the position where they apply. DUCKDB_API void AddRule(const string &rule_definition, - grammar_transform_process_function_t transform_process = nullptr); + grammar_transform_process_function_t transform_process = nullptr, + bool suppress_suggestions = false); DUCKDB_API void AddChoice(const string &rule_name, const string &choice, const grammar_cursor_function_t &find_cursor = nullptr); DUCKDB_API void PrependChoice(const string &rule_name, const string &choice, @@ -72,7 +88,10 @@ class ParsedGrammar { //! matcher then skips building a result for the rule and the child is transformed as itself. Setting a new //! transform clears that promise unless the caller repeats it, since it is a property of the transform. DUCKDB_API void SetTransformProcess(const string &rule_name, grammar_transform_process_function_t transform_process, - bool collapsible = false); + bool collapsible = false, bool suppress_suggestions = false); + //! Declares how transforming the rule nests the resulting expression, so that max_expression_depth can be enforced + //! while transforming: the depth of the expression trees is bounded by the levels of the rules on the stack + DUCKDB_API void SetExpressionDepth(const string &rule_name, ExpressionDepthKind kind); DUCKDB_API void AddTerminalRuleOverride(const string &rule_name, terminal_rule_matcher_factory_t matcher_factory); private: @@ -101,8 +120,10 @@ class ParsedGrammar { //! Immutable semantic data referenced directly by matchers and parse results. struct CompiledGrammarRule { CompiledGrammarRule(string name_p, grammar_transform_process_function_t transform_process_p, - bool collapsible_p = false) - : name(std::move(name_p)), transform_process(std::move(transform_process_p)), collapsible(collapsible_p) { + bool collapsible_p = false, ExpressionDepthKind expression_depth_p = ExpressionDepthKind::NONE, + bool suppress_suggestions_p = false) + : name(std::move(name_p)), transform_process(std::move(transform_process_p)), collapsible(collapsible_p), + suppress_suggestions(suppress_suggestions_p), expression_depth(expression_depth_p) { } arena_ptr StartTransform(PEGTransformer &transformer, ParseResult &parse_result) const; @@ -111,6 +132,10 @@ struct CompiledGrammarRule { grammar_transform_process_function_t transform_process; //! See ParsedGrammar::SetTransformProcess bool collapsible; + //! See ParsedGrammar::AddRule + bool suppress_suggestions; + //! See ParsedGrammar::SetExpressionDepth + ExpressionDepthKind expression_depth; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp index 800dd8de7..66714802d 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp @@ -402,6 +402,8 @@ struct TransformStackFrame { ParseResult &parse_result; arena_ptr process; arena_ptr child_result; + //! How much this frame adds to the expression depth while it is on the stack + idx_t expression_depth_weight = 0; }; #ifdef DEBUG @@ -413,6 +415,7 @@ using frame_stack_t = stack; class TransformStack { public: explicit TransformStack(PEGTransformer &transformer); + ~TransformStack(); arena_ptr Execute(TransformInput input); template @@ -431,6 +434,7 @@ class TransformStack { private: void PushFrame(TransformInput input); + void PopFrame(); void InitializeFrame(TransformStackFrame &frame); arena_ptr ExecuteFrame(TransformStackFrame &frame); @@ -570,6 +574,13 @@ class PEGTransformer { friend class StackChecker; idx_t stack_depth = 0; + //! An upper bound of the depth of the expression trees currently being transformed - tracked by the transform + //! stacks: every nested expression, nested type and link of an operator chain counts while it is transformed + idx_t expression_depth = 0; + //! The depth a frame of this rule adds while it is on the stack (see ParsedGrammar::SetExpressionDepth) + static idx_t ExpressionDepthWeight(optional_ptr rule, ParseResult &parse_result); + void EnterExpression(idx_t weight); + void ExitExpression(idx_t weight); StackChecker StackCheck(idx_t extra_stack = 1) { if (stack_depth + extra_stack >= options.max_expression_depth) { @@ -932,13 +943,6 @@ class PEGTransformerFactory { static void InitializeAnalyzeKeywordTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeAnalyzeKeywordTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeExpressionStatementTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeExpressionStatementTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeExpressionAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeExpressionAliasTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeIndexNameTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeIndexNameTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1692,6 +1696,14 @@ class PEGTransformerFactory { static void InitializeColumnConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeColumnConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeNameableColumnConstraintTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr + FinalizeNameableColumnConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeColumnConstraintElementTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr + FinalizeColumnConstraintElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static void InitializeNotNullConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeNotNullConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4263,8 +4275,6 @@ class PEGTransformerFactory { static CatalogType TransformCommentColumn(PEGTransformer &transformer); static Value TransformStringLiteralValue(PEGTransformer &transformer, const string &string_literal); static Identifier TransformAnalyzeKeyword(PEGTransformer &transformer); - static unique_ptr TransformExpressionStatement(PEGTransformer &transformer, - vector> expression_alias); static Identifier TransformConstraintName(PEGTransformer &transformer, const Identifier &col_id_or_string); static Identifier TransformCollationName(PEGTransformer &transformer, const Identifier &identifier); static unique_ptr TransformNumberLiteral(PEGTransformer &transformer, ParseResult &parse_result); @@ -4614,7 +4624,9 @@ class PEGTransformerFactory { static ConstraintColumnDefinition TransformColumnDefinition(PEGTransformer &transformer, const Identifier &identifier, const optional &type, optional generated_column, - const bool &has_result, optional> column_constraint); + optional> column_constraint); + static ColumnConstraintEntry TransformNameableColumnConstraint(PEGTransformer &transformer, const bool &has_result, + ColumnConstraintEntry child); static ColumnConstraintEntry TransformNotNullConstraint(PEGTransformer &transformer, const bool &child); static bool TransformNullConstraint(PEGTransformer &transformer); static bool TransformNotNullColumnConstraint(PEGTransformer &transformer); diff --git a/src/duckdb/src/include/duckdb/parser/qualified_name.hpp b/src/duckdb/src/include/duckdb/parser/qualified_name.hpp index 312dc7f2f..eb1a9096b 100644 --- a/src/duckdb/src/include/duckdb/parser/qualified_name.hpp +++ b/src/duckdb/src/include/duckdb/parser/qualified_name.hpp @@ -140,6 +140,8 @@ struct QualifiedName { //! Parse the (optional) schema and a name from a string in the format of e.g. "schema"."table"; if there is no dot //! the schema will be set to INVALID_SCHEMA static QualifiedName Parse(const string &input); + //! Parse a qualified name, returning false and setting the error if it is malformed or any component is empty + static bool TryParse(const string &input, QualifiedName &result, string &error); static vector ParseComponents(const string &input); string ToString(QualifiedNameToStringMode mode = QualifiedNameToStringMode::DEFAULT) const; //! Render only the qualification (every component before the name), with a trailing "." after each component diff --git a/src/duckdb/src/include/duckdb/parser/query_node.hpp b/src/duckdb/src/include/duckdb/parser/query_node.hpp index d0e05aa69..a63375ea6 100644 --- a/src/duckdb/src/include/duckdb/parser/query_node.hpp +++ b/src/duckdb/src/include/duckdb/parser/query_node.hpp @@ -79,6 +79,8 @@ class QueryNode { virtual void Serialize(Serializer &serializer) const; static unique_ptr Deserialize(Deserializer &deserializer); + //! Throws a SerializationException if a deserialized query node does not have a valid shape + static void VerifyDeserialized(const unique_ptr &node); protected: //! Copy base QueryNode properties from another expression to this one, diff --git a/src/duckdb/src/include/duckdb/parser/tableref.hpp b/src/duckdb/src/include/duckdb/parser/tableref.hpp index b09320f67..00b3e99eb 100644 --- a/src/duckdb/src/include/duckdb/parser/tableref.hpp +++ b/src/duckdb/src/include/duckdb/parser/tableref.hpp @@ -55,6 +55,8 @@ class TableRef { virtual void Serialize(Serializer &serializer) const; static unique_ptr Deserialize(Deserializer &deserializer); + //! Throws a SerializationException if a deserialized table reference does not have a valid shape + void VerifyDeserialized() const; public: template diff --git a/src/duckdb/src/include/duckdb/planner/binder.hpp b/src/duckdb/src/include/duckdb/planner/binder.hpp index 4ff8a5153..827357bbc 100644 --- a/src/duckdb/src/include/duckdb/planner/binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/binder.hpp @@ -327,6 +327,7 @@ class Binder : public enable_shared_from_this { //! Add the view to the set of currently bound views - used for detecting recursive view definitions void AddBoundView(ViewCatalogEntry &view); + void AddBoundTableMacro(CatalogEntry &table_macro); void BeginSubqueryBind(Binder &parent, ExpressionBinder &binder); //! The innermost enclosing scope @@ -433,6 +434,8 @@ class Binder : public enable_shared_from_this { bool inside_subquery = false; //! The set of bound views reference_set_t bound_views; + //! The table macros that are being expanded by this binder + reference_set_t bound_table_macros; //! Used to retrieve CatalogEntry's CatalogEntryRetriever entry_retriever; //! Unnamed subquery index diff --git a/src/duckdb/src/include/duckdb/planner/bound_expression_sql_exporter.hpp b/src/duckdb/src/include/duckdb/planner/bound_expression_sql_exporter.hpp index 2999a80fc..1ec341255 100644 --- a/src/duckdb/src/include/duckdb/planner/bound_expression_sql_exporter.hpp +++ b/src/duckdb/src/include/duckdb/planner/bound_expression_sql_exporter.hpp @@ -31,7 +31,7 @@ struct ResolvedSQLColumnReference { //! Type represented by the exported SQL expression. LogicalType type; //! Optimizer-selected type accepted from the bound plan, when different. - optional optimizer_type; + optional optimizer_type = {}; }; using BoundExpressionSQLBindingResolver = diff --git a/src/duckdb/src/include/duckdb/planner/expression/window_range_info.hpp b/src/duckdb/src/include/duckdb/planner/expression/window_range_info.hpp index e740f0ac0..7efff37f6 100644 --- a/src/duckdb/src/include/duckdb/planner/expression/window_range_info.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression/window_range_info.hpp @@ -26,8 +26,10 @@ struct WindowRangeCast { void Serialize(Serializer &serializer) const; static WindowRangeCast Deserialize(Deserializer &deserializer); - static bool Capture(const Expression &expression, optional_ptr input, - vector &casts); + //! Records the casts the binder wrapped around an operand of the range arithmetic. The operand is the expression + //! still in place, or a copy of a constant operand the function binder may have folded - the chain then ends in + //! that constant + static bool Capture(const Expression &expression, const Expression &operand, vector &casts); static optional_ptr Match(const Expression &expression, const vector &casts); }; @@ -46,8 +48,8 @@ struct WindowRangeBoundary { void Serialize(Serializer &serializer) const; static unique_ptr Deserialize(Deserializer &deserializer); - static unique_ptr Capture(const Expression &expression, optional_ptr order, - optional_ptr offset); + static unique_ptr Capture(const Expression &expression, const Expression &order, + const Expression &offset); optional_ptr Match(const Expression &expression, const Expression &order) const; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/planner/expression_binder/table_function_binder.hpp b/src/duckdb/src/include/duckdb/planner/expression_binder/table_function_binder.hpp index 5fce9b6ee..91b9cd55b 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_binder/table_function_binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_binder/table_function_binder.hpp @@ -15,20 +15,16 @@ namespace duckdb { //! The table function binder can bind standard table function parameters (i.e., non-table-in-out functions) class TableFunctionBinder : public ExpressionBinder { public: + //! How an identifier that is not a lambda parameter or lateral column is bound. FOLLOW_SETTING: resolve SQL value + //! functions (e.g. user), then convert to a string as table_function_identifier_conversion allows. ALLOW: the + //! identifier is a name (e.g. a COPY column list): bind it as the string it spells, without warning and without + //! resolving SQL value functions. enum class IdentifierConversionPolicy : uint8_t { FOLLOW_SETTING, ALLOW }; TableFunctionBinder( Binder &binder, ClientContext &context, string table_function_name = string(), string clause = "Table function", IdentifierConversionPolicy identifier_conversion_policy = IdentifierConversionPolicy::FOLLOW_SETTING); -public: - void DisableSQLValueFunctions() { - accept_sql_value_functions = false; - } - void EnableSQLValueFunctions() { - accept_sql_value_functions = true; - } - protected: BindResult BindLambdaReference(LambdaRefExpression &expr, idx_t depth); BindResult BindColumnReference(unique_ptr &expr, idx_t depth, bool root_expression); @@ -40,8 +36,6 @@ class TableFunctionBinder : public ExpressionBinder { string table_function_name; string clause; IdentifierConversionPolicy identifier_conversion_policy; - //! Whether sql_value_functions (GetSQLValueFunctionName) are considered when binding column refs - bool accept_sql_value_functions = true; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/planner/expression_nullability.hpp b/src/duckdb/src/include/duckdb/planner/expression_nullability.hpp index 94c6c616c..8cbe1191c 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_nullability.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_nullability.hpp @@ -26,6 +26,9 @@ class LogicalOperator; bool ExpressionBecomesNull(const Expression &expr, const std::function &column_becomes_null); +//! Returns whether the filter cannot be TRUE when expr is NULL. +bool FilterRejectsNull(const Expression &filter, const Expression &expr); + //! Conservatively proves that an expression cannot be NULL at a logical operator's output. class NotNullExpressionAnalyzer { public: diff --git a/src/duckdb/src/include/duckdb/planner/filter/table_filter_function_helpers.hpp b/src/duckdb/src/include/duckdb/planner/filter/table_filter_function_helpers.hpp index ed1cc23e5..88b12ad54 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/table_filter_function_helpers.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/table_filter_function_helpers.hpp @@ -72,6 +72,18 @@ inline idx_t FillSelectionInversion(idx_t count, const SelectionVector &true_sel return SelectionVector::Inverted(true_sel, *false_sel, true_count, count); } +inline SelectionVector &GetFilterResultSelection(idx_t count, optional_ptr input_sel, + optional_ptr true_sel, SelectionVector &scratch_sel) { + if (true_sel && (!input_sel || true_sel.get() != input_sel.get())) { + return *true_sel; + } + // Preserve the input selection until TranslateSelection has mapped the local matches. + if (scratch_sel.Capacity() < count) { + scratch_sel.Initialize(count); + } + return scratch_sel; +} + inline idx_t TranslateSelection(idx_t count, optional_ptr input_sel, const SelectionVector &local_true_sel, idx_t local_true_count, optional_ptr true_sel, optional_ptr false_sel) { diff --git a/src/duckdb/src/include/duckdb/planner/logical_plan_sql_export_context.hpp b/src/duckdb/src/include/duckdb/planner/logical_plan_sql_export_context.hpp index 31731b830..75100655d 100644 --- a/src/duckdb/src/include/duckdb/planner/logical_plan_sql_export_context.hpp +++ b/src/duckdb/src/include/duckdb/planner/logical_plan_sql_export_context.hpp @@ -2,6 +2,7 @@ #include "duckdb/planner/logical_plan_sql_exporter.hpp" #include "duckdb/parser/query_node/select_node.hpp" +#include "duckdb/common/reference_map.hpp" namespace duckdb { class Expression; @@ -33,6 +34,12 @@ class LogicalPlanSQLExportContext { } Identifier NextRelationAlias(const Identifier &preferred = Identifier()); + //! Reserve parsed relation names before introducing generated CTEs around a query. + void ReserveRelationNames(QueryNode &query); + //! An unqualified relation reference a generated source name would rebind; empty when there is none. + optional FindCapturedSourceReference(QueryNode &query, const identifier_set_t &source_names) const; + //! The effective source boundary, including enclosing export scopes. + optional_ptr FindSource(const LogicalOperator &op) const; LogicalPlanVerificationResult ExportChild(LogicalOperator &child, const LogicalPlanVerificationPath &path); LogicalPlanVerificationResult @@ -59,6 +66,16 @@ class LogicalPlanSQLExportContext { const vector> &Ancestors() const { return ancestors; } + //! Enters an operator exported outside the standard child recursion (e.g. a RETURNING root) + struct AncestorScope { + AncestorScope(LogicalPlanSQLExportContext &context_p, LogicalOperator &op) : context(context_p) { + context.ancestors.push_back(op); + } + ~AncestorScope() { + context.ancestors.pop_back(); + } + LogicalPlanSQLExportContext &context; + }; //! Make a CTE relation referenceable while its consumers are exported void PushNamedRelation(TableIndex index, const Identifier &name, bool is_recurring); //! Remove the innermost named relation and return how often it was referenced @@ -78,6 +95,8 @@ class LogicalPlanSQLExportContext { ClientContext &context; idx_t next_relation_ordinal = 0; identifier_set_t relation_aliases; + //! The named-source reads this export created; any other reference to a source name is a capture. + reference_set_t source_reads; vector> ancestors; vector named_relations; }; diff --git a/src/duckdb/src/include/duckdb/planner/logical_plan_sql_exporter.hpp b/src/duckdb/src/include/duckdb/planner/logical_plan_sql_exporter.hpp index 742efa4ef..33331fa18 100644 --- a/src/duckdb/src/include/duckdb/planner/logical_plan_sql_exporter.hpp +++ b/src/duckdb/src/include/duckdb/planner/logical_plan_sql_exporter.hpp @@ -28,7 +28,7 @@ struct LogicalPlanSQLExportField { //! Type represented by the exported SQL expression. LogicalType type; //! Optimizer-selected type accepted from the bound plan, when different. - optional optimizer_type; + optional optimizer_type = {}; }; struct LogicalPlanSQLExportRelation { @@ -43,11 +43,25 @@ struct LogicalPlanSQLExportOptions { optional> output_names; }; +struct LogicalPlanSQLExportReplacement { + reference op; + LogicalPlanSQLExportRelation relation; +}; + class LogicalPlanSQLExporter { public: //! Export a verified plan to an owned query with positional binding/type fields. DUCKDB_API static LogicalPlanVerificationResult Export(ClientContext &context, LogicalOperator &root, const LogicalPlanSQLExportOptions &options = {}); + + //! Export an already resolved plan using owned SQL relations at explicit subtree boundaries. + //! Checks existing binding/type metadata without re-resolving the input. Each replacement preserves + //! the replaced subtree's visible bindings and types and is materialized once in the returned SQL. + //! Replacements must be disjoint, reachable subtrees. Neither the plan nor replacement ASTs are consumed. + DUCKDB_API static LogicalPlanSQLExportResult + ExportWithSources(ClientContext &context, LogicalOperator &root, + const vector &replacements, + const LogicalPlanSQLExportOptions &options = {}); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/planner/logical_plan_verifier.hpp b/src/duckdb/src/include/duckdb/planner/logical_plan_verifier.hpp index 3ed31043e..7a7852449 100644 --- a/src/duckdb/src/include/duckdb/planner/logical_plan_verifier.hpp +++ b/src/duckdb/src/include/duckdb/planner/logical_plan_verifier.hpp @@ -19,12 +19,18 @@ class LogicalPlanVerifier : private ColumnBindingResolver { public: static void Verify(ClientContext &context, LogicalOperator &op); DUCKDB_API static LogicalPlanVerificationResult VerifyAlways(LogicalOperator &op); + //! Check bindings against an already resolved plan without resolving or changing its output metadata. + //! The caller supplies the resolved types; this does not infer or repair stale operator types. + DUCKDB_API static LogicalPlanVerificationResult + VerifyResolvedBindings(LogicalOperator &op); private: + enum class TypeResolution { RESOLVE, EXISTING }; explicit LogicalPlanVerifier(LogicalPlanVerificationState &verification_state); unique_ptr VisitReplace(BoundColumnRefExpression &expr, unique_ptr *expr_ptr) override; - static bool ResolveOperatorTypes(LogicalOperator &op, LogicalPlanVerificationState &verification_state); + static bool ResolveOperatorTypes(LogicalOperator &op, LogicalPlanVerificationState &verification_state, + TypeResolution resolution = TypeResolution::RESOLVE); static void VerifyColumnBindings(LogicalOperator &op, LogicalPlanVerificationState &verification_state); static LogicalPlanVerificationResult VerifyAlwaysInternal(LogicalOperator &op, optional_ptr first_error); diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_comparison_join.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_comparison_join.hpp index a66580f9c..4a2f03bd3 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_comparison_join.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_comparison_join.hpp @@ -69,6 +69,8 @@ class LogicalComparisonJoin : public LogicalJoin { bool HasEquality(idx_t &range_count) const; bool HasArbitraryConditions() const; + //! Two-valued MARK comparisons with at least one null-safe equality hash key. + bool HasNullSafeMarkJoinConditions() const; bool TryGetMarkJoinGroupTypes(vector &group_types) const; }; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_delete.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_delete.hpp index cc7c51086..1bca8cfb6 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_delete.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_delete.hpp @@ -32,6 +32,9 @@ class LogicalDelete : public LogicalOperator { void Serialize(Serializer &serializer) const override; static unique_ptr Deserialize(Deserializer &deserializer); + LogicalPlanSQLExportResult ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) override; + idx_t EstimateCardinality(ClientContext &context) override; vector GetTableIndex() const override; string GetName() const override; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp index 39dc666e0..4f0d7d1ec 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_insert.hpp @@ -75,6 +75,9 @@ class LogicalInsert : public LogicalOperator { void Serialize(Serializer &serializer) const override; static unique_ptr Deserialize(Deserializer &deserializer); + LogicalPlanSQLExportResult ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) override; + protected: vector GetColumnBindings() override; void ResolveTypes() override; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_merge_into.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_merge_into.hpp index e8cb2ed0d..eef39e6ad 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_merge_into.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_merge_into.hpp @@ -66,6 +66,9 @@ class LogicalMergeInto : public LogicalOperator { void Serialize(Serializer &serializer) const override; static unique_ptr Deserialize(Deserializer &deserializer); + LogicalPlanSQLExportResult ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) override; + idx_t EstimateCardinality(ClientContext &context) override; vector GetTableIndex() const override; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_update.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_update.hpp index c9664a6bb..2a85e5909 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_update.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_update.hpp @@ -48,6 +48,9 @@ class LogicalUpdate : public LogicalOperator { void Serialize(Serializer &serializer) const override; static unique_ptr Deserialize(Deserializer &deserializer); + LogicalPlanSQLExportResult ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) override; + idx_t EstimateCardinality(ClientContext &context) override; string GetName() const override; diff --git a/src/duckdb/src/include/duckdb/planner/sql_export/logical_plan_sql_exporter_internal.hpp b/src/duckdb/src/include/duckdb/planner/sql_export/logical_plan_sql_exporter_internal.hpp index 3a1144381..532d74127 100644 --- a/src/duckdb/src/include/duckdb/planner/sql_export/logical_plan_sql_exporter_internal.hpp +++ b/src/duckdb/src/include/duckdb/planner/sql_export/logical_plan_sql_exporter_internal.hpp @@ -106,4 +106,21 @@ struct LogicalPlanSQLExportHelpers { static bool IsIdentityProjection(const LogicalProjection &projection, const vector &fields); }; + +//! Export of data-modifying plan roots and CTE bodies (sql_export_dml.cpp) +struct SQLExportDMLHelpers { + //! The RETURNING projection over a data-modifying operator, when the root has that shape. + static optional_ptr MatchReturningRoot(LogicalOperator &root); + //! Export a RETURNING root; output names become the RETURNING aliases. + static LogicalPlanSQLExportResult ExportReturning(LogicalPlanSQLExportContext &context, LogicalProjection &root, + const optional> &output_names, + const LogicalPlanVerificationPath &path); + //! Whether this operator is a data-modifying statement body: a DML operator or its RETURNING projection. + static bool MatchDMLBody(LogicalOperator &op); + //! Export a data-modifying statement body outside the plan root (e.g. a materialized CTE side). + static LogicalPlanSQLExportResult ExportBody(LogicalPlanSQLExportContext &context, LogicalOperator &op, + const LogicalPlanVerificationPath &path); + //! Whether this exported query is a data-modifying statement node. + static bool IsDMLNode(const QueryNode &query); +}; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/storage/arena_allocator.hpp b/src/duckdb/src/include/duckdb/storage/arena_allocator.hpp index 6830cbb9f..dd3c40b24 100644 --- a/src/duckdb/src/include/duckdb/storage/arena_allocator.hpp +++ b/src/duckdb/src/include/duckdb/storage/arena_allocator.hpp @@ -60,6 +60,16 @@ class ArenaAllocator { head->current_position -= shrink_size; } + //! A position in the arena - everything allocated after it was taken can be released again with Rewind + struct Position { + ArenaChunk *chunk = nullptr; + idx_t current_position = 0; + }; + //! Returns the current position of the arena + DUCKDB_API Position GetPosition() const; + //! Releases everything that was allocated after the position was taken - those allocations may no longer be used + DUCKDB_API void Rewind(const Position &position); + //! Resets the current head and destroys all previous arena chunks DUCKDB_API void Reset(); //! Like Reset, but also destroys the current chunk, so no memory stays allocated @@ -80,6 +90,11 @@ class ArenaAllocator { Allocator &GetAllocator() { return arena_allocator; } + //! Returns the allocator the arena itself allocates its blocks from - for memory that must be freeable + //! individually but should share the arena's memory accounting + Allocator &GetUnderlyingAllocator() { + return allocator; + } template T *Make(ARGS &&... args) { diff --git a/src/duckdb/src/include/duckdb/storage/buffer/block_handle.hpp b/src/duckdb/src/include/duckdb/storage/buffer/block_handle.hpp index 8941a0fac..26002f55d 100644 --- a/src/duckdb/src/include/duckdb/storage/buffer/block_handle.hpp +++ b/src/duckdb/src/include/duckdb/storage/buffer/block_handle.hpp @@ -74,19 +74,27 @@ class BlockMemory : public enable_shared_from_this { } //! Returns the number of readers. int32_t GetReaders() const { - return readers; + return readers.load(std::memory_order_relaxed); } //! Increments the number of readers prior to returning it. - int32_t IncrementReaders() { - return ++readers; + int32_t IncrementReaders(BlockLock &l) { + VerifyMutex(l); + // Tiny buffers do not decrement on unpin, so their reader count can wrap. + auto new_readers = static_cast(static_cast(readers.load(std::memory_order_relaxed)) + 1); + readers.store(new_readers, std::memory_order_relaxed); + return new_readers; } //! Decrements the number of readers prior to returning it. - int32_t DecrementReaders() { - return --readers; + int32_t DecrementReaders(BlockLock &l) { + VerifyMutex(l); + auto new_readers = static_cast(static_cast(readers.load(std::memory_order_relaxed)) - 1); + readers.store(new_readers, std::memory_order_relaxed); + return new_readers; } //! Sets the number of readers. - void SetReaders(int32_t n) { - readers = n; + void SetReaders(BlockLock &l, int32_t n) { + VerifyMutex(l); + readers.store(n, std::memory_order_relaxed); } //! Returns the memory tag. MemoryTag GetMemoryTag() const { @@ -111,11 +119,15 @@ class BlockMemory : public enable_shared_from_this { } //! Returns the eviction sequence number. idx_t GetEvictionSequenceNumber() const { - return eviction_seq_num; + return eviction_seq_num.load(std::memory_order_acquire); } //! Increments the eviction sequence number prior to returning it. - idx_t NextEvictionSequenceNumber() { - return ++eviction_seq_num; + idx_t NextEvictionSequenceNumber(BlockLock &l) { + VerifyMutex(l); + auto new_sequence_number = eviction_seq_num.load(std::memory_order_relaxed) + 1; + // Publish preceding dead-node accounting to lock-free queue readers. + eviction_seq_num.store(new_sequence_number, std::memory_order_release); + return new_sequence_number; } //! Returns true, if the block has a live (not yet dead-counted) entry in the eviction queue. bool HasLiveQueueEntry(BlockLock &l) const { @@ -186,14 +198,6 @@ class BlockMemory : public enable_shared_from_this { VerifyMutex(l); memory_charge.Merge(std::move(reservation)); } - //! Returns true, if there is a swizzled memory pointer, else false. - bool IsSwizzled() const { - return !unswizzled; - } - //! Sets the swizzled memory pointer. - void SetSwizzling(const char *unswizzler) { - unswizzled = unswizzler; - } //! Sets the eviction queue index. void SetEvictionQueueIndex(const idx_t index) { // The index can only be set once. @@ -227,7 +231,7 @@ class BlockMemory : public enable_shared_from_this { mutex lock; //! Whether the block is loaded or unloaded. atomic state; - //! The number of concurrent readers. + //! The number of concurrent readers. Writers hold the block lock; reads may be unlocked. atomic readers; //! The memory tag. const MemoryTag tag; @@ -236,7 +240,7 @@ class BlockMemory : public enable_shared_from_this { //! A pointer to the loaded data, if any. unique_ptr buffer; //! The internal eviction sequence number. Monotonic: it is never reset, so an eviction queue - //! entry is stale if and only if its sequence number differs from this one. + //! entry is stale if and only if its sequence number differs from this one. Writers hold the block lock. atomic eviction_seq_num; //! Whether the block has a live entry in the eviction queue, i.e., an entry whose sequence //! number matches eviction_seq_num and which has not been counted as a dead node. @@ -251,8 +255,6 @@ class BlockMemory : public enable_shared_from_this { atomic memory_usage; //! The current memory reservation/usage. BufferPoolReservation memory_charge; - //! Swizzled memory pointers. - const char *unswizzled; //! The eviction queue index, currently only FileBufferType::MANAGED_BUFFER. atomic eviction_queue_idx; }; @@ -303,7 +305,7 @@ class BlockHandle : public enable_shared_from_this { public: BufferHandle LoadFromBuffer(BlockLock &l, data_ptr_t data, unique_ptr reusable_buffer, BufferPoolReservation reservation); - BufferHandle Load(QueryContext context, unique_ptr buffer = nullptr); + BufferHandle Load(BlockLock &l, QueryContext context, unique_ptr buffer = nullptr); private: //! The block manager, which loads the block. diff --git a/src/duckdb/src/include/duckdb/storage/buffer/buffer_pool.hpp b/src/duckdb/src/include/duckdb/storage/buffer/buffer_pool.hpp index 5f8756008..3a04f77f5 100644 --- a/src/duckdb/src/include/duckdb/storage/buffer/buffer_pool.hpp +++ b/src/duckdb/src/include/duckdb/storage/buffer/buffer_pool.hpp @@ -135,16 +135,23 @@ class BufferPool { }; struct MemoryUsage { - //! The maximum difference between memory statistics and actual usage is 2MB (64 * 32k) + //! Total memory accounting differs from actual usage by at most 2MB (64 * 32k) static constexpr idx_t MEMORY_USAGE_CACHE_COUNT = 64; static constexpr idx_t MEMORY_USAGE_CACHE_THRESHOLD = 32 << 10; + //! Per-tag statistics are flushed when read and can use a larger cache threshold. + static constexpr idx_t MEMORY_USAGE_TAG_CACHE_THRESHOLD = 1 << 20; static constexpr idx_t TOTAL_MEMORY_USAGE_INDEX = MEMORY_TAG_COUNT; using MemoryUsageCounters = array, MEMORY_TAG_COUNT + 1>; + //! Keep neighboring caches separate on machines with 64- or 128-byte cache lines. + struct alignas(128) MemoryUsageCache { + MemoryUsageCounters counters; + }; + //! global memory usage counters MemoryUsageCounters memory_usage; //! cache memory usage to improve performance - array memory_usage_caches; + array memory_usage_caches; MemoryUsage(); @@ -163,7 +170,7 @@ class BufferPool { } int64_t cached = 0; for (auto &cache : memory_usage_caches) { - cached += cache[index].exchange(0, std::memory_order_relaxed); + cached += cache.counters[index].exchange(0, std::memory_order_relaxed); } auto used_memory = memory_usage[index].fetch_add(cached, std::memory_order_relaxed) + cached; return used_memory > 0 ? static_cast(used_memory) : 0; diff --git a/src/duckdb/src/include/duckdb/storage/checkpoint/table_data_writer.hpp b/src/duckdb/src/include/duckdb/storage/checkpoint/table_data_writer.hpp index ae137da59..37eb2be5d 100644 --- a/src/duckdb/src/include/duckdb/storage/checkpoint/table_data_writer.hpp +++ b/src/duckdb/src/include/duckdb/storage/checkpoint/table_data_writer.hpp @@ -34,6 +34,7 @@ class TableDataWriter { virtual void WriteUnchangedTable(MetaBlockPointer pointer, const vector &metadata_pointers, idx_t total_rows, idx_t next_row_id) = 0; + //! global_stats is empty when reusing existing table metadata. virtual void FinalizeTable(const TableStatistics &global_stats, DataTableInfo &info, RowGroupCollection &collection, Serializer &serializer) = 0; virtual unique_ptr GetRowGroupWriter(RowGroup &row_group) = 0; diff --git a/src/duckdb/src/include/duckdb/storage/external_file_cache/caching_file_system.hpp b/src/duckdb/src/include/duckdb/storage/external_file_cache/caching_file_system.hpp index 33b9412cd..2b28b8088 100644 --- a/src/duckdb/src/include/duckdb/storage/external_file_cache/caching_file_system.hpp +++ b/src/duckdb/src/include/duckdb/storage/external_file_cache/caching_file_system.hpp @@ -92,6 +92,8 @@ struct CachingFileHandle { shared_ptr EnsureCachedFileCurrent(); //! Whether validation metadata permits using cached blocks. bool CanUseCache(); + //! Read without caching, splitting large reads of cacheable files into parallel reads of at most a block. + void ReadUncached(data_ptr_t buffer, idx_t nr_bytes, idx_t location); //! Reconcile cached blocks with validation metadata observed while reading them. void ReconcileCacheAfterRead(CachedFile &cached_file); //! Record a timed read of a local file into the throughput estimate @@ -100,8 +102,10 @@ struct CachingFileHandle { private: QueryContext context; - //! The client caching file system that was used to create this CachingFileHandle - CachingFileSystem &caching_file_system; + //! The client file system, held directly because the caching file system can be destroyed before this handle + FileSystem &file_system; + //! The DatabaseInstance + DatabaseInstance &db; //! The DB external file cache ExternalFileCache &external_file_cache; //! For opening the file (possibly with extra info) diff --git a/src/duckdb/src/include/duckdb/storage/standard_buffer_manager.hpp b/src/duckdb/src/include/duckdb/storage/standard_buffer_manager.hpp index 150a15a7e..cd2d661eb 100644 --- a/src/duckdb/src/include/duckdb/storage/standard_buffer_manager.hpp +++ b/src/duckdb/src/include/duckdb/storage/standard_buffer_manager.hpp @@ -187,7 +187,9 @@ class StandardBufferManager : public BufferManager { struct TemporaryFileData { //! The directory name where temporary files are stored string path; - //! Lock for creating the temp handle (marked mutable so 'GetMaxSwap' can be const) + //! Publishes successful temporary-directory initialization + atomic initialized {false}; + //! Protects configuration and handle creation (mutable so 'GetMaxSwap' can be const) mutable mutex lock; //! Size of temp file on disk atomic size_on_disk = {0}; diff --git a/src/duckdb/src/include/duckdb/storage/table/append_state.hpp b/src/duckdb/src/include/duckdb/storage/table/append_state.hpp index 2275a27e5..9dff4148c 100644 --- a/src/duckdb/src/include/duckdb/storage/table/append_state.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/append_state.hpp @@ -90,8 +90,6 @@ struct RowGroupAppendState { unsafe_unique_array states; //! Offset within the row_group idx_t offset_in_row_group; - //! A sub-allocation block for transient storage - SuballocationBlock transient; }; struct TableAppendState { diff --git a/src/duckdb/src/include/duckdb/storage/table/row_group.hpp b/src/duckdb/src/include/duckdb/storage/table/row_group.hpp index db8b69417..7ab232dc7 100644 --- a/src/duckdb/src/include/duckdb/storage/table/row_group.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/row_group.hpp @@ -51,6 +51,7 @@ class TableFilterSet; struct ColumnFetchState; struct PrefetchState; struct RowGroupAppendState; +struct SuballocationBlock; class MetadataManager; class RowVersionManager; class CommitDropState; @@ -216,6 +217,8 @@ class RowGroup : public SegmentBase { PersistentRowGroupData SerializeRowGroupInfo(idx_t row_group_start) const; static void InitializeAppend(SegmentNode &row_group, RowGroupAppendState &append_state); + //! Continue a finalized append in a new row group, retaining unused transient block space + static void InitializeNextAppend(SegmentNode &row_group, RowGroupAppendState &append_state); void Append(RowGroupAppendState &append_state, DataChunk &chunk, idx_t append_count); void FinalizeAppend(RowGroupAppendState &append_state); @@ -308,6 +311,8 @@ class RowGroup : public SegmentBase { PerColumnMetadataBlocks per_column_metadata_blocks; atomic deletes_is_loaded; atomic allocation_size; + //! Append cursor, retained between appends and moved when appending to the next row group + unique_ptr transient; //! The row id column data (mutable because `const` can lazy load) mutable unique_ptr row_id_column_data; //! Whether or not `row_id_column_data` is loaded (mutable because `const` can lazy load) diff --git a/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp b/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp index 5f5fc7db3..18b73bf3c 100644 --- a/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/row_group_collection.hpp @@ -145,7 +145,8 @@ class RowGroupCollection { void UpdateColumn(TransactionData transaction, DuckTableEntry &table_entry, Vector &row_ids, const vector &column_path, DataChunk &updates); - void Checkpoint(TableDataWriter &writer, TableStatistics &global_stats); + //! Returns whether global_stats contains newly checkpointed statistics. + bool Checkpoint(TableDataWriter &writer, TableStatistics &global_stats); //! Decides how vacuum handles this table's indexes. VacuumIndexStrategy diff --git a/src/duckdb/src/include/duckdb/storage/table/scan_state.hpp b/src/duckdb/src/include/duckdb/storage/table/scan_state.hpp index c60eabeb8..2da96f373 100644 --- a/src/duckdb/src/include/duckdb/storage/table/scan_state.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/scan_state.hpp @@ -421,7 +421,6 @@ struct ParallelCollectionScanState { idx_t vector_index; idx_t max_row; idx_t batch_index; - atomic processed_rows; //! Rows of assignments that were skipped entirely (e.g. by zonemaps) without being handed out atomic skipped_rows; optional_idx row_number_base; diff --git a/src/duckdb/src/include/duckdb_extension_v2.h b/src/duckdb/src/include/duckdb_extension_v2.h index a50252398..bc3df295e 100644 --- a/src/duckdb/src/include/duckdb_extension_v2.h +++ b/src/duckdb/src/include/duckdb_extension_v2.h @@ -91,11 +91,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_combine) (duckdb_v2_column_data_collection_handle target, duckdb_v2_column_data_collection_handle *source, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, - duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create_with_context) - (duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, + DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_create) + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_column_data_collection_destroy)(duckdb_v2_column_data_collection_handle *collection); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_reset) @@ -117,68 +114,27 @@ typedef struct { duckdb_v2_column_data_collection_worker_scan_state_handle *out_state, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_worker_scan_state_destroy) (duckdb_v2_column_data_collection_worker_scan_state_handle *state); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create) - (duckdb_v2_instance_handle instance, duckdb_v2_connection_handle *out_conn, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_id) - (duckdb_v2_connection_handle conn, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_name) - (duckdb_v2_connection_handle conn, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_by_index) + (duckdb_v2_config_handle config, idx_t index, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_from_text) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_create_type_with_alias) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle base_type, - const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_by_name) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_count) + (duckdb_v2_config_handle config, idx_t *out_count, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_create) + (duckdb_v2_instance_handle instance, duckdb_v2_connection_handle *out_conn, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_connection_destroy)(duckdb_v2_connection_handle *conn); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_by_index) - (duckdb_v2_connection_handle conn, idx_t index, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_by_name) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_get_option_count) - (duckdb_v2_connection_handle conn, idx_t *out_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_connection_interrupt) (duckdb_v2_connection_handle conn, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_connection_progress_get) (duckdb_v2_connection_handle conn, double *out_percentage, uint64_t *out_rows_processed, uint64_t *out_total_rows_to_process, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_connection_set_option) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, - DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_id) - (duckdb_v2_context_handle ctx, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_name) - (duckdb_v2_context_handle ctx, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_from_text) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_create_type_with_alias) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle base_type, const duckdb_v2_identifier_t *alias_name, - duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy_with_context) - (duckdb_v2_context_handle context, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, + DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_copy) + (duckdb_v2_factory_handle factory, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create) - (const duckdb_v2_logical_type_handle *types, idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types, idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_create_with_context) - (duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types, idx_t column_count, + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types, idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_data_chunk_destroy)(duckdb_v2_data_chunk_handle *chunk); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_get_size) @@ -199,25 +155,32 @@ typedef struct { DUCKDB_V2_ERROR (*duckdb_v2_error_info_get_text)(duckdb_v2_error_info_handle info, duckdb_v2_str *out_text); DUCKDB_V2_ERROR (*duckdb_v2_error_info_set_code)(duckdb_v2_error_info_handle info, DUCKDB_V2_ERROR code); DUCKDB_V2_ERROR (*duckdb_v2_error_info_set_text)(duckdb_v2_error_info_handle info, const duckdb_v2_str *text); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_id) + (duckdb_v2_factory_handle factory, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, + const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_name) + (duckdb_v2_factory_handle factory, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, + const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_from_text) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_type_with_alias) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle base_type, + const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_attach) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, const duckdb_v2_identifier_t *name, + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, const duckdb_v2_str *path, duckdb_v2_attach_options_handle options, bool make_default, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_create) (duckdb_v2_environment_handle env, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_create_with_options) + (duckdb_v2_environment_handle env, const duckdb_v2_str *option_names, const duckdb_v2_str *option_values, + idx_t option_count, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_instance_destroy)(duckdb_v2_instance_handle *instance); DUCKDB_V2_ERROR(*duckdb_v2_instance_detach) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_by_index) - (duckdb_v2_instance_handle instance, idx_t index, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_by_name) - (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_get_option_count) - (duckdb_v2_instance_handle instance, idx_t *out_count, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_instance_set_option) - (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, - duckdb_v2_error_info_handle *err); + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_library_version)(duckdb_v2_str *out_version, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_logical_type_copy) (duckdb_v2_logical_type_handle type, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); @@ -242,16 +205,12 @@ typedef struct { (duckdb_v2_option_handle option, idx_t index, duckdb_v2_identifier_t *out_alias, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_alias_count) (duckdb_v2_option_handle option, idx_t *out_count, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_setting) - (duckdb_v2_option_handle option, duckdb_v2_str *out_default_setting, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_value) + (duckdb_v2_option_handle option, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_description) (duckdb_v2_option_handle option, duckdb_v2_str *out_description, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_option_get_name) (duckdb_v2_option_handle option, duckdb_v2_identifier_t *out_name, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_setting) - (duckdb_v2_option_handle option, duckdb_v2_str *out_setting, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_option_get_target_scope) - (duckdb_v2_option_handle option, DUCKDB_V2_OPTION_TARGET_SCOPE *out_target_scope, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_parse_sql) (duckdb_v2_connection_handle conn, const char *sql, duckdb_v2_statement_iterator_handle *out_iterator, duckdb_v2_error_info_handle *err); @@ -292,235 +251,120 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_statement_iterator_next) (duckdb_v2_statement_iterator_handle iterator, duckdb_v2_sql_statement_handle *out_statement, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_cast_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_cast_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, + DUCKDB_V2_ERROR(*duckdb_v2_value_cast) + (duckdb_v2_factory_handle factory, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_array_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_array) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_array_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bigint) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bignum) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bit_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool) + (duckdb_v2_factory_handle factory, bool in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_blob_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_date) + (duckdb_v2_factory_handle factory, int32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool_with_connection) - (duckdb_v2_connection_handle conn, bool in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_bool_with_context) - (duckdb_v2_context_handle ctx, bool in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_date_with_connection) - (duckdb_v2_connection_handle conn, int32_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_date_with_context) - (duckdb_v2_context_handle ctx, int32_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_decimal_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, uint8_t width, uint8_t scale, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_double_with_connection) - (duckdb_v2_connection_handle conn, double in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_double_with_context) - (duckdb_v2_context_handle ctx, double in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_float_with_connection) - (duckdb_v2_connection_handle conn, float in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_double) + (duckdb_v2_factory_handle factory, double in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_float_with_context) - (duckdb_v2_context_handle ctx, float in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_float) + (duckdb_v2_factory_handle factory, float in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_hugeint) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_int_with_connection) - (duckdb_v2_connection_handle conn, int32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_int) + (duckdb_v2_factory_handle factory, int32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_int_with_context) - (duckdb_v2_context_handle ctx, int32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval) + (duckdb_v2_factory_handle factory, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_interval_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_interval_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_list_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_list_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_list) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_map_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, - const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_map_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_map) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_value_create_null) - (duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_null_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_null_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint) + (duckdb_v2_factory_handle factory, int16_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint_with_connection) - (duckdb_v2_connection_handle conn, int16_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_smallint_with_context) - (duckdb_v2_context_handle ctx, int16_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, - idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_struct) + (duckdb_v2_factory_handle factory, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz_with_connection) - (duckdb_v2_connection_handle conn, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz_with_context) - (duckdb_v2_context_handle ctx, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_time_tz) + (duckdb_v2_factory_handle factory, uint64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ms) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_sec) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_with_connection) - (duckdb_v2_connection_handle conn, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_with_context) - (duckdb_v2_context_handle ctx, int64_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_timestamp_tz_ns) + (duckdb_v2_factory_handle factory, int64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint_with_connection) - (duckdb_v2_connection_handle conn, int8_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint) + (duckdb_v2_factory_handle factory, int8_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tinyint_with_context) - (duckdb_v2_context_handle ctx, int8_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_value_handle *children, idx_t field_count, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_tuple) + (duckdb_v2_factory_handle factory, const duckdb_v2_value_handle *children, idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_type_with_connection) - (duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_type_with_context) - (duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle in_type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint_with_connection) - (duckdb_v2_connection_handle conn, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint_with_context) - (duckdb_v2_context_handle ctx, uint64_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_type) + (duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle in_type, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_ubigint) + (duckdb_v2_factory_handle factory, uint64_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint_with_connection) - (duckdb_v2_connection_handle conn, uint32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uhugeint) + (duckdb_v2_factory_handle factory, const duckdb_v2_uhugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint_with_context) - (duckdb_v2_context_handle ctx, uint32_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uint) + (duckdb_v2_factory_handle factory, uint32_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint_with_connection) - (duckdb_v2_connection_handle conn, uint16_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint) + (duckdb_v2_factory_handle factory, uint16_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_usmallint_with_context) - (duckdb_v2_context_handle ctx, uint16_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint) + (duckdb_v2_factory_handle factory, uint8_t in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint_with_connection) - (duckdb_v2_connection_handle conn, uint8_t in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid) + (duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_utinyint_with_context) - (duckdb_v2_context_handle ctx, uint8_t in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_uuid_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_hugeint_t *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar_with_connection) - (duckdb_v2_connection_handle conn, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar_with_context) - (duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, + DUCKDB_V2_ERROR(*duckdb_v2_value_create_varchar) + (duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_value_destroy)(duckdb_v2_value_handle *value); DUCKDB_V2_ERROR(*duckdb_v2_value_get_bigint) @@ -628,20 +472,20 @@ typedef struct { (duckdb_v2_vector_handle vector, idx_t size, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_vector_set_value) (duckdb_v2_vector_handle vector, idx_t row, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_scalar_function) + (duckdb_v2_connection_handle conn, duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_context_log) (duckdb_v2_context_handle ctx, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_scalar_function) + (duckdb_v2_extension_handle extension, duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_function_signature_add_parameter) (duckdb_v2_function_signature_handle sig, const duckdb_v2_identifier_t *name, duckdb_v2_logical_type_handle type, duckdb_v2_value_handle value, DUCKDB_V2_FUNCTION_PARAMETER_KIND kind, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_function_signature_set_return_type) (duckdb_v2_function_signature_handle sig, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_scalar_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_scalar_function_destroy)(duckdb_v2_scalar_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_exec_get_arg) (duckdb_v2_scalar_function_exec_info_handle info, idx_t index, duckdb_v2_vector_handle *vector, @@ -669,8 +513,6 @@ typedef struct { (duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_init_set_init_data) (duckdb_v2_scalar_function_init_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_register) - (duckdb_v2_scalar_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_bind_callback) (duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -697,12 +539,8 @@ typedef struct { (duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_combine_get_user_data) (duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_aggregate_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_aggregate_function_destroy)(duckdb_v2_aggregate_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_destroy_get_bind_data) (duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); @@ -736,8 +574,6 @@ typedef struct { (duckdb_v2_aggregate_function_init_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_init_get_user_data) (duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_register) - (duckdb_v2_aggregate_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_set_bind_callback) (duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -784,23 +620,29 @@ typedef struct { (duckdb_v2_aggregate_function_update_info_handle info, void ***states, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_update_get_user_data) (duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_aggregate_function) + (duckdb_v2_connection_handle conn, duckdb_v2_aggregate_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_aggregate_function) + (duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle function, + duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_set_property) (duckdb_v2_aggregate_function_handle function, DUCKDB_V2_FUNCTION_PROPERTY_KEY key, DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_property) (duckdb_v2_scalar_function_handle function, DUCKDB_V2_FUNCTION_PROPERTY_KEY key, DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_table_function) + (duckdb_v2_connection_handle conn, duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_table_function) + (duckdb_v2_extension_handle extension, duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_bind_add_result_column) (duckdb_v2_table_function_bind_info_handle info, const duckdb_v2_identifier_t *name, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_bind_set_cardinality) (duckdb_v2_table_function_bind_info_handle info, idx_t cardinality, bool is_exact, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_table_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_table_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_table_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_table_function_destroy)(duckdb_v2_table_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_table_function_exec_get_bind_data) (duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); @@ -840,8 +682,6 @@ typedef struct { (duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_progress_set_progress) (duckdb_v2_table_function_progress_info_handle info, double progress, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_table_function_register) - (duckdb_v2_table_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_bind_callback) (duckdb_v2_table_function_handle function, duckdb_v2_table_function_bind_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -861,11 +701,8 @@ typedef struct { duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_user_data) (duckdb_v2_table_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_cast_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_cast_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_cast_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_cast_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_cast_function_destroy)(duckdb_v2_cast_function_handle *function); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_exec_get_input) (duckdb_v2_cast_function_exec_info_handle info, duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); @@ -877,8 +714,6 @@ typedef struct { (duckdb_v2_cast_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_exec_get_user_data) (duckdb_v2_cast_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_cast_function_register) - (duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_cast_function_set_exec_callback) (duckdb_v2_cast_function_handle function, duckdb_v2_cast_function_exec_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -894,17 +729,27 @@ typedef struct { (duckdb_v2_cast_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_column_data_collection_clear) (duckdb_v2_column_data_collection_handle collection, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_cast_function) + (duckdb_v2_connection_handle conn, duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_custom_type) + (duckdb_v2_connection_handle conn, duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_replacement_scan) + (duckdb_v2_connection_handle conn, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_file_system) + (duckdb_v2_context_handle context, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_type_create) + (duckdb_v2_factory_handle factory, duckdb_v2_custom_type_handle *type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_custom_type_destroy)(duckdb_v2_custom_type_handle *type); - DUCKDB_V2_ERROR(*duckdb_v2_custom_type_register) - (duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_custom_type_set_base_type) (duckdb_v2_custom_type_handle type, duckdb_v2_logical_type_handle base_type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_custom_type_set_name) (duckdb_v2_custom_type_handle type, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_cast_function) + (duckdb_v2_extension_handle extension, duckdb_v2_cast_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_custom_type) + (duckdb_v2_extension_handle extension, duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_replacement_scan) + (duckdb_v2_extension_handle extension, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_close)(duckdb_v2_file_handle file, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_destroy)(duckdb_v2_file_handle *file); DUCKDB_V2_ERROR(*duckdb_v2_file_open_options_create) @@ -924,11 +769,6 @@ typedef struct { (duckdb_v2_file_handle file, idx_t position, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_size)(duckdb_v2_file_handle file, idx_t *size, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_file_sync)(duckdb_v2_file_handle file, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_file_system_get_from_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_file_system_handle *file_system, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_file_system_get_from_context) - (duckdb_v2_context_handle context, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_file_system_open) (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *file_path, duckdb_v2_file_open_options_handle options, duckdb_v2_file_handle *file, duckdb_v2_error_info_handle *err); @@ -940,6 +780,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_file_write_at) (duckdb_v2_file_handle file, const void *buffer, idx_t buffer_size, idx_t location, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_register_replacement_scan) + (duckdb_v2_instance_handle instance, duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_qname_create) (const duckdb_v2_identifier_t *parts, idx_t part_count, duckdb_v2_qname_handle *name, duckdb_v2_error_info_handle *err); @@ -962,19 +804,13 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_add_named_argument) (duckdb_v2_replacement_scan_info_handle info, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create_with_instance) - (duckdb_v2_instance_handle instance, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_create) + (duckdb_v2_factory_handle factory, duckdb_v2_replacement_scan_handle *scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_replacement_scan_destroy)(duckdb_v2_replacement_scan_handle *scan); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_get_name) (duckdb_v2_replacement_scan_info_handle info, duckdb_v2_qname_handle *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_get_user_data) (duckdb_v2_replacement_scan_info_handle info, void **data, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_register) - (duckdb_v2_replacement_scan_handle scan, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_replacement_scan_set_alias) (duckdb_v2_replacement_scan_info_handle info, const duckdb_v2_identifier_t *alias, duckdb_v2_error_info_handle *err); @@ -1025,6 +861,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_connection_describe_table) (duckdb_v2_connection_handle conn, duckdb_v2_qname_handle name, duckdb_v2_table_description_handle *desc, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_copy_function) + (duckdb_v2_connection_handle conn, duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_from_bind_get_column_count) (duckdb_v2_copy_from_bind_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_from_bind_get_column_name) @@ -1098,14 +936,9 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_copy_from_set_progress_callback) (duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_progress_callback_fn callback, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_copy_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_copy_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_copy_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_copy_function_destroy)(duckdb_v2_copy_function_handle *function); - DUCKDB_V2_ERROR(*duckdb_v2_copy_function_register) - (duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_name) (duckdb_v2_copy_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_user_data) @@ -1208,6 +1041,8 @@ typedef struct { (duckdb_v2_expression_handle expression, duckdb_v2_logical_type_handle *type, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_expression_get_type) (duckdb_v2_expression_handle expression, DUCKDB_V2_EXPRESSION_TYPE *type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_copy_function) + (duckdb_v2_extension_handle extension, duckdb_v2_copy_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_identifier_render_quoted) (const duckdb_v2_identifier_t *name, char *out_text, idx_t out_capacity, idx_t *out_length, duckdb_v2_error_info_handle *err); @@ -1292,15 +1127,8 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_attach_options_set) (duckdb_v2_attach_options_handle options, const duckdb_v2_identifier_t *key, const duckdb_v2_str *setting, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_by_index) - (duckdb_v2_context_handle ctx, idx_t index, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_by_name) - (duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *name, duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_context_get_option_count) - (duckdb_v2_context_handle ctx, idx_t *out_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_instance_set_default) - (duckdb_v2_instance_handle instance, const duckdb_v2_str *path, duckdb_v2_error_info_handle *err); + (duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_partition_data_get_bind_data) (duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_table_function_partition_data_get_global_state) @@ -1397,9 +1225,187 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_set_resolve_types_callback) (duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_resolve_types_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_factory) + (duckdb_v2_connection_handle conn, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_factory) + (duckdb_v2_context_handle ctx, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_factory) + (duckdb_v2_instance_handle instance, duckdb_v2_factory_handle *out_factory, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_bind_set_bind_data) (duckdb_v2_scalar_function_bind_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_get_option_value) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle *out_value, + DUCKDB_V2_SETTING_SCOPE *out_scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_set_option_text) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, const duckdb_v2_str *setting, + DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_config_set_option_value) + (duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, duckdb_v2_value_handle value, + DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_config) + (duckdb_v2_connection_handle conn, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_get_file_system) + (duckdb_v2_connection_handle conn, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_log) + (duckdb_v2_connection_handle conn, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, + const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_context_get_config) + (duckdb_v2_context_handle ctx, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_config) + (duckdb_v2_instance_handle instance, duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_get_file_system) + (duckdb_v2_instance_handle instance, duckdb_v2_file_system_handle *file_system, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_log) + (duckdb_v2_instance_handle instance, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, + const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_get_default_scope) + (duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE *out_scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_option_supports_scope) + (duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, bool *out_supported, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_aggregate_function_get_docs) + (duckdb_v2_aggregate_function_handle function, duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_pragma_function) + (duckdb_v2_connection_handle conn, duckdb_v2_pragma_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_pragma_function) + (duckdb_v2_extension_handle extension, duckdb_v2_pragma_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_add_category) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *category, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_add_example) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *title, const duckdb_v2_str *example, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_function_docs_set_description) + (duckdb_v2_function_docs_handle docs, const duckdb_v2_str *description, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_pragma_function_handle *function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_pragma_function_destroy)(duckdb_v2_pragma_function_handle *function); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_get_docs) + (duckdb_v2_pragma_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_get_signature) + (duckdb_v2_pragma_function_handle function, duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_query_set_query) + (duckdb_v2_pragma_function_query_info_handle info, const duckdb_v2_str *query, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_exec_callback) + (duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_name) + (duckdb_v2_pragma_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_query_callback) + (duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_query_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_pragma_function_set_user_data) + (duckdb_v2_pragma_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_scalar_function_get_docs) + (duckdb_v2_scalar_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_table_function_get_docs) + (duckdb_v2_table_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_custom_option) + (duckdb_v2_connection_handle conn, duckdb_v2_custom_option_handle option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_create) + (duckdb_v2_factory_handle factory, duckdb_v2_custom_option_handle *option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_custom_option_destroy)(duckdb_v2_custom_option_handle *option); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_default_scope) + (duckdb_v2_custom_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_default_value) + (duckdb_v2_custom_option_handle option, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_description) + (duckdb_v2_custom_option_handle option, const duckdb_v2_str *description, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_name) + (duckdb_v2_custom_option_handle option, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_supports_scope) + (duckdb_v2_custom_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, bool supported, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_type) + (duckdb_v2_custom_option_handle option, duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_update_callback) + (duckdb_v2_custom_option_handle option, duckdb_v2_custom_option_update_callback_fn callback, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_set_user_data) + (duckdb_v2_custom_option_handle option, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_scope) + (duckdb_v2_custom_option_update_info_handle info, DUCKDB_V2_SETTING_SCOPE *scope, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_user_data) + (duckdb_v2_custom_option_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_get_value) + (duckdb_v2_custom_option_update_info_handle info, duckdb_v2_value_handle *value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_is_reset) + (duckdb_v2_custom_option_update_info_handle info, bool *is_reset, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_custom_option_update_set_value) + (duckdb_v2_custom_option_update_info_handle info, duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_custom_option) + (duckdb_v2_extension_handle extension, duckdb_v2_custom_option_handle option, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_log_sink) + (duckdb_v2_extension_handle extension, duckdb_v2_log_sink_handle sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_macro_function) + (duckdb_v2_extension_handle extension, duckdb_v2_macro_function_handle function, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_exists) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_exists, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_expand) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, duckdb_v2_text_sink_fn sink, void *user_data, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_is_remote) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_is_remote, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_instance_register_log_sink) + (duckdb_v2_instance_handle instance, duckdb_v2_log_sink_handle sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_create) + (duckdb_v2_factory_handle factory, duckdb_v2_log_sink_handle *sink, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_log_sink_destroy)(duckdb_v2_log_sink_handle *sink); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_flush_callback) + (duckdb_v2_log_sink_handle sink, duckdb_v2_log_sink_flush_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_name) + (duckdb_v2_log_sink_handle sink, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_user_data) + (duckdb_v2_log_sink_handle sink, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_set_write_callback) + (duckdb_v2_log_sink_handle sink, duckdb_v2_log_sink_write_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_connection_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_query_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_thread_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_log_sink_write_info_get_transaction_id) + (duckdb_v2_log_sink_write_info_handle info, idx_t *id, bool *present, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_create) + (duckdb_v2_factory_handle factory, DUCKDB_V2_MACRO_FUNCTION_TYPE type, duckdb_v2_macro_function_handle *function, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_macro_function_destroy)(duckdb_v2_macro_function_handle *function); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_get_docs) + (duckdb_v2_macro_function_handle function, duckdb_v2_function_docs_handle *docs, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_get_signature) + (duckdb_v2_macro_function_handle function, duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_set_body) + (duckdb_v2_macro_function_handle function, const duckdb_v2_str *body, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_macro_function_set_name) + (duckdb_v2_macro_function_handle function, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_vector_cast) + (duckdb_v2_factory_handle factory, duckdb_v2_vector_handle source, duckdb_v2_vector_handle target, + DUCKDB_V2_CAST_MODE mode, bool *out_converted, duckdb_v2_error_info_handle *cast_err, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_copy_function_set_file_extension) + (duckdb_v2_copy_function_handle function, const duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR (*duckdb_v2_data_chunk_clear)(duckdb_v2_data_chunk_handle chunk, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_glob) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *pattern, + duckdb_v2_file_system_path_glob_callback_fn callback, void *user_data, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_move) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *source, const duckdb_v2_str *target, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_file_system_path_remove) + (duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, bool *out_removed, + duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_statement_add_collection) + (duckdb_v2_sql_statement_handle statement, const duckdb_v2_identifier_t *name, + duckdb_v2_column_data_collection_handle collection, const duckdb_v2_identifier_t *column_names, idx_t column_count, + duckdb_v2_error_info_handle *err); #if DUCKDB_V2_API_ALLOW_UNSTABLE + DUCKDB_V2_ERROR(*duckdb_v2_connection_register_multi_file_function) + (duckdb_v2_connection_handle conn, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_copy_to_set_statistics_callback) (duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_statistics_callback_fn callback, duckdb_v2_error_info_handle *err); @@ -1415,15 +1421,13 @@ typedef struct { (duckdb_v2_copy_to_statistics_info_handle info, idx_t row_count, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_data_chunk_get_capacity) (duckdb_v2_data_chunk_handle chunk, idx_t *out_capacity, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create_with_connection) - (duckdb_v2_connection_handle connection, duckdb_v2_multi_file_function_handle *function, + DUCKDB_V2_ERROR(*duckdb_v2_extension_register_multi_file_function) + (duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create_with_extension) - (duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle *function, + DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_create) + (duckdb_v2_factory_handle factory, duckdb_v2_multi_file_function_handle *function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR (*duckdb_v2_multi_file_function_destroy)(duckdb_v2_multi_file_function_handle *function); - DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_register) - (duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_set_file_extension) (duckdb_v2_multi_file_function_handle function, duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); DUCKDB_V2_ERROR(*duckdb_v2_multi_file_function_set_name) @@ -1458,6 +1462,9 @@ typedef struct { DUCKDB_V2_ERROR(*duckdb_v2_table_function_set_get_bind_info_callback) (duckdb_v2_table_function_handle function, duckdb_v2_table_function_get_bind_info_callback_fn callback, duckdb_v2_error_info_handle *err); + DUCKDB_V2_ERROR(*duckdb_v2_factory_create_common_type) + (duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types, idx_t type_count, + duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); #endif // capigen:end appended } duckdb_ext_api_v2; @@ -1477,11 +1484,8 @@ typedef struct { duckdb_ext_api.duckdb_v2_column_data_collection_append_state_create #define duckdb_v2_column_data_collection_append_state_destroy \ duckdb_ext_api.duckdb_v2_column_data_collection_append_state_destroy -#define duckdb_v2_column_data_collection_combine duckdb_ext_api.duckdb_v2_column_data_collection_combine -#define duckdb_v2_column_data_collection_create_with_connection \ - duckdb_ext_api.duckdb_v2_column_data_collection_create_with_connection -#define duckdb_v2_column_data_collection_create_with_context \ - duckdb_ext_api.duckdb_v2_column_data_collection_create_with_context +#define duckdb_v2_column_data_collection_combine duckdb_ext_api.duckdb_v2_column_data_collection_combine +#define duckdb_v2_column_data_collection_create duckdb_ext_api.duckdb_v2_column_data_collection_create #define duckdb_v2_column_data_collection_destroy duckdb_ext_api.duckdb_v2_column_data_collection_destroy #define duckdb_v2_column_data_collection_reset duckdb_ext_api.duckdb_v2_column_data_collection_reset #define duckdb_v2_column_data_collection_row_count duckdb_ext_api.duckdb_v2_column_data_collection_row_count @@ -1494,243 +1498,184 @@ typedef struct { duckdb_ext_api.duckdb_v2_column_data_collection_worker_scan_state_create #define duckdb_v2_column_data_collection_worker_scan_state_destroy \ duckdb_ext_api.duckdb_v2_column_data_collection_worker_scan_state_destroy -#define duckdb_v2_connection_create duckdb_ext_api.duckdb_v2_connection_create -#define duckdb_v2_connection_create_type_from_id duckdb_ext_api.duckdb_v2_connection_create_type_from_id -#define duckdb_v2_connection_create_type_from_name duckdb_ext_api.duckdb_v2_connection_create_type_from_name -#define duckdb_v2_connection_create_type_from_text duckdb_ext_api.duckdb_v2_connection_create_type_from_text -#define duckdb_v2_connection_create_type_with_alias duckdb_ext_api.duckdb_v2_connection_create_type_with_alias -#define duckdb_v2_connection_destroy duckdb_ext_api.duckdb_v2_connection_destroy -#define duckdb_v2_connection_get_option_by_index duckdb_ext_api.duckdb_v2_connection_get_option_by_index -#define duckdb_v2_connection_get_option_by_name duckdb_ext_api.duckdb_v2_connection_get_option_by_name -#define duckdb_v2_connection_get_option_count duckdb_ext_api.duckdb_v2_connection_get_option_count -#define duckdb_v2_connection_interrupt duckdb_ext_api.duckdb_v2_connection_interrupt -#define duckdb_v2_connection_progress_get duckdb_ext_api.duckdb_v2_connection_progress_get -#define duckdb_v2_connection_set_option duckdb_ext_api.duckdb_v2_connection_set_option -#define duckdb_v2_context_create_type_from_id duckdb_ext_api.duckdb_v2_context_create_type_from_id -#define duckdb_v2_context_create_type_from_name duckdb_ext_api.duckdb_v2_context_create_type_from_name -#define duckdb_v2_context_create_type_from_text duckdb_ext_api.duckdb_v2_context_create_type_from_text -#define duckdb_v2_context_create_type_with_alias duckdb_ext_api.duckdb_v2_context_create_type_with_alias -#define duckdb_v2_data_chunk_copy_with_connection duckdb_ext_api.duckdb_v2_data_chunk_copy_with_connection -#define duckdb_v2_data_chunk_copy_with_context duckdb_ext_api.duckdb_v2_data_chunk_copy_with_context -#define duckdb_v2_data_chunk_create duckdb_ext_api.duckdb_v2_data_chunk_create -#define duckdb_v2_data_chunk_create_with_connection duckdb_ext_api.duckdb_v2_data_chunk_create_with_connection -#define duckdb_v2_data_chunk_create_with_context duckdb_ext_api.duckdb_v2_data_chunk_create_with_context -#define duckdb_v2_data_chunk_destroy duckdb_ext_api.duckdb_v2_data_chunk_destroy -#define duckdb_v2_data_chunk_get_size duckdb_ext_api.duckdb_v2_data_chunk_get_size -#define duckdb_v2_data_chunk_get_vector duckdb_ext_api.duckdb_v2_data_chunk_get_vector -#define duckdb_v2_data_chunk_get_vector_count duckdb_ext_api.duckdb_v2_data_chunk_get_vector_count -#define duckdb_v2_environment_create duckdb_ext_api.duckdb_v2_environment_create -#define duckdb_v2_environment_destroy duckdb_ext_api.duckdb_v2_environment_destroy -#define duckdb_v2_environment_get_instance_count duckdb_ext_api.duckdb_v2_environment_get_instance_count -#define duckdb_v2_error_info_destroy duckdb_ext_api.duckdb_v2_error_info_destroy -#define duckdb_v2_error_info_get_code duckdb_ext_api.duckdb_v2_error_info_get_code -#define duckdb_v2_error_info_get_raw_text duckdb_ext_api.duckdb_v2_error_info_get_raw_text -#define duckdb_v2_error_info_get_text duckdb_ext_api.duckdb_v2_error_info_get_text -#define duckdb_v2_error_info_set_code duckdb_ext_api.duckdb_v2_error_info_set_code -#define duckdb_v2_error_info_set_text duckdb_ext_api.duckdb_v2_error_info_set_text -#define duckdb_v2_instance_attach duckdb_ext_api.duckdb_v2_instance_attach -#define duckdb_v2_instance_create duckdb_ext_api.duckdb_v2_instance_create -#define duckdb_v2_instance_destroy duckdb_ext_api.duckdb_v2_instance_destroy -#define duckdb_v2_instance_detach duckdb_ext_api.duckdb_v2_instance_detach -#define duckdb_v2_instance_get_option_by_index duckdb_ext_api.duckdb_v2_instance_get_option_by_index -#define duckdb_v2_instance_get_option_by_name duckdb_ext_api.duckdb_v2_instance_get_option_by_name -#define duckdb_v2_instance_get_option_count duckdb_ext_api.duckdb_v2_instance_get_option_count -#define duckdb_v2_instance_set_option duckdb_ext_api.duckdb_v2_instance_set_option -#define duckdb_v2_library_version duckdb_ext_api.duckdb_v2_library_version -#define duckdb_v2_logical_type_copy duckdb_ext_api.duckdb_v2_logical_type_copy -#define duckdb_v2_logical_type_destroy duckdb_ext_api.duckdb_v2_logical_type_destroy -#define duckdb_v2_logical_type_get_id duckdb_ext_api.duckdb_v2_logical_type_get_id -#define duckdb_v2_logical_type_get_name duckdb_ext_api.duckdb_v2_logical_type_get_name -#define duckdb_v2_logical_type_get_param duckdb_ext_api.duckdb_v2_logical_type_get_param -#define duckdb_v2_logical_type_get_param_count duckdb_ext_api.duckdb_v2_logical_type_get_param_count -#define duckdb_v2_logical_type_is_equal duckdb_ext_api.duckdb_v2_logical_type_is_equal -#define duckdb_v2_logical_type_to_text duckdb_ext_api.duckdb_v2_logical_type_to_text -#define duckdb_v2_option_destroy duckdb_ext_api.duckdb_v2_option_destroy -#define duckdb_v2_option_get_alias duckdb_ext_api.duckdb_v2_option_get_alias -#define duckdb_v2_option_get_alias_count duckdb_ext_api.duckdb_v2_option_get_alias_count -#define duckdb_v2_option_get_default_setting duckdb_ext_api.duckdb_v2_option_get_default_setting -#define duckdb_v2_option_get_description duckdb_ext_api.duckdb_v2_option_get_description -#define duckdb_v2_option_get_name duckdb_ext_api.duckdb_v2_option_get_name -#define duckdb_v2_option_get_setting duckdb_ext_api.duckdb_v2_option_get_setting -#define duckdb_v2_option_get_target_scope duckdb_ext_api.duckdb_v2_option_get_target_scope -#define duckdb_v2_parse_sql duckdb_ext_api.duckdb_v2_parse_sql -#define duckdb_v2_result_destroy duckdb_ext_api.duckdb_v2_result_destroy -#define duckdb_v2_result_drain duckdb_ext_api.duckdb_v2_result_drain -#define duckdb_v2_result_fetch_chunk duckdb_ext_api.duckdb_v2_result_fetch_chunk -#define duckdb_v2_result_get_result_type duckdb_ext_api.duckdb_v2_result_get_result_type -#define duckdb_v2_result_get_schema duckdb_ext_api.duckdb_v2_result_get_schema -#define duckdb_v2_result_get_statement_type duckdb_ext_api.duckdb_v2_result_get_statement_type -#define duckdb_v2_result_render_box duckdb_ext_api.duckdb_v2_result_render_box -#define duckdb_v2_result_step duckdb_ext_api.duckdb_v2_result_step -#define duckdb_v2_result_wait duckdb_ext_api.duckdb_v2_result_wait -#define duckdb_v2_schema_destroy duckdb_ext_api.duckdb_v2_schema_destroy -#define duckdb_v2_schema_get_count duckdb_ext_api.duckdb_v2_schema_get_count -#define duckdb_v2_schema_get_field duckdb_ext_api.duckdb_v2_schema_get_field -#define duckdb_v2_sql_statement_destroy duckdb_ext_api.duckdb_v2_sql_statement_destroy -#define duckdb_v2_statement_bind duckdb_ext_api.duckdb_v2_statement_bind -#define duckdb_v2_statement_execute duckdb_ext_api.duckdb_v2_statement_execute -#define duckdb_v2_statement_iterator_destroy duckdb_ext_api.duckdb_v2_statement_iterator_destroy -#define duckdb_v2_statement_iterator_next duckdb_ext_api.duckdb_v2_statement_iterator_next -#define duckdb_v2_value_cast_with_connection duckdb_ext_api.duckdb_v2_value_cast_with_connection -#define duckdb_v2_value_cast_with_context duckdb_ext_api.duckdb_v2_value_cast_with_context -#define duckdb_v2_value_create_array_with_connection duckdb_ext_api.duckdb_v2_value_create_array_with_connection -#define duckdb_v2_value_create_array_with_context duckdb_ext_api.duckdb_v2_value_create_array_with_context -#define duckdb_v2_value_create_bigint_with_connection duckdb_ext_api.duckdb_v2_value_create_bigint_with_connection -#define duckdb_v2_value_create_bigint_with_context duckdb_ext_api.duckdb_v2_value_create_bigint_with_context -#define duckdb_v2_value_create_bignum_with_connection duckdb_ext_api.duckdb_v2_value_create_bignum_with_connection -#define duckdb_v2_value_create_bignum_with_context duckdb_ext_api.duckdb_v2_value_create_bignum_with_context -#define duckdb_v2_value_create_bit_with_connection duckdb_ext_api.duckdb_v2_value_create_bit_with_connection -#define duckdb_v2_value_create_bit_with_context duckdb_ext_api.duckdb_v2_value_create_bit_with_context -#define duckdb_v2_value_create_blob_with_connection duckdb_ext_api.duckdb_v2_value_create_blob_with_connection -#define duckdb_v2_value_create_blob_with_context duckdb_ext_api.duckdb_v2_value_create_blob_with_context -#define duckdb_v2_value_create_bool_with_connection duckdb_ext_api.duckdb_v2_value_create_bool_with_connection -#define duckdb_v2_value_create_bool_with_context duckdb_ext_api.duckdb_v2_value_create_bool_with_context -#define duckdb_v2_value_create_date_with_connection duckdb_ext_api.duckdb_v2_value_create_date_with_connection -#define duckdb_v2_value_create_date_with_context duckdb_ext_api.duckdb_v2_value_create_date_with_context -#define duckdb_v2_value_create_decimal_with_connection duckdb_ext_api.duckdb_v2_value_create_decimal_with_connection -#define duckdb_v2_value_create_decimal_with_context duckdb_ext_api.duckdb_v2_value_create_decimal_with_context -#define duckdb_v2_value_create_double_with_connection duckdb_ext_api.duckdb_v2_value_create_double_with_connection -#define duckdb_v2_value_create_double_with_context duckdb_ext_api.duckdb_v2_value_create_double_with_context -#define duckdb_v2_value_create_float_with_connection duckdb_ext_api.duckdb_v2_value_create_float_with_connection -#define duckdb_v2_value_create_float_with_context duckdb_ext_api.duckdb_v2_value_create_float_with_context -#define duckdb_v2_value_create_hugeint_with_connection duckdb_ext_api.duckdb_v2_value_create_hugeint_with_connection -#define duckdb_v2_value_create_hugeint_with_context duckdb_ext_api.duckdb_v2_value_create_hugeint_with_context -#define duckdb_v2_value_create_int_with_connection duckdb_ext_api.duckdb_v2_value_create_int_with_connection -#define duckdb_v2_value_create_int_with_context duckdb_ext_api.duckdb_v2_value_create_int_with_context -#define duckdb_v2_value_create_interval_with_connection duckdb_ext_api.duckdb_v2_value_create_interval_with_connection -#define duckdb_v2_value_create_interval_with_context duckdb_ext_api.duckdb_v2_value_create_interval_with_context -#define duckdb_v2_value_create_list_with_connection duckdb_ext_api.duckdb_v2_value_create_list_with_connection -#define duckdb_v2_value_create_list_with_context duckdb_ext_api.duckdb_v2_value_create_list_with_context -#define duckdb_v2_value_create_map_with_connection duckdb_ext_api.duckdb_v2_value_create_map_with_connection -#define duckdb_v2_value_create_map_with_context duckdb_ext_api.duckdb_v2_value_create_map_with_context -#define duckdb_v2_value_create_null duckdb_ext_api.duckdb_v2_value_create_null -#define duckdb_v2_value_create_null_with_connection duckdb_ext_api.duckdb_v2_value_create_null_with_connection -#define duckdb_v2_value_create_null_with_context duckdb_ext_api.duckdb_v2_value_create_null_with_context -#define duckdb_v2_value_create_smallint_with_connection duckdb_ext_api.duckdb_v2_value_create_smallint_with_connection -#define duckdb_v2_value_create_smallint_with_context duckdb_ext_api.duckdb_v2_value_create_smallint_with_context -#define duckdb_v2_value_create_struct_with_connection duckdb_ext_api.duckdb_v2_value_create_struct_with_connection -#define duckdb_v2_value_create_struct_with_context duckdb_ext_api.duckdb_v2_value_create_struct_with_context -#define duckdb_v2_value_create_time_ns_with_connection duckdb_ext_api.duckdb_v2_value_create_time_ns_with_connection -#define duckdb_v2_value_create_time_ns_with_context duckdb_ext_api.duckdb_v2_value_create_time_ns_with_context -#define duckdb_v2_value_create_time_tz_with_connection duckdb_ext_api.duckdb_v2_value_create_time_tz_with_connection -#define duckdb_v2_value_create_time_tz_with_context duckdb_ext_api.duckdb_v2_value_create_time_tz_with_context -#define duckdb_v2_value_create_time_with_connection duckdb_ext_api.duckdb_v2_value_create_time_with_connection -#define duckdb_v2_value_create_time_with_context duckdb_ext_api.duckdb_v2_value_create_time_with_context -#define duckdb_v2_value_create_timestamp_ms_with_connection \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_ms_with_connection -#define duckdb_v2_value_create_timestamp_ms_with_context duckdb_ext_api.duckdb_v2_value_create_timestamp_ms_with_context -#define duckdb_v2_value_create_timestamp_ns_with_connection \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_ns_with_connection -#define duckdb_v2_value_create_timestamp_ns_with_context duckdb_ext_api.duckdb_v2_value_create_timestamp_ns_with_context -#define duckdb_v2_value_create_timestamp_sec_with_connection \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_sec_with_connection -#define duckdb_v2_value_create_timestamp_sec_with_context \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_sec_with_context -#define duckdb_v2_value_create_timestamp_tz_ns_with_connection \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_tz_ns_with_connection -#define duckdb_v2_value_create_timestamp_tz_ns_with_context \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_tz_ns_with_context -#define duckdb_v2_value_create_timestamp_tz_with_connection \ - duckdb_ext_api.duckdb_v2_value_create_timestamp_tz_with_connection -#define duckdb_v2_value_create_timestamp_tz_with_context duckdb_ext_api.duckdb_v2_value_create_timestamp_tz_with_context -#define duckdb_v2_value_create_timestamp_with_connection duckdb_ext_api.duckdb_v2_value_create_timestamp_with_connection -#define duckdb_v2_value_create_timestamp_with_context duckdb_ext_api.duckdb_v2_value_create_timestamp_with_context -#define duckdb_v2_value_create_tinyint_with_connection duckdb_ext_api.duckdb_v2_value_create_tinyint_with_connection -#define duckdb_v2_value_create_tinyint_with_context duckdb_ext_api.duckdb_v2_value_create_tinyint_with_context -#define duckdb_v2_value_create_tuple_with_connection duckdb_ext_api.duckdb_v2_value_create_tuple_with_connection -#define duckdb_v2_value_create_tuple_with_context duckdb_ext_api.duckdb_v2_value_create_tuple_with_context -#define duckdb_v2_value_create_type_with_connection duckdb_ext_api.duckdb_v2_value_create_type_with_connection -#define duckdb_v2_value_create_type_with_context duckdb_ext_api.duckdb_v2_value_create_type_with_context -#define duckdb_v2_value_create_ubigint_with_connection duckdb_ext_api.duckdb_v2_value_create_ubigint_with_connection -#define duckdb_v2_value_create_ubigint_with_context duckdb_ext_api.duckdb_v2_value_create_ubigint_with_context -#define duckdb_v2_value_create_uhugeint_with_connection duckdb_ext_api.duckdb_v2_value_create_uhugeint_with_connection -#define duckdb_v2_value_create_uhugeint_with_context duckdb_ext_api.duckdb_v2_value_create_uhugeint_with_context -#define duckdb_v2_value_create_uint_with_connection duckdb_ext_api.duckdb_v2_value_create_uint_with_connection -#define duckdb_v2_value_create_uint_with_context duckdb_ext_api.duckdb_v2_value_create_uint_with_context -#define duckdb_v2_value_create_usmallint_with_connection duckdb_ext_api.duckdb_v2_value_create_usmallint_with_connection -#define duckdb_v2_value_create_usmallint_with_context duckdb_ext_api.duckdb_v2_value_create_usmallint_with_context -#define duckdb_v2_value_create_utinyint_with_connection duckdb_ext_api.duckdb_v2_value_create_utinyint_with_connection -#define duckdb_v2_value_create_utinyint_with_context duckdb_ext_api.duckdb_v2_value_create_utinyint_with_context -#define duckdb_v2_value_create_uuid_with_connection duckdb_ext_api.duckdb_v2_value_create_uuid_with_connection -#define duckdb_v2_value_create_uuid_with_context duckdb_ext_api.duckdb_v2_value_create_uuid_with_context -#define duckdb_v2_value_create_varchar_with_connection duckdb_ext_api.duckdb_v2_value_create_varchar_with_connection -#define duckdb_v2_value_create_varchar_with_context duckdb_ext_api.duckdb_v2_value_create_varchar_with_context -#define duckdb_v2_value_destroy duckdb_ext_api.duckdb_v2_value_destroy -#define duckdb_v2_value_get_bigint duckdb_ext_api.duckdb_v2_value_get_bigint -#define duckdb_v2_value_get_blob duckdb_ext_api.duckdb_v2_value_get_blob -#define duckdb_v2_value_get_bool duckdb_ext_api.duckdb_v2_value_get_bool -#define duckdb_v2_value_get_child duckdb_ext_api.duckdb_v2_value_get_child -#define duckdb_v2_value_get_child_count duckdb_ext_api.duckdb_v2_value_get_child_count -#define duckdb_v2_value_get_date duckdb_ext_api.duckdb_v2_value_get_date -#define duckdb_v2_value_get_decimal duckdb_ext_api.duckdb_v2_value_get_decimal -#define duckdb_v2_value_get_double duckdb_ext_api.duckdb_v2_value_get_double -#define duckdb_v2_value_get_float duckdb_ext_api.duckdb_v2_value_get_float -#define duckdb_v2_value_get_hugeint duckdb_ext_api.duckdb_v2_value_get_hugeint -#define duckdb_v2_value_get_int duckdb_ext_api.duckdb_v2_value_get_int -#define duckdb_v2_value_get_interval duckdb_ext_api.duckdb_v2_value_get_interval -#define duckdb_v2_value_get_logical_type duckdb_ext_api.duckdb_v2_value_get_logical_type -#define duckdb_v2_value_get_smallint duckdb_ext_api.duckdb_v2_value_get_smallint -#define duckdb_v2_value_get_time duckdb_ext_api.duckdb_v2_value_get_time -#define duckdb_v2_value_get_time_ns duckdb_ext_api.duckdb_v2_value_get_time_ns -#define duckdb_v2_value_get_time_tz duckdb_ext_api.duckdb_v2_value_get_time_tz -#define duckdb_v2_value_get_timestamp duckdb_ext_api.duckdb_v2_value_get_timestamp -#define duckdb_v2_value_get_timestamp_ms duckdb_ext_api.duckdb_v2_value_get_timestamp_ms -#define duckdb_v2_value_get_timestamp_ns duckdb_ext_api.duckdb_v2_value_get_timestamp_ns -#define duckdb_v2_value_get_timestamp_sec duckdb_ext_api.duckdb_v2_value_get_timestamp_sec -#define duckdb_v2_value_get_timestamp_tz duckdb_ext_api.duckdb_v2_value_get_timestamp_tz -#define duckdb_v2_value_get_timestamp_tz_ns duckdb_ext_api.duckdb_v2_value_get_timestamp_tz_ns -#define duckdb_v2_value_get_tinyint duckdb_ext_api.duckdb_v2_value_get_tinyint -#define duckdb_v2_value_get_type duckdb_ext_api.duckdb_v2_value_get_type -#define duckdb_v2_value_get_ubigint duckdb_ext_api.duckdb_v2_value_get_ubigint -#define duckdb_v2_value_get_uhugeint duckdb_ext_api.duckdb_v2_value_get_uhugeint -#define duckdb_v2_value_get_uint duckdb_ext_api.duckdb_v2_value_get_uint -#define duckdb_v2_value_get_usmallint duckdb_ext_api.duckdb_v2_value_get_usmallint -#define duckdb_v2_value_get_utinyint duckdb_ext_api.duckdb_v2_value_get_utinyint -#define duckdb_v2_value_get_uuid duckdb_ext_api.duckdb_v2_value_get_uuid -#define duckdb_v2_value_get_varchar duckdb_ext_api.duckdb_v2_value_get_varchar -#define duckdb_v2_value_is_null duckdb_ext_api.duckdb_v2_value_is_null -#define duckdb_v2_value_to_string duckdb_ext_api.duckdb_v2_value_to_string -#define duckdb_v2_vector_constant_set_valid duckdb_ext_api.duckdb_v2_vector_constant_set_valid -#define duckdb_v2_vector_flat_get_validity_mutable duckdb_ext_api.duckdb_v2_vector_flat_get_validity_mutable -#define duckdb_v2_vector_flatten duckdb_ext_api.duckdb_v2_vector_flatten -#define duckdb_v2_vector_get_arena duckdb_ext_api.duckdb_v2_vector_get_arena -#define duckdb_v2_vector_get_child duckdb_ext_api.duckdb_v2_vector_get_child -#define duckdb_v2_vector_get_child_count duckdb_ext_api.duckdb_v2_vector_get_child_count -#define duckdb_v2_vector_get_data_mutable duckdb_ext_api.duckdb_v2_vector_get_data_mutable -#define duckdb_v2_vector_get_logical_type duckdb_ext_api.duckdb_v2_vector_get_logical_type -#define duckdb_v2_vector_get_size duckdb_ext_api.duckdb_v2_vector_get_size -#define duckdb_v2_vector_get_value duckdb_ext_api.duckdb_v2_vector_get_value -#define duckdb_v2_vector_get_vector_type duckdb_ext_api.duckdb_v2_vector_get_vector_type -#define duckdb_v2_vector_get_view duckdb_ext_api.duckdb_v2_vector_get_view -#define duckdb_v2_vector_make_constant duckdb_ext_api.duckdb_v2_vector_make_constant -#define duckdb_v2_vector_make_sequence duckdb_ext_api.duckdb_v2_vector_make_sequence -#define duckdb_v2_vector_reference duckdb_ext_api.duckdb_v2_vector_reference -#define duckdb_v2_vector_set_null duckdb_ext_api.duckdb_v2_vector_set_null -#define duckdb_v2_vector_set_size duckdb_ext_api.duckdb_v2_vector_set_size -#define duckdb_v2_vector_set_value duckdb_ext_api.duckdb_v2_vector_set_value -#define duckdb_v2_context_log duckdb_ext_api.duckdb_v2_context_log -#define duckdb_v2_function_signature_add_parameter duckdb_ext_api.duckdb_v2_function_signature_add_parameter -#define duckdb_v2_function_signature_set_return_type duckdb_ext_api.duckdb_v2_function_signature_set_return_type -#define duckdb_v2_scalar_function_create_with_connection duckdb_ext_api.duckdb_v2_scalar_function_create_with_connection -#define duckdb_v2_scalar_function_create_with_extension duckdb_ext_api.duckdb_v2_scalar_function_create_with_extension -#define duckdb_v2_scalar_function_destroy duckdb_ext_api.duckdb_v2_scalar_function_destroy -#define duckdb_v2_scalar_function_exec_get_arg duckdb_ext_api.duckdb_v2_scalar_function_exec_get_arg -#define duckdb_v2_scalar_function_exec_get_arg_count duckdb_ext_api.duckdb_v2_scalar_function_exec_get_arg_count -#define duckdb_v2_scalar_function_exec_get_bind_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_bind_data -#define duckdb_v2_scalar_function_exec_get_init_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_init_data -#define duckdb_v2_scalar_function_exec_get_result duckdb_ext_api.duckdb_v2_scalar_function_exec_get_result -#define duckdb_v2_scalar_function_exec_get_row_count duckdb_ext_api.duckdb_v2_scalar_function_exec_get_row_count -#define duckdb_v2_scalar_function_exec_get_user_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_user_data -#define duckdb_v2_scalar_function_get_signature duckdb_ext_api.duckdb_v2_scalar_function_get_signature -#define duckdb_v2_scalar_function_init_get_bind_data duckdb_ext_api.duckdb_v2_scalar_function_init_get_bind_data -#define duckdb_v2_scalar_function_init_get_user_data duckdb_ext_api.duckdb_v2_scalar_function_init_get_user_data -#define duckdb_v2_scalar_function_init_set_init_data duckdb_ext_api.duckdb_v2_scalar_function_init_set_init_data -#define duckdb_v2_scalar_function_register duckdb_ext_api.duckdb_v2_scalar_function_register -#define duckdb_v2_scalar_function_set_bind_callback duckdb_ext_api.duckdb_v2_scalar_function_set_bind_callback -#define duckdb_v2_scalar_function_set_exec_callback duckdb_ext_api.duckdb_v2_scalar_function_set_exec_callback -#define duckdb_v2_scalar_function_set_init_callback duckdb_ext_api.duckdb_v2_scalar_function_set_init_callback -#define duckdb_v2_scalar_function_set_name duckdb_ext_api.duckdb_v2_scalar_function_set_name -#define duckdb_v2_scalar_function_set_user_data duckdb_ext_api.duckdb_v2_scalar_function_set_user_data +#define duckdb_v2_config_get_option_by_index duckdb_ext_api.duckdb_v2_config_get_option_by_index +#define duckdb_v2_config_get_option_by_name duckdb_ext_api.duckdb_v2_config_get_option_by_name +#define duckdb_v2_config_get_option_count duckdb_ext_api.duckdb_v2_config_get_option_count +#define duckdb_v2_connection_create duckdb_ext_api.duckdb_v2_connection_create +#define duckdb_v2_connection_destroy duckdb_ext_api.duckdb_v2_connection_destroy +#define duckdb_v2_connection_interrupt duckdb_ext_api.duckdb_v2_connection_interrupt +#define duckdb_v2_connection_progress_get duckdb_ext_api.duckdb_v2_connection_progress_get +#define duckdb_v2_data_chunk_copy duckdb_ext_api.duckdb_v2_data_chunk_copy +#define duckdb_v2_data_chunk_create duckdb_ext_api.duckdb_v2_data_chunk_create +#define duckdb_v2_data_chunk_destroy duckdb_ext_api.duckdb_v2_data_chunk_destroy +#define duckdb_v2_data_chunk_get_size duckdb_ext_api.duckdb_v2_data_chunk_get_size +#define duckdb_v2_data_chunk_get_vector duckdb_ext_api.duckdb_v2_data_chunk_get_vector +#define duckdb_v2_data_chunk_get_vector_count duckdb_ext_api.duckdb_v2_data_chunk_get_vector_count +#define duckdb_v2_environment_create duckdb_ext_api.duckdb_v2_environment_create +#define duckdb_v2_environment_destroy duckdb_ext_api.duckdb_v2_environment_destroy +#define duckdb_v2_environment_get_instance_count duckdb_ext_api.duckdb_v2_environment_get_instance_count +#define duckdb_v2_error_info_destroy duckdb_ext_api.duckdb_v2_error_info_destroy +#define duckdb_v2_error_info_get_code duckdb_ext_api.duckdb_v2_error_info_get_code +#define duckdb_v2_error_info_get_raw_text duckdb_ext_api.duckdb_v2_error_info_get_raw_text +#define duckdb_v2_error_info_get_text duckdb_ext_api.duckdb_v2_error_info_get_text +#define duckdb_v2_error_info_set_code duckdb_ext_api.duckdb_v2_error_info_set_code +#define duckdb_v2_error_info_set_text duckdb_ext_api.duckdb_v2_error_info_set_text +#define duckdb_v2_factory_create_type_from_id duckdb_ext_api.duckdb_v2_factory_create_type_from_id +#define duckdb_v2_factory_create_type_from_name duckdb_ext_api.duckdb_v2_factory_create_type_from_name +#define duckdb_v2_factory_create_type_from_text duckdb_ext_api.duckdb_v2_factory_create_type_from_text +#define duckdb_v2_factory_create_type_with_alias duckdb_ext_api.duckdb_v2_factory_create_type_with_alias +#define duckdb_v2_instance_attach duckdb_ext_api.duckdb_v2_instance_attach +#define duckdb_v2_instance_create duckdb_ext_api.duckdb_v2_instance_create +#define duckdb_v2_instance_create_with_options duckdb_ext_api.duckdb_v2_instance_create_with_options +#define duckdb_v2_instance_destroy duckdb_ext_api.duckdb_v2_instance_destroy +#define duckdb_v2_instance_detach duckdb_ext_api.duckdb_v2_instance_detach +#define duckdb_v2_library_version duckdb_ext_api.duckdb_v2_library_version +#define duckdb_v2_logical_type_copy duckdb_ext_api.duckdb_v2_logical_type_copy +#define duckdb_v2_logical_type_destroy duckdb_ext_api.duckdb_v2_logical_type_destroy +#define duckdb_v2_logical_type_get_id duckdb_ext_api.duckdb_v2_logical_type_get_id +#define duckdb_v2_logical_type_get_name duckdb_ext_api.duckdb_v2_logical_type_get_name +#define duckdb_v2_logical_type_get_param duckdb_ext_api.duckdb_v2_logical_type_get_param +#define duckdb_v2_logical_type_get_param_count duckdb_ext_api.duckdb_v2_logical_type_get_param_count +#define duckdb_v2_logical_type_is_equal duckdb_ext_api.duckdb_v2_logical_type_is_equal +#define duckdb_v2_logical_type_to_text duckdb_ext_api.duckdb_v2_logical_type_to_text +#define duckdb_v2_option_destroy duckdb_ext_api.duckdb_v2_option_destroy +#define duckdb_v2_option_get_alias duckdb_ext_api.duckdb_v2_option_get_alias +#define duckdb_v2_option_get_alias_count duckdb_ext_api.duckdb_v2_option_get_alias_count +#define duckdb_v2_option_get_default_value duckdb_ext_api.duckdb_v2_option_get_default_value +#define duckdb_v2_option_get_description duckdb_ext_api.duckdb_v2_option_get_description +#define duckdb_v2_option_get_name duckdb_ext_api.duckdb_v2_option_get_name +#define duckdb_v2_parse_sql duckdb_ext_api.duckdb_v2_parse_sql +#define duckdb_v2_result_destroy duckdb_ext_api.duckdb_v2_result_destroy +#define duckdb_v2_result_drain duckdb_ext_api.duckdb_v2_result_drain +#define duckdb_v2_result_fetch_chunk duckdb_ext_api.duckdb_v2_result_fetch_chunk +#define duckdb_v2_result_get_result_type duckdb_ext_api.duckdb_v2_result_get_result_type +#define duckdb_v2_result_get_schema duckdb_ext_api.duckdb_v2_result_get_schema +#define duckdb_v2_result_get_statement_type duckdb_ext_api.duckdb_v2_result_get_statement_type +#define duckdb_v2_result_render_box duckdb_ext_api.duckdb_v2_result_render_box +#define duckdb_v2_result_step duckdb_ext_api.duckdb_v2_result_step +#define duckdb_v2_result_wait duckdb_ext_api.duckdb_v2_result_wait +#define duckdb_v2_schema_destroy duckdb_ext_api.duckdb_v2_schema_destroy +#define duckdb_v2_schema_get_count duckdb_ext_api.duckdb_v2_schema_get_count +#define duckdb_v2_schema_get_field duckdb_ext_api.duckdb_v2_schema_get_field +#define duckdb_v2_sql_statement_destroy duckdb_ext_api.duckdb_v2_sql_statement_destroy +#define duckdb_v2_statement_bind duckdb_ext_api.duckdb_v2_statement_bind +#define duckdb_v2_statement_execute duckdb_ext_api.duckdb_v2_statement_execute +#define duckdb_v2_statement_iterator_destroy duckdb_ext_api.duckdb_v2_statement_iterator_destroy +#define duckdb_v2_statement_iterator_next duckdb_ext_api.duckdb_v2_statement_iterator_next +#define duckdb_v2_value_cast duckdb_ext_api.duckdb_v2_value_cast +#define duckdb_v2_value_create_array duckdb_ext_api.duckdb_v2_value_create_array +#define duckdb_v2_value_create_bigint duckdb_ext_api.duckdb_v2_value_create_bigint +#define duckdb_v2_value_create_bignum duckdb_ext_api.duckdb_v2_value_create_bignum +#define duckdb_v2_value_create_bit duckdb_ext_api.duckdb_v2_value_create_bit +#define duckdb_v2_value_create_blob duckdb_ext_api.duckdb_v2_value_create_blob +#define duckdb_v2_value_create_bool duckdb_ext_api.duckdb_v2_value_create_bool +#define duckdb_v2_value_create_date duckdb_ext_api.duckdb_v2_value_create_date +#define duckdb_v2_value_create_decimal duckdb_ext_api.duckdb_v2_value_create_decimal +#define duckdb_v2_value_create_double duckdb_ext_api.duckdb_v2_value_create_double +#define duckdb_v2_value_create_float duckdb_ext_api.duckdb_v2_value_create_float +#define duckdb_v2_value_create_hugeint duckdb_ext_api.duckdb_v2_value_create_hugeint +#define duckdb_v2_value_create_int duckdb_ext_api.duckdb_v2_value_create_int +#define duckdb_v2_value_create_interval duckdb_ext_api.duckdb_v2_value_create_interval +#define duckdb_v2_value_create_list duckdb_ext_api.duckdb_v2_value_create_list +#define duckdb_v2_value_create_map duckdb_ext_api.duckdb_v2_value_create_map +#define duckdb_v2_value_create_null duckdb_ext_api.duckdb_v2_value_create_null +#define duckdb_v2_value_create_smallint duckdb_ext_api.duckdb_v2_value_create_smallint +#define duckdb_v2_value_create_struct duckdb_ext_api.duckdb_v2_value_create_struct +#define duckdb_v2_value_create_time duckdb_ext_api.duckdb_v2_value_create_time +#define duckdb_v2_value_create_time_ns duckdb_ext_api.duckdb_v2_value_create_time_ns +#define duckdb_v2_value_create_time_tz duckdb_ext_api.duckdb_v2_value_create_time_tz +#define duckdb_v2_value_create_timestamp duckdb_ext_api.duckdb_v2_value_create_timestamp +#define duckdb_v2_value_create_timestamp_ms duckdb_ext_api.duckdb_v2_value_create_timestamp_ms +#define duckdb_v2_value_create_timestamp_ns duckdb_ext_api.duckdb_v2_value_create_timestamp_ns +#define duckdb_v2_value_create_timestamp_sec duckdb_ext_api.duckdb_v2_value_create_timestamp_sec +#define duckdb_v2_value_create_timestamp_tz duckdb_ext_api.duckdb_v2_value_create_timestamp_tz +#define duckdb_v2_value_create_timestamp_tz_ns duckdb_ext_api.duckdb_v2_value_create_timestamp_tz_ns +#define duckdb_v2_value_create_tinyint duckdb_ext_api.duckdb_v2_value_create_tinyint +#define duckdb_v2_value_create_tuple duckdb_ext_api.duckdb_v2_value_create_tuple +#define duckdb_v2_value_create_type duckdb_ext_api.duckdb_v2_value_create_type +#define duckdb_v2_value_create_ubigint duckdb_ext_api.duckdb_v2_value_create_ubigint +#define duckdb_v2_value_create_uhugeint duckdb_ext_api.duckdb_v2_value_create_uhugeint +#define duckdb_v2_value_create_uint duckdb_ext_api.duckdb_v2_value_create_uint +#define duckdb_v2_value_create_usmallint duckdb_ext_api.duckdb_v2_value_create_usmallint +#define duckdb_v2_value_create_utinyint duckdb_ext_api.duckdb_v2_value_create_utinyint +#define duckdb_v2_value_create_uuid duckdb_ext_api.duckdb_v2_value_create_uuid +#define duckdb_v2_value_create_varchar duckdb_ext_api.duckdb_v2_value_create_varchar +#define duckdb_v2_value_destroy duckdb_ext_api.duckdb_v2_value_destroy +#define duckdb_v2_value_get_bigint duckdb_ext_api.duckdb_v2_value_get_bigint +#define duckdb_v2_value_get_blob duckdb_ext_api.duckdb_v2_value_get_blob +#define duckdb_v2_value_get_bool duckdb_ext_api.duckdb_v2_value_get_bool +#define duckdb_v2_value_get_child duckdb_ext_api.duckdb_v2_value_get_child +#define duckdb_v2_value_get_child_count duckdb_ext_api.duckdb_v2_value_get_child_count +#define duckdb_v2_value_get_date duckdb_ext_api.duckdb_v2_value_get_date +#define duckdb_v2_value_get_decimal duckdb_ext_api.duckdb_v2_value_get_decimal +#define duckdb_v2_value_get_double duckdb_ext_api.duckdb_v2_value_get_double +#define duckdb_v2_value_get_float duckdb_ext_api.duckdb_v2_value_get_float +#define duckdb_v2_value_get_hugeint duckdb_ext_api.duckdb_v2_value_get_hugeint +#define duckdb_v2_value_get_int duckdb_ext_api.duckdb_v2_value_get_int +#define duckdb_v2_value_get_interval duckdb_ext_api.duckdb_v2_value_get_interval +#define duckdb_v2_value_get_logical_type duckdb_ext_api.duckdb_v2_value_get_logical_type +#define duckdb_v2_value_get_smallint duckdb_ext_api.duckdb_v2_value_get_smallint +#define duckdb_v2_value_get_time duckdb_ext_api.duckdb_v2_value_get_time +#define duckdb_v2_value_get_time_ns duckdb_ext_api.duckdb_v2_value_get_time_ns +#define duckdb_v2_value_get_time_tz duckdb_ext_api.duckdb_v2_value_get_time_tz +#define duckdb_v2_value_get_timestamp duckdb_ext_api.duckdb_v2_value_get_timestamp +#define duckdb_v2_value_get_timestamp_ms duckdb_ext_api.duckdb_v2_value_get_timestamp_ms +#define duckdb_v2_value_get_timestamp_ns duckdb_ext_api.duckdb_v2_value_get_timestamp_ns +#define duckdb_v2_value_get_timestamp_sec duckdb_ext_api.duckdb_v2_value_get_timestamp_sec +#define duckdb_v2_value_get_timestamp_tz duckdb_ext_api.duckdb_v2_value_get_timestamp_tz +#define duckdb_v2_value_get_timestamp_tz_ns duckdb_ext_api.duckdb_v2_value_get_timestamp_tz_ns +#define duckdb_v2_value_get_tinyint duckdb_ext_api.duckdb_v2_value_get_tinyint +#define duckdb_v2_value_get_type duckdb_ext_api.duckdb_v2_value_get_type +#define duckdb_v2_value_get_ubigint duckdb_ext_api.duckdb_v2_value_get_ubigint +#define duckdb_v2_value_get_uhugeint duckdb_ext_api.duckdb_v2_value_get_uhugeint +#define duckdb_v2_value_get_uint duckdb_ext_api.duckdb_v2_value_get_uint +#define duckdb_v2_value_get_usmallint duckdb_ext_api.duckdb_v2_value_get_usmallint +#define duckdb_v2_value_get_utinyint duckdb_ext_api.duckdb_v2_value_get_utinyint +#define duckdb_v2_value_get_uuid duckdb_ext_api.duckdb_v2_value_get_uuid +#define duckdb_v2_value_get_varchar duckdb_ext_api.duckdb_v2_value_get_varchar +#define duckdb_v2_value_is_null duckdb_ext_api.duckdb_v2_value_is_null +#define duckdb_v2_value_to_string duckdb_ext_api.duckdb_v2_value_to_string +#define duckdb_v2_vector_constant_set_valid duckdb_ext_api.duckdb_v2_vector_constant_set_valid +#define duckdb_v2_vector_flat_get_validity_mutable duckdb_ext_api.duckdb_v2_vector_flat_get_validity_mutable +#define duckdb_v2_vector_flatten duckdb_ext_api.duckdb_v2_vector_flatten +#define duckdb_v2_vector_get_arena duckdb_ext_api.duckdb_v2_vector_get_arena +#define duckdb_v2_vector_get_child duckdb_ext_api.duckdb_v2_vector_get_child +#define duckdb_v2_vector_get_child_count duckdb_ext_api.duckdb_v2_vector_get_child_count +#define duckdb_v2_vector_get_data_mutable duckdb_ext_api.duckdb_v2_vector_get_data_mutable +#define duckdb_v2_vector_get_logical_type duckdb_ext_api.duckdb_v2_vector_get_logical_type +#define duckdb_v2_vector_get_size duckdb_ext_api.duckdb_v2_vector_get_size +#define duckdb_v2_vector_get_value duckdb_ext_api.duckdb_v2_vector_get_value +#define duckdb_v2_vector_get_vector_type duckdb_ext_api.duckdb_v2_vector_get_vector_type +#define duckdb_v2_vector_get_view duckdb_ext_api.duckdb_v2_vector_get_view +#define duckdb_v2_vector_make_constant duckdb_ext_api.duckdb_v2_vector_make_constant +#define duckdb_v2_vector_make_sequence duckdb_ext_api.duckdb_v2_vector_make_sequence +#define duckdb_v2_vector_reference duckdb_ext_api.duckdb_v2_vector_reference +#define duckdb_v2_vector_set_null duckdb_ext_api.duckdb_v2_vector_set_null +#define duckdb_v2_vector_set_size duckdb_ext_api.duckdb_v2_vector_set_size +#define duckdb_v2_vector_set_value duckdb_ext_api.duckdb_v2_vector_set_value +#define duckdb_v2_connection_register_scalar_function duckdb_ext_api.duckdb_v2_connection_register_scalar_function +#define duckdb_v2_context_log duckdb_ext_api.duckdb_v2_context_log +#define duckdb_v2_extension_register_scalar_function duckdb_ext_api.duckdb_v2_extension_register_scalar_function +#define duckdb_v2_function_signature_add_parameter duckdb_ext_api.duckdb_v2_function_signature_add_parameter +#define duckdb_v2_function_signature_set_return_type duckdb_ext_api.duckdb_v2_function_signature_set_return_type +#define duckdb_v2_scalar_function_create duckdb_ext_api.duckdb_v2_scalar_function_create +#define duckdb_v2_scalar_function_destroy duckdb_ext_api.duckdb_v2_scalar_function_destroy +#define duckdb_v2_scalar_function_exec_get_arg duckdb_ext_api.duckdb_v2_scalar_function_exec_get_arg +#define duckdb_v2_scalar_function_exec_get_arg_count duckdb_ext_api.duckdb_v2_scalar_function_exec_get_arg_count +#define duckdb_v2_scalar_function_exec_get_bind_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_bind_data +#define duckdb_v2_scalar_function_exec_get_init_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_init_data +#define duckdb_v2_scalar_function_exec_get_result duckdb_ext_api.duckdb_v2_scalar_function_exec_get_result +#define duckdb_v2_scalar_function_exec_get_row_count duckdb_ext_api.duckdb_v2_scalar_function_exec_get_row_count +#define duckdb_v2_scalar_function_exec_get_user_data duckdb_ext_api.duckdb_v2_scalar_function_exec_get_user_data +#define duckdb_v2_scalar_function_get_signature duckdb_ext_api.duckdb_v2_scalar_function_get_signature +#define duckdb_v2_scalar_function_init_get_bind_data duckdb_ext_api.duckdb_v2_scalar_function_init_get_bind_data +#define duckdb_v2_scalar_function_init_get_user_data duckdb_ext_api.duckdb_v2_scalar_function_init_get_user_data +#define duckdb_v2_scalar_function_init_set_init_data duckdb_ext_api.duckdb_v2_scalar_function_init_set_init_data +#define duckdb_v2_scalar_function_set_bind_callback duckdb_ext_api.duckdb_v2_scalar_function_set_bind_callback +#define duckdb_v2_scalar_function_set_exec_callback duckdb_ext_api.duckdb_v2_scalar_function_set_exec_callback +#define duckdb_v2_scalar_function_set_init_callback duckdb_ext_api.duckdb_v2_scalar_function_set_init_callback +#define duckdb_v2_scalar_function_set_name duckdb_ext_api.duckdb_v2_scalar_function_set_name +#define duckdb_v2_scalar_function_set_user_data duckdb_ext_api.duckdb_v2_scalar_function_set_user_data #define duckdb_v2_aggregate_function_bind_set_return_type \ duckdb_ext_api.duckdb_v2_aggregate_function_bind_set_return_type #define duckdb_v2_aggregate_function_combine_get_bind_data \ @@ -1741,10 +1686,7 @@ typedef struct { #define duckdb_v2_aggregate_function_combine_get_targets duckdb_ext_api.duckdb_v2_aggregate_function_combine_get_targets #define duckdb_v2_aggregate_function_combine_get_user_data \ duckdb_ext_api.duckdb_v2_aggregate_function_combine_get_user_data -#define duckdb_v2_aggregate_function_create_with_connection \ - duckdb_ext_api.duckdb_v2_aggregate_function_create_with_connection -#define duckdb_v2_aggregate_function_create_with_extension \ - duckdb_ext_api.duckdb_v2_aggregate_function_create_with_extension +#define duckdb_v2_aggregate_function_create duckdb_ext_api.duckdb_v2_aggregate_function_create #define duckdb_v2_aggregate_function_destroy duckdb_ext_api.duckdb_v2_aggregate_function_destroy #define duckdb_v2_aggregate_function_destroy_get_bind_data \ duckdb_ext_api.duckdb_v2_aggregate_function_destroy_get_bind_data @@ -1769,7 +1711,6 @@ typedef struct { duckdb_ext_api.duckdb_v2_aggregate_function_init_get_state_count #define duckdb_v2_aggregate_function_init_get_states duckdb_ext_api.duckdb_v2_aggregate_function_init_get_states #define duckdb_v2_aggregate_function_init_get_user_data duckdb_ext_api.duckdb_v2_aggregate_function_init_get_user_data -#define duckdb_v2_aggregate_function_register duckdb_ext_api.duckdb_v2_aggregate_function_register #define duckdb_v2_aggregate_function_set_bind_callback duckdb_ext_api.duckdb_v2_aggregate_function_set_bind_callback #define duckdb_v2_aggregate_function_set_combine_callback \ duckdb_ext_api.duckdb_v2_aggregate_function_set_combine_callback @@ -1795,19 +1736,22 @@ typedef struct { #define duckdb_v2_aggregate_function_update_get_states duckdb_ext_api.duckdb_v2_aggregate_function_update_get_states #define duckdb_v2_aggregate_function_update_get_user_data \ duckdb_ext_api.duckdb_v2_aggregate_function_update_get_user_data -#define duckdb_v2_aggregate_function_set_property duckdb_ext_api.duckdb_v2_aggregate_function_set_property -#define duckdb_v2_scalar_function_set_property duckdb_ext_api.duckdb_v2_scalar_function_set_property -#define duckdb_v2_table_function_bind_add_result_column duckdb_ext_api.duckdb_v2_table_function_bind_add_result_column -#define duckdb_v2_table_function_bind_set_cardinality duckdb_ext_api.duckdb_v2_table_function_bind_set_cardinality -#define duckdb_v2_table_function_create_with_connection duckdb_ext_api.duckdb_v2_table_function_create_with_connection -#define duckdb_v2_table_function_create_with_extension duckdb_ext_api.duckdb_v2_table_function_create_with_extension -#define duckdb_v2_table_function_destroy duckdb_ext_api.duckdb_v2_table_function_destroy -#define duckdb_v2_table_function_exec_get_bind_data duckdb_ext_api.duckdb_v2_table_function_exec_get_bind_data -#define duckdb_v2_table_function_exec_get_global_state duckdb_ext_api.duckdb_v2_table_function_exec_get_global_state -#define duckdb_v2_table_function_exec_get_local_state duckdb_ext_api.duckdb_v2_table_function_exec_get_local_state -#define duckdb_v2_table_function_exec_get_output_chunk duckdb_ext_api.duckdb_v2_table_function_exec_get_output_chunk -#define duckdb_v2_table_function_exec_get_user_data duckdb_ext_api.duckdb_v2_table_function_exec_get_user_data -#define duckdb_v2_table_function_get_signature duckdb_ext_api.duckdb_v2_table_function_get_signature +#define duckdb_v2_connection_register_aggregate_function duckdb_ext_api.duckdb_v2_connection_register_aggregate_function +#define duckdb_v2_extension_register_aggregate_function duckdb_ext_api.duckdb_v2_extension_register_aggregate_function +#define duckdb_v2_aggregate_function_set_property duckdb_ext_api.duckdb_v2_aggregate_function_set_property +#define duckdb_v2_scalar_function_set_property duckdb_ext_api.duckdb_v2_scalar_function_set_property +#define duckdb_v2_connection_register_table_function duckdb_ext_api.duckdb_v2_connection_register_table_function +#define duckdb_v2_extension_register_table_function duckdb_ext_api.duckdb_v2_extension_register_table_function +#define duckdb_v2_table_function_bind_add_result_column duckdb_ext_api.duckdb_v2_table_function_bind_add_result_column +#define duckdb_v2_table_function_bind_set_cardinality duckdb_ext_api.duckdb_v2_table_function_bind_set_cardinality +#define duckdb_v2_table_function_create duckdb_ext_api.duckdb_v2_table_function_create +#define duckdb_v2_table_function_destroy duckdb_ext_api.duckdb_v2_table_function_destroy +#define duckdb_v2_table_function_exec_get_bind_data duckdb_ext_api.duckdb_v2_table_function_exec_get_bind_data +#define duckdb_v2_table_function_exec_get_global_state duckdb_ext_api.duckdb_v2_table_function_exec_get_global_state +#define duckdb_v2_table_function_exec_get_local_state duckdb_ext_api.duckdb_v2_table_function_exec_get_local_state +#define duckdb_v2_table_function_exec_get_output_chunk duckdb_ext_api.duckdb_v2_table_function_exec_get_output_chunk +#define duckdb_v2_table_function_exec_get_user_data duckdb_ext_api.duckdb_v2_table_function_exec_get_user_data +#define duckdb_v2_table_function_get_signature duckdb_ext_api.duckdb_v2_table_function_get_signature #define duckdb_v2_table_function_init_global_get_bind_data \ duckdb_ext_api.duckdb_v2_table_function_init_global_get_bind_data #define duckdb_v2_table_function_init_global_get_user_data \ @@ -1829,7 +1773,6 @@ typedef struct { duckdb_ext_api.duckdb_v2_table_function_progress_get_global_state #define duckdb_v2_table_function_progress_get_user_data duckdb_ext_api.duckdb_v2_table_function_progress_get_user_data #define duckdb_v2_table_function_progress_set_progress duckdb_ext_api.duckdb_v2_table_function_progress_set_progress -#define duckdb_v2_table_function_register duckdb_ext_api.duckdb_v2_table_function_register #define duckdb_v2_table_function_set_bind_callback duckdb_ext_api.duckdb_v2_table_function_set_bind_callback #define duckdb_v2_table_function_set_exec_callback duckdb_ext_api.duckdb_v2_table_function_set_exec_callback #define duckdb_v2_table_function_set_init_global_callback \ @@ -1838,27 +1781,30 @@ typedef struct { #define duckdb_v2_table_function_set_name duckdb_ext_api.duckdb_v2_table_function_set_name #define duckdb_v2_table_function_set_progress_callback duckdb_ext_api.duckdb_v2_table_function_set_progress_callback #define duckdb_v2_table_function_set_user_data duckdb_ext_api.duckdb_v2_table_function_set_user_data -#define duckdb_v2_cast_function_create_with_connection duckdb_ext_api.duckdb_v2_cast_function_create_with_connection -#define duckdb_v2_cast_function_create_with_extension duckdb_ext_api.duckdb_v2_cast_function_create_with_extension +#define duckdb_v2_cast_function_create duckdb_ext_api.duckdb_v2_cast_function_create #define duckdb_v2_cast_function_destroy duckdb_ext_api.duckdb_v2_cast_function_destroy #define duckdb_v2_cast_function_exec_get_input duckdb_ext_api.duckdb_v2_cast_function_exec_get_input #define duckdb_v2_cast_function_exec_get_mode duckdb_ext_api.duckdb_v2_cast_function_exec_get_mode #define duckdb_v2_cast_function_exec_get_output duckdb_ext_api.duckdb_v2_cast_function_exec_get_output #define duckdb_v2_cast_function_exec_get_row_count duckdb_ext_api.duckdb_v2_cast_function_exec_get_row_count #define duckdb_v2_cast_function_exec_get_user_data duckdb_ext_api.duckdb_v2_cast_function_exec_get_user_data -#define duckdb_v2_cast_function_register duckdb_ext_api.duckdb_v2_cast_function_register #define duckdb_v2_cast_function_set_exec_callback duckdb_ext_api.duckdb_v2_cast_function_set_exec_callback #define duckdb_v2_cast_function_set_implicit_cast_cost duckdb_ext_api.duckdb_v2_cast_function_set_implicit_cast_cost #define duckdb_v2_cast_function_set_source_type duckdb_ext_api.duckdb_v2_cast_function_set_source_type #define duckdb_v2_cast_function_set_target_type duckdb_ext_api.duckdb_v2_cast_function_set_target_type #define duckdb_v2_cast_function_set_user_data duckdb_ext_api.duckdb_v2_cast_function_set_user_data #define duckdb_v2_column_data_collection_clear duckdb_ext_api.duckdb_v2_column_data_collection_clear -#define duckdb_v2_custom_type_create_with_connection duckdb_ext_api.duckdb_v2_custom_type_create_with_connection -#define duckdb_v2_custom_type_create_with_extension duckdb_ext_api.duckdb_v2_custom_type_create_with_extension +#define duckdb_v2_connection_register_cast_function duckdb_ext_api.duckdb_v2_connection_register_cast_function +#define duckdb_v2_connection_register_custom_type duckdb_ext_api.duckdb_v2_connection_register_custom_type +#define duckdb_v2_connection_register_replacement_scan duckdb_ext_api.duckdb_v2_connection_register_replacement_scan +#define duckdb_v2_context_get_file_system duckdb_ext_api.duckdb_v2_context_get_file_system +#define duckdb_v2_custom_type_create duckdb_ext_api.duckdb_v2_custom_type_create #define duckdb_v2_custom_type_destroy duckdb_ext_api.duckdb_v2_custom_type_destroy -#define duckdb_v2_custom_type_register duckdb_ext_api.duckdb_v2_custom_type_register #define duckdb_v2_custom_type_set_base_type duckdb_ext_api.duckdb_v2_custom_type_set_base_type #define duckdb_v2_custom_type_set_name duckdb_ext_api.duckdb_v2_custom_type_set_name +#define duckdb_v2_extension_register_cast_function duckdb_ext_api.duckdb_v2_extension_register_cast_function +#define duckdb_v2_extension_register_custom_type duckdb_ext_api.duckdb_v2_extension_register_custom_type +#define duckdb_v2_extension_register_replacement_scan duckdb_ext_api.duckdb_v2_extension_register_replacement_scan #define duckdb_v2_file_close duckdb_ext_api.duckdb_v2_file_close #define duckdb_v2_file_destroy duckdb_ext_api.duckdb_v2_file_destroy #define duckdb_v2_file_open_options_create duckdb_ext_api.duckdb_v2_file_open_options_create @@ -1870,12 +1816,11 @@ typedef struct { #define duckdb_v2_file_seek duckdb_ext_api.duckdb_v2_file_seek #define duckdb_v2_file_size duckdb_ext_api.duckdb_v2_file_size #define duckdb_v2_file_sync duckdb_ext_api.duckdb_v2_file_sync -#define duckdb_v2_file_system_get_from_connection duckdb_ext_api.duckdb_v2_file_system_get_from_connection -#define duckdb_v2_file_system_get_from_context duckdb_ext_api.duckdb_v2_file_system_get_from_context #define duckdb_v2_file_system_open duckdb_ext_api.duckdb_v2_file_system_open #define duckdb_v2_file_tell duckdb_ext_api.duckdb_v2_file_tell #define duckdb_v2_file_write duckdb_ext_api.duckdb_v2_file_write #define duckdb_v2_file_write_at duckdb_ext_api.duckdb_v2_file_write_at +#define duckdb_v2_instance_register_replacement_scan duckdb_ext_api.duckdb_v2_instance_register_replacement_scan #define duckdb_v2_qname_create duckdb_ext_api.duckdb_v2_qname_create #define duckdb_v2_qname_destroy duckdb_ext_api.duckdb_v2_qname_destroy #define duckdb_v2_qname_equals duckdb_ext_api.duckdb_v2_qname_equals @@ -1886,14 +1831,10 @@ typedef struct { #define duckdb_v2_qname_render duckdb_ext_api.duckdb_v2_qname_render #define duckdb_v2_replacement_scan_add_argument duckdb_ext_api.duckdb_v2_replacement_scan_add_argument #define duckdb_v2_replacement_scan_add_named_argument duckdb_ext_api.duckdb_v2_replacement_scan_add_named_argument -#define duckdb_v2_replacement_scan_create_with_connection \ - duckdb_ext_api.duckdb_v2_replacement_scan_create_with_connection -#define duckdb_v2_replacement_scan_create_with_extension duckdb_ext_api.duckdb_v2_replacement_scan_create_with_extension -#define duckdb_v2_replacement_scan_create_with_instance duckdb_ext_api.duckdb_v2_replacement_scan_create_with_instance +#define duckdb_v2_replacement_scan_create duckdb_ext_api.duckdb_v2_replacement_scan_create #define duckdb_v2_replacement_scan_destroy duckdb_ext_api.duckdb_v2_replacement_scan_destroy #define duckdb_v2_replacement_scan_get_name duckdb_ext_api.duckdb_v2_replacement_scan_get_name #define duckdb_v2_replacement_scan_get_user_data duckdb_ext_api.duckdb_v2_replacement_scan_get_user_data -#define duckdb_v2_replacement_scan_register duckdb_ext_api.duckdb_v2_replacement_scan_register #define duckdb_v2_replacement_scan_set_alias duckdb_ext_api.duckdb_v2_replacement_scan_set_alias #define duckdb_v2_replacement_scan_set_callback duckdb_ext_api.duckdb_v2_replacement_scan_set_callback #define duckdb_v2_replacement_scan_set_collection duckdb_ext_api.duckdb_v2_replacement_scan_set_collection @@ -1916,6 +1857,7 @@ typedef struct { #define duckdb_v2_column_description_has_default duckdb_ext_api.duckdb_v2_column_description_has_default #define duckdb_v2_column_description_has_generated duckdb_ext_api.duckdb_v2_column_description_has_generated #define duckdb_v2_connection_describe_table duckdb_ext_api.duckdb_v2_connection_describe_table +#define duckdb_v2_connection_register_copy_function duckdb_ext_api.duckdb_v2_connection_register_copy_function #define duckdb_v2_copy_from_bind_get_column_count duckdb_ext_api.duckdb_v2_copy_from_bind_get_column_count #define duckdb_v2_copy_from_bind_get_column_name duckdb_ext_api.duckdb_v2_copy_from_bind_get_column_name #define duckdb_v2_copy_from_bind_get_column_type duckdb_ext_api.duckdb_v2_copy_from_bind_get_column_type @@ -1948,10 +1890,8 @@ typedef struct { #define duckdb_v2_copy_from_set_init_global_callback duckdb_ext_api.duckdb_v2_copy_from_set_init_global_callback #define duckdb_v2_copy_from_set_init_local_callback duckdb_ext_api.duckdb_v2_copy_from_set_init_local_callback #define duckdb_v2_copy_from_set_progress_callback duckdb_ext_api.duckdb_v2_copy_from_set_progress_callback -#define duckdb_v2_copy_function_create_with_connection duckdb_ext_api.duckdb_v2_copy_function_create_with_connection -#define duckdb_v2_copy_function_create_with_extension duckdb_ext_api.duckdb_v2_copy_function_create_with_extension +#define duckdb_v2_copy_function_create duckdb_ext_api.duckdb_v2_copy_function_create #define duckdb_v2_copy_function_destroy duckdb_ext_api.duckdb_v2_copy_function_destroy -#define duckdb_v2_copy_function_register duckdb_ext_api.duckdb_v2_copy_function_register #define duckdb_v2_copy_function_set_name duckdb_ext_api.duckdb_v2_copy_function_set_name #define duckdb_v2_copy_function_set_user_data duckdb_ext_api.duckdb_v2_copy_function_set_user_data #define duckdb_v2_copy_to_batch_get_bind_data duckdb_ext_api.duckdb_v2_copy_to_batch_get_bind_data @@ -1997,6 +1937,7 @@ typedef struct { #define duckdb_v2_expression_get_child_count duckdb_ext_api.duckdb_v2_expression_get_child_count #define duckdb_v2_expression_get_return_type duckdb_ext_api.duckdb_v2_expression_get_return_type #define duckdb_v2_expression_get_type duckdb_ext_api.duckdb_v2_expression_get_type +#define duckdb_v2_extension_register_copy_function duckdb_ext_api.duckdb_v2_extension_register_copy_function #define duckdb_v2_identifier_render_quoted duckdb_ext_api.duckdb_v2_identifier_render_quoted #define duckdb_v2_prepared_statement_create duckdb_ext_api.duckdb_v2_prepared_statement_create #define duckdb_v2_prepared_statement_destroy duckdb_ext_api.duckdb_v2_prepared_statement_destroy @@ -2044,9 +1985,6 @@ typedef struct { #define duckdb_v2_attach_options_create duckdb_ext_api.duckdb_v2_attach_options_create #define duckdb_v2_attach_options_destroy duckdb_ext_api.duckdb_v2_attach_options_destroy #define duckdb_v2_attach_options_set duckdb_ext_api.duckdb_v2_attach_options_set -#define duckdb_v2_context_get_option_by_index duckdb_ext_api.duckdb_v2_context_get_option_by_index -#define duckdb_v2_context_get_option_by_name duckdb_ext_api.duckdb_v2_context_get_option_by_name -#define duckdb_v2_context_get_option_count duckdb_ext_api.duckdb_v2_context_get_option_count #define duckdb_v2_instance_set_default duckdb_ext_api.duckdb_v2_instance_set_default #define duckdb_v2_table_function_partition_data_get_bind_data \ duckdb_ext_api.duckdb_v2_table_function_partition_data_get_bind_data @@ -2107,7 +2045,89 @@ typedef struct { duckdb_ext_api.duckdb_v2_scalar_function_resolve_types_set_return_type #define duckdb_v2_scalar_function_set_resolve_types_callback \ duckdb_ext_api.duckdb_v2_scalar_function_set_resolve_types_callback -#define duckdb_v2_scalar_function_bind_set_bind_data duckdb_ext_api.duckdb_v2_scalar_function_bind_set_bind_data +#define duckdb_v2_connection_get_factory duckdb_ext_api.duckdb_v2_connection_get_factory +#define duckdb_v2_context_get_factory duckdb_ext_api.duckdb_v2_context_get_factory +#define duckdb_v2_instance_get_factory duckdb_ext_api.duckdb_v2_instance_get_factory +#define duckdb_v2_scalar_function_bind_set_bind_data duckdb_ext_api.duckdb_v2_scalar_function_bind_set_bind_data +#define duckdb_v2_config_get_option_value duckdb_ext_api.duckdb_v2_config_get_option_value +#define duckdb_v2_config_set_option_text duckdb_ext_api.duckdb_v2_config_set_option_text +#define duckdb_v2_config_set_option_value duckdb_ext_api.duckdb_v2_config_set_option_value +#define duckdb_v2_connection_get_config duckdb_ext_api.duckdb_v2_connection_get_config +#define duckdb_v2_connection_get_file_system duckdb_ext_api.duckdb_v2_connection_get_file_system +#define duckdb_v2_connection_log duckdb_ext_api.duckdb_v2_connection_log +#define duckdb_v2_context_get_config duckdb_ext_api.duckdb_v2_context_get_config +#define duckdb_v2_instance_get_config duckdb_ext_api.duckdb_v2_instance_get_config +#define duckdb_v2_instance_get_file_system duckdb_ext_api.duckdb_v2_instance_get_file_system +#define duckdb_v2_instance_log duckdb_ext_api.duckdb_v2_instance_log +#define duckdb_v2_option_get_default_scope duckdb_ext_api.duckdb_v2_option_get_default_scope +#define duckdb_v2_option_supports_scope duckdb_ext_api.duckdb_v2_option_supports_scope +#define duckdb_v2_aggregate_function_get_docs duckdb_ext_api.duckdb_v2_aggregate_function_get_docs +#define duckdb_v2_connection_register_pragma_function duckdb_ext_api.duckdb_v2_connection_register_pragma_function +#define duckdb_v2_extension_register_pragma_function duckdb_ext_api.duckdb_v2_extension_register_pragma_function +#define duckdb_v2_function_docs_add_category duckdb_ext_api.duckdb_v2_function_docs_add_category +#define duckdb_v2_function_docs_add_example duckdb_ext_api.duckdb_v2_function_docs_add_example +#define duckdb_v2_function_docs_set_description duckdb_ext_api.duckdb_v2_function_docs_set_description +#define duckdb_v2_pragma_function_create duckdb_ext_api.duckdb_v2_pragma_function_create +#define duckdb_v2_pragma_function_destroy duckdb_ext_api.duckdb_v2_pragma_function_destroy +#define duckdb_v2_pragma_function_get_docs duckdb_ext_api.duckdb_v2_pragma_function_get_docs +#define duckdb_v2_pragma_function_get_signature duckdb_ext_api.duckdb_v2_pragma_function_get_signature +#define duckdb_v2_pragma_function_query_set_query duckdb_ext_api.duckdb_v2_pragma_function_query_set_query +#define duckdb_v2_pragma_function_set_exec_callback duckdb_ext_api.duckdb_v2_pragma_function_set_exec_callback +#define duckdb_v2_pragma_function_set_name duckdb_ext_api.duckdb_v2_pragma_function_set_name +#define duckdb_v2_pragma_function_set_query_callback duckdb_ext_api.duckdb_v2_pragma_function_set_query_callback +#define duckdb_v2_pragma_function_set_user_data duckdb_ext_api.duckdb_v2_pragma_function_set_user_data +#define duckdb_v2_scalar_function_get_docs duckdb_ext_api.duckdb_v2_scalar_function_get_docs +#define duckdb_v2_table_function_get_docs duckdb_ext_api.duckdb_v2_table_function_get_docs +#define duckdb_v2_connection_register_custom_option duckdb_ext_api.duckdb_v2_connection_register_custom_option +#define duckdb_v2_custom_option_create duckdb_ext_api.duckdb_v2_custom_option_create +#define duckdb_v2_custom_option_destroy duckdb_ext_api.duckdb_v2_custom_option_destroy +#define duckdb_v2_custom_option_set_default_scope duckdb_ext_api.duckdb_v2_custom_option_set_default_scope +#define duckdb_v2_custom_option_set_default_value duckdb_ext_api.duckdb_v2_custom_option_set_default_value +#define duckdb_v2_custom_option_set_description duckdb_ext_api.duckdb_v2_custom_option_set_description +#define duckdb_v2_custom_option_set_name duckdb_ext_api.duckdb_v2_custom_option_set_name +#define duckdb_v2_custom_option_set_supports_scope duckdb_ext_api.duckdb_v2_custom_option_set_supports_scope +#define duckdb_v2_custom_option_set_type duckdb_ext_api.duckdb_v2_custom_option_set_type +#define duckdb_v2_custom_option_set_update_callback duckdb_ext_api.duckdb_v2_custom_option_set_update_callback +#define duckdb_v2_custom_option_set_user_data duckdb_ext_api.duckdb_v2_custom_option_set_user_data +#define duckdb_v2_custom_option_update_get_scope duckdb_ext_api.duckdb_v2_custom_option_update_get_scope +#define duckdb_v2_custom_option_update_get_user_data duckdb_ext_api.duckdb_v2_custom_option_update_get_user_data +#define duckdb_v2_custom_option_update_get_value duckdb_ext_api.duckdb_v2_custom_option_update_get_value +#define duckdb_v2_custom_option_update_is_reset duckdb_ext_api.duckdb_v2_custom_option_update_is_reset +#define duckdb_v2_custom_option_update_set_value duckdb_ext_api.duckdb_v2_custom_option_update_set_value +#define duckdb_v2_extension_register_custom_option duckdb_ext_api.duckdb_v2_extension_register_custom_option +#define duckdb_v2_extension_register_log_sink duckdb_ext_api.duckdb_v2_extension_register_log_sink +#define duckdb_v2_extension_register_macro_function duckdb_ext_api.duckdb_v2_extension_register_macro_function +#define duckdb_v2_file_system_path_exists duckdb_ext_api.duckdb_v2_file_system_path_exists +#define duckdb_v2_file_system_path_expand duckdb_ext_api.duckdb_v2_file_system_path_expand +#define duckdb_v2_file_system_path_is_remote duckdb_ext_api.duckdb_v2_file_system_path_is_remote +#define duckdb_v2_instance_register_log_sink duckdb_ext_api.duckdb_v2_instance_register_log_sink +#define duckdb_v2_log_sink_create duckdb_ext_api.duckdb_v2_log_sink_create +#define duckdb_v2_log_sink_destroy duckdb_ext_api.duckdb_v2_log_sink_destroy +#define duckdb_v2_log_sink_set_flush_callback duckdb_ext_api.duckdb_v2_log_sink_set_flush_callback +#define duckdb_v2_log_sink_set_name duckdb_ext_api.duckdb_v2_log_sink_set_name +#define duckdb_v2_log_sink_set_user_data duckdb_ext_api.duckdb_v2_log_sink_set_user_data +#define duckdb_v2_log_sink_set_write_callback duckdb_ext_api.duckdb_v2_log_sink_set_write_callback +#define duckdb_v2_log_sink_write_info_get_connection_id duckdb_ext_api.duckdb_v2_log_sink_write_info_get_connection_id +#define duckdb_v2_log_sink_write_info_get_query_id duckdb_ext_api.duckdb_v2_log_sink_write_info_get_query_id +#define duckdb_v2_log_sink_write_info_get_thread_id duckdb_ext_api.duckdb_v2_log_sink_write_info_get_thread_id +#define duckdb_v2_log_sink_write_info_get_transaction_id duckdb_ext_api.duckdb_v2_log_sink_write_info_get_transaction_id +#define duckdb_v2_macro_function_create duckdb_ext_api.duckdb_v2_macro_function_create +#define duckdb_v2_macro_function_destroy duckdb_ext_api.duckdb_v2_macro_function_destroy +#define duckdb_v2_macro_function_get_docs duckdb_ext_api.duckdb_v2_macro_function_get_docs +#define duckdb_v2_macro_function_get_signature duckdb_ext_api.duckdb_v2_macro_function_get_signature +#define duckdb_v2_macro_function_set_body duckdb_ext_api.duckdb_v2_macro_function_set_body +#define duckdb_v2_macro_function_set_name duckdb_ext_api.duckdb_v2_macro_function_set_name +#define duckdb_v2_vector_cast duckdb_ext_api.duckdb_v2_vector_cast +#define duckdb_v2_copy_function_set_file_extension duckdb_ext_api.duckdb_v2_copy_function_set_file_extension +#define duckdb_v2_data_chunk_clear duckdb_ext_api.duckdb_v2_data_chunk_clear +#define duckdb_v2_file_system_path_glob duckdb_ext_api.duckdb_v2_file_system_path_glob +#define duckdb_v2_file_system_path_move duckdb_ext_api.duckdb_v2_file_system_path_move +#define duckdb_v2_file_system_path_remove duckdb_ext_api.duckdb_v2_file_system_path_remove +#define duckdb_v2_statement_add_collection duckdb_ext_api.duckdb_v2_statement_add_collection +#if DUCKDB_V2_API_ALLOW_UNSTABLE +#define duckdb_v2_connection_register_multi_file_function \ + duckdb_ext_api.duckdb_v2_connection_register_multi_file_function +#endif #if DUCKDB_V2_API_ALLOW_UNSTABLE #define duckdb_v2_copy_to_set_statistics_callback duckdb_ext_api.duckdb_v2_copy_to_set_statistics_callback #endif @@ -2130,20 +2150,15 @@ typedef struct { #define duckdb_v2_data_chunk_get_capacity duckdb_ext_api.duckdb_v2_data_chunk_get_capacity #endif #if DUCKDB_V2_API_ALLOW_UNSTABLE -#define duckdb_v2_multi_file_function_create_with_connection \ - duckdb_ext_api.duckdb_v2_multi_file_function_create_with_connection +#define duckdb_v2_extension_register_multi_file_function duckdb_ext_api.duckdb_v2_extension_register_multi_file_function #endif #if DUCKDB_V2_API_ALLOW_UNSTABLE -#define duckdb_v2_multi_file_function_create_with_extension \ - duckdb_ext_api.duckdb_v2_multi_file_function_create_with_extension +#define duckdb_v2_multi_file_function_create duckdb_ext_api.duckdb_v2_multi_file_function_create #endif #if DUCKDB_V2_API_ALLOW_UNSTABLE #define duckdb_v2_multi_file_function_destroy duckdb_ext_api.duckdb_v2_multi_file_function_destroy #endif #if DUCKDB_V2_API_ALLOW_UNSTABLE -#define duckdb_v2_multi_file_function_register duckdb_ext_api.duckdb_v2_multi_file_function_register -#endif -#if DUCKDB_V2_API_ALLOW_UNSTABLE #define duckdb_v2_multi_file_function_set_file_extension duckdb_ext_api.duckdb_v2_multi_file_function_set_file_extension #endif #if DUCKDB_V2_API_ALLOW_UNSTABLE @@ -2199,6 +2214,9 @@ typedef struct { #define duckdb_v2_table_function_set_get_bind_info_callback \ duckdb_ext_api.duckdb_v2_table_function_set_get_bind_info_callback #endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE +#define duckdb_v2_factory_create_common_type duckdb_ext_api.duckdb_v2_factory_create_common_type +#endif // capigen:end appended #endif // DUCKDB_BUILD_STATIC_EXTENSION diff --git a/src/duckdb/src/include/duckdb_v2.h b/src/duckdb/src/include/duckdb_v2.h index b18ef619c..efceccf75 100644 --- a/src/duckdb/src/include/duckdb_v2.h +++ b/src/duckdb/src/include/duckdb_v2.h @@ -249,10 +249,21 @@ typedef struct _duckdb_v2_connection { } * duckdb_v2_connection_handle; /*! - * An opaque, owned, read-only descriptor of a single config option as seen from an instance, connection, or context: - * its canonical name, current setting, default setting, description, target scope, and aliases. Returned by the - * *_get_option and *_get_option_by_index functions; the caller always destroys it via `duckdb_v2_option_destroy()`. - * Options are written with the *_set_option functions, which take a name and a setting directly. + * A borrowed handle to the scope config options are read from and written at. Taken from an instance + * (`duckdb_v2_instance_get_config()`), a connection (`duckdb_v2_connection_get_config()`) or a context + * (`duckdb_v2_context_get_config()`), and valid for as long as its source is; the caller never destroys it. An + * instance's config is the GLOBAL scope alone; a connection's or a context's config reads that client's cascade (its + * SESSION value, else the GLOBAL one, else the default) and writes at any scope the option supports. + */ +typedef struct _duckdb_v2_config { + void *internal_ptr; +} * duckdb_v2_config_handle; + +/*! + * An opaque, owned, read-only descriptor of a single config option as registered on an instance: its canonical name, + * default value, description, the scopes it may be written at, and aliases. It carries no current value: read that with + * the *_get_option_value functions, and write it with the *_set_option functions. Returned by the *_get_option_by_name + * and *_get_option_by_index functions; the caller always destroys it via `duckdb_v2_option_destroy()`. */ typedef struct _duckdb_v2_option { void *internal_ptr; @@ -290,11 +301,11 @@ typedef struct _duckdb_v2_value { * (`duckdb_v2_result_step()`) or draining (`duckdb_v2_result_fetch_chunk()`). Single consumer: step from one thread at * a time. * - * A result is a cursor on the connection's execution, not a box of data. While it is live — not finished, cancelled, - * errored, or destroyed — the connection refuses new queries with ERROR_RESOURCE_IN_USE, and the query's transaction - * stays open, deferring version cleanup and checkpointing, so drain or destroy it promptly. Side-effecting statements - * (PRAGMA, ALTER, ...) take effect only once the result is drained. Always destroy via `duckdb_v2_result_destroy()`, - * which is safe even on a partially consumed stream. + * A result is a cursor on the connection's execution, not a box of data. While its statement runs — at most until it is + * finished, cancelled, errored, or destroyed — the connection refuses new queries with ERROR_RESOURCE_IN_USE, and the + * query's transaction stays open, deferring version cleanup and checkpointing, so drain or destroy it promptly. + * Side-effecting statements (PRAGMA, ALTER, ...) take effect only once the result is drained. Always destroy via + * `duckdb_v2_result_destroy()`, which is safe even on a partially consumed stream. */ typedef struct _duckdb_v2_result { void *internal_ptr; @@ -332,15 +343,26 @@ typedef struct _duckdb_v2_arena { /*! * A borrowed handle to a client context: a connection seen from inside DuckDB. Handed out within DuckDB-managed scopes * — function bind / init / exec callbacks, replacement scans, and the extension entrypoint — and valid only for the - * duration of that scope; the caller never destroys it. A context is the scope for reading (settings, the file system) - * and for constructing values and types, not for registration: catalog entries and instance-level hooks are installed - * through an extension or a connection. A transaction is always active while a context is handed out, which is what the - * context-scoped constructors assume. + * duration of that scope; the caller never destroys it. Within that scope a transaction is already active. A context + * reaches the same capabilities a connection does, through `duckdb_v2_context_get_factory()`, + * `duckdb_v2_context_get_config()`, `duckdb_v2_context_get_file_system()` and `duckdb_v2_context_log()`. */ typedef struct _duckdb_v2_context { void *internal_ptr; } * duckdb_v2_context_handle; +/*! + * A borrowed handle to the scope values, types, data chunks and column data collections are created in. Taken from an + * instance (`duckdb_v2_instance_get_factory()`), a connection (`duckdb_v2_connection_get_factory()`) or a context + * (`duckdb_v2_context_get_factory()`), and valid for as long as its source is; the caller never destroys it. A factory + * resolves in the scope of its source: a connection's or a context's factory reaches the catalog and the client's + * settings, while an instance's factory knows only the built-in types and casts, with default settings, and refuses + * anything that needs a catalog. + */ +typedef struct _duckdb_v2_factory { + void *internal_ptr; +} * duckdb_v2_factory_handle; + /*! * An entry in a selection-vector. * @@ -806,7 +828,7 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_arena_allocate(duckdb_v2_arena_handle are * The mode a cast is being executed in. A normal cast must either succeed for every row or report an error, which * aborts the query. A "try" cast (SQL TRY_CAST, and implicit casts the engine probes speculatively) tolerates per-row * failures: the callback writes NULL for the rows it could not convert instead of aborting. Read with - * `duckdb_v2_cast_function_exec_get_mode()`. + * `duckdb_v2_cast_function_exec_get_mode()`, and passed to `duckdb_v2_vector_cast()`. */ typedef enum DUCKDB_V2_CAST_MODE { //! A regular cast. A conversion failure reported through the error slot aborts the query. @@ -825,11 +847,11 @@ typedef enum DUCKDB_V2_CAST_MODE { /* --- Types for cast --- */ /*! - * An owned opaque handle to a cast function being built. Created with - * `duckdb_v2_cast_function_create_with_connection()` or `duckdb_v2_cast_function_create_with_extension()`, configured + * An owned opaque handle to a cast function being built. Created with `duckdb_v2_cast_function_create()`, configured * with the setter functions (e.g. `duckdb_v2_cast_function_set_source_type()`, - * `duckdb_v2_cast_function_set_exec_callback()`, etc.), made available with `duckdb_v2_cast_function_register()`, and - * destroyed with `duckdb_v2_cast_function_destroy()`. + * `duckdb_v2_cast_function_set_exec_callback()`, etc.), made available with + * `duckdb_v2_connection_register_cast_function()` or `duckdb_v2_extension_register_cast_function()`, and destroyed with + * `duckdb_v2_cast_function_destroy()`. */ typedef struct _duckdb_v2_cast_function { void *internal_ptr; @@ -855,47 +877,28 @@ typedef void (*duckdb_v2_cast_function_exec_callback_fn)(duckdb_v2_cast_function /* --- Functions for cast --- */ /*! - * Creates a new cast function that will be registered on the connection's database. + * Creates a new cast function through a factory. * * The function starts out empty: configure it with the setter functions (e.g. * `duckdb_v2_cast_function_set_source_type()`, `duckdb_v2_cast_function_set_exec_callback()`, etc.), then make it - * available with `duckdb_v2_cast_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_cast_function_destroy()`, also after registration. - * - * history: - * - stable: v2.0.0 - * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created cast function. Owned by the caller. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_cast_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_cast_function_handle *function, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a new cast function that will be registered on the loading extension's database. + * available with `duckdb_v2_connection_register_cast_function()` or `duckdb_v2_extension_register_cast_function()`. The + * caller owns the returned handle and must destroy it with `duckdb_v2_cast_function_destroy()`, also after + * registration. * - * Use this from an extension load callback, where an extension handle is available. The function starts out empty: - * configure it with the setter functions (e.g. `duckdb_v2_cast_function_set_source_type()`, - * `duckdb_v2_cast_function_set_exec_callback()`, etc.), then make it available with - * `duckdb_v2_cast_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_cast_function_destroy()`, also after registration. + * The cast function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the function in. + * @param factory The factory to create the cast function through. * @param function On success, receives the newly created cast function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_cast_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_cast_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_cast_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_cast_function_handle *function, + duckdb_v2_error_info_handle *err); /*! * Sets the type the cast converts from. @@ -1090,22 +1093,47 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_cast_function_exec_get_mode(duckdb_v2_cas /*! * Registers the cast function, making it available to CAST and TRY_CAST. * - * The function is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a source type, a target type and an exec callback; both types must be fully defined concrete - * types. Registering a cast for a pair that already has one replaces it. The caller still owns the handle after - * registration and must destroy it with `duckdb_v2_cast_function_destroy()`, which does not affect the registered - * function. + * The cast is registered on the connection's database and takes effect immediately for every connection; it is not part + * of the connection's transaction, so a rollback does not undo it. Registration requires a source type, a target type + * and an exec callback; both types must be fully defined concrete types. Registering a cast for a pair that already has + * one replaces it. The cast function must have been created through a factory of the same database. The caller still + * owns the handle after registration and must destroy it with `duckdb_v2_cast_function_destroy()`, which does not + * affect the registered function. * * history: * - stable: v2.0.0 * - * @param function The function to register. + * @param conn The connection to register on. + * @param function The cast function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_cast_function_register(duckdb_v2_cast_function_handle function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_cast_function(duckdb_v2_connection_handle conn, + duckdb_v2_cast_function_handle function, + duckdb_v2_error_info_handle *err); + +/*! + * Registers the cast function, making it available to CAST and TRY_CAST. + * + * The cast is registered on the loading extension's database. Use this from the extension's load callback. Registration + * requires a source type, a target type and an exec callback; both types must be fully defined concrete types. + * Registering a cast for a pair that already has one replaces it. The cast function must have been created through a + * factory of the same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_cast_function_destroy()`, which does not affect the registered function. + * + * history: + * - stable: v2.0.0 + * + * @param extension The loading extension. + * @param function The cast function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_cast_function(duckdb_v2_extension_handle extension, + duckdb_v2_cast_function_handle function, + duckdb_v2_error_info_handle *err); /*! * Destroys the cast function, releasing its resources. @@ -1181,40 +1209,16 @@ typedef struct _duckdb_v2_column_data_collection_append_state { /* --- Functions for column_data_collection --- */ /*! - * Creates an empty column data collection from a connection. - * - * Allocates a new, empty column data collection, drawing its buffer allocator from the connection's context. The - * collection starts with no chunks and is ready to have chunks appended to it. A collection must have at least one - * column; an empty types array is rejected with INVALID_INPUT. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection whose context supplies the collection's allocator. - * @param types_array Pointer to an array of logical_type handles, one per column. This defines the schema for the - * chunks that will be stored in the collection. All chunks appended to this collection must have vectors that conform - * to these types. - * @param types_count Number of elements in the types_array. - * @param out_collection Receives the new collection handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create_with_connection( - duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, - duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); - -/*! - * Creates an empty column data collection from a context. + * Creates an empty column data collection from a factory. * - * Allocates a new, empty column data collection, drawing its buffer allocator from the context. Use this from inside a - * callback or extension where a context is already in hand. The collection starts with no chunks and is ready to have - * chunks appended to it. A collection must have at least one column; an empty types array is rejected with - * INVALID_INPUT. + * Allocates a new, empty column data collection, drawing its buffers from the factory's database. The collection starts + * with no chunks and is ready to have chunks appended to it. A collection must have at least one column; an empty types + * array is rejected with INVALID_INPUT. * * history: * - stable: v2.0.0 * - * @param context The context whose allocator will be used for the collection. + * @param factory The factory to create with. * @param types_array Pointer to an array of logical_type handles, one per column. This defines the schema for the * chunks that will be stored in the collection. All chunks appended to this collection must have vectors that conform * to these types. @@ -1223,8 +1227,8 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create_with_connec * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create_with_context( - duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create( + duckdb_v2_factory_handle factory, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err); /*! @@ -1282,8 +1286,8 @@ duckdb_v2_column_data_collection_destroy(duckdb_v2_column_data_collection_handle * * Transfers all chunks from the source collection to the target collection. This destroys the source collection, * setting the source handle to NULL. The two collections must have the same column types, and must have been created - * from the same context (or connections to the same database): the transferred chunks keep their original buffers, so - * combining collections from different databases would tie the target to a buffer manager it does not own. + * from factories of the same database: the transferred chunks keep their original buffers, so combining collections + * from different databases would tie the target to a buffer manager it does not own. * * history: * - stable: v2.0.0 @@ -1476,41 +1480,21 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_data_collection_scan( /* --- Enums for configuration --- */ /*! - * The scope target of an option: where DuckDB permits its setting to be written. UNKNOWN is reported for an option - * whose declaration carries no explicit scope target, which includes every extension option. - */ -typedef enum DUCKDB_V2_OPTION_TARGET_SCOPE { - //! Target scope is not known. - DUCKDB_V2_OPTION_TARGET_SCOPE_UNKNOWN = 0, - - //! May only be written at GLOBAL (instance) scope. - DUCKDB_V2_OPTION_TARGET_SCOPE_GLOBAL_ONLY = 1, - - //! May only be written at LOCAL (session) scope. - DUCKDB_V2_OPTION_TARGET_SCOPE_LOCAL_ONLY = 2, - - //! May be written at either scope; defaults to GLOBAL when unspecified. - DUCKDB_V2_OPTION_TARGET_SCOPE_GLOBAL_DEFAULT = 3, - - //! May be written at either scope; defaults to LOCAL when unspecified. - DUCKDB_V2_OPTION_TARGET_SCOPE_LOCAL_DEFAULT = 4, - DUCKDB_V2_OPTION_TARGET_SCOPE_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_OPTION_TARGET_SCOPE; - -/*! - * Destination scope for a connection-side option write, as taken by connection_option_set. AUTOMATIC defers to the - * option's target scope, like SQL `SET name = value`. GLOBAL writes through to the instance, visible to all - * connections, like `SET GLOBAL`. LOCAL writes to the connection's session only, like `SET LOCAL` / `SET SESSION`. + * The scope of an option's value. Writes take DEFAULT, GLOBAL or SESSION: DEFAULT writes to the option's default scope + * (`duckdb_v2_option_get_default_scope()`), like SQL `SET name = value`; GLOBAL writes through to the instance, visible + * to all connections, like `SET GLOBAL`; SESSION writes to one connection's session only, like `SET SESSION`. Reads + * report GLOBAL or SESSION, as DuckDB attributes the value: an option no scope sets reads as GLOBAL, its default, and a + * legacy option read through a connection or context reads as SESSION. */ typedef enum DUCKDB_V2_SETTING_SCOPE { - //! Resolve from the option's target scope. - DUCKDB_V2_SETTING_SCOPE_AUTOMATIC = 0, + //! Write only: the option's default scope (`duckdb_v2_option_get_default_scope()`), not its default value. + DUCKDB_V2_SETTING_SCOPE_DEFAULT = 0, - //! Write through to the instance (visible to all connections). + //! The instance's value, visible to all connections. DUCKDB_V2_SETTING_SCOPE_GLOBAL = 1, - //! Write to the connection's session only. - DUCKDB_V2_SETTING_SCOPE_LOCAL = 2, + //! A connection's session value. + DUCKDB_V2_SETTING_SCOPE_SESSION = 2, DUCKDB_V2_SETTING_SCOPE_MAX_ENUM = 0x7FFFFFFF, } DUCKDB_V2_SETTING_SCOPE; @@ -1525,475 +1509,649 @@ typedef enum DUCKDB_V2_SETTING_SCOPE { /* --- Functions for configuration --- */ /*! - * Destroys an option descriptor. + * Borrows the config of an instance: the GLOBAL scope. * - * Frees the handle along with the strings it owns. On success the slot is set to null. Safe to call on an already-null - * slot. + * Reads report GLOBAL, and writes are `SET GLOBAL`. Writing at SESSION scope returns ERROR_INPUT_INVALID: an instance + * has no session. Options that can only be chosen at startup are passed to `duckdb_v2_instance_create_with_options()`. + * The returned handle is borrowed: it is valid for as long as the instance is, and must not be destroyed. * * history: * - stable: v2.0.0 * - * @param option The option handle to destroy. + * @param instance The instance. + * @param out_config Receives the borrowed config. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_destroy(duckdb_v2_option_handle *option); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_config(duckdb_v2_instance_handle instance, + duckdb_v2_config_handle *out_config, + duckdb_v2_error_info_handle *err); /*! - * Borrows the option's canonical name. + * Borrows the config of a connection. * - * When the option was resolved from an alias, this still reports the canonical name; the alias is reachable via - * `duckdb_v2_option_get_alias()`. + * Reads follow the connection's cascade: its SESSION value if it set one, otherwise the GLOBAL value, otherwise the + * default. Writes accept any scope the option supports. The returned handle is borrowed: it is valid for as long as the + * connection is, and must not be destroyed. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_name Receives a borrowed view of the option name. Valid until the option is destroyed. + * @param conn The connection. + * @param out_config Receives the borrowed config. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_name(duckdb_v2_option_handle option, duckdb_v2_identifier_t *out_name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_config(duckdb_v2_connection_handle conn, + duckdb_v2_config_handle *out_config, + duckdb_v2_error_info_handle *err); /*! - * Borrows the option's current setting (its string-encoded value). + * Borrows the config of a context. * - * The effective setting at the scope of the instance, connection, or context the descriptor was read from. + * The same scope `duckdb_v2_connection_get_config()` gives for the context's connection. The returned handle is + * borrowed: it is valid for as long as the context is, and must not be destroyed. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_setting Receives a borrowed view of the setting. Valid until the option is destroyed. + * @param ctx The context. + * @param out_config Receives the borrowed config. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_setting(duckdb_v2_option_handle option, duckdb_v2_str *out_setting, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_config(duckdb_v2_context_handle ctx, + duckdb_v2_config_handle *out_config, duckdb_v2_error_info_handle *err); /*! - * Borrows the option's static default setting. + * Reads the effective value of an option in this config, and the scope it came from. * - * The empty view for an option whose declaration has no default. + * Aliases resolve transparently, and an unknown name returns ERROR_INPUT_INVALID. The caller destroys the returned + * value. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_default_setting Receives a borrowed view of the default setting. Valid until the option is destroyed. + * @param config The config to read from. + * @param name Option name (canonical or alias). + * @param out_value Receives the value, typed as the option. Owned by the caller. + * @param out_scope Receives the scope the value came from. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_default_setting(duckdb_v2_option_handle option, - duckdb_v2_str *out_default_setting, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_get_option_value(duckdb_v2_config_handle config, + const duckdb_v2_identifier_t *name, + duckdb_v2_value_handle *out_value, + DUCKDB_V2_SETTING_SCOPE *out_scope, + duckdb_v2_error_info_handle *err); /*! - * Borrows the option's human-readable description. + * Writes an option in this config. + * + * `scope` chooses the destination, mirroring SQL: + * - DEFAULT writes to the option's default scope, like a bare `SET name = value`. + * - GLOBAL writes through to the instance, visible to all connections, like `SET GLOBAL`. + * - SESSION writes to one connection's session only, like `SET SESSION`. + * A scope neither the option (`duckdb_v2_option_supports_scope()`) nor the config supports returns ERROR_INPUT_INVALID; + * an instance's config supports only GLOBAL. The value is cast to the option's type as SQL `SET` does. An unknown name + * is rejected unless an extension that defines it can be autoloaded. An option that can only be chosen at startup is + * rejected: pass it to `duckdb_v2_instance_create_with_options()`. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_description Receives a borrowed view of the description. Valid until the option is destroyed. + * @param config The config to write to. + * @param name Option name (canonical or alias). + * @param value The value, cast to the option's type as SQL `SET` does. Borrowed. + * @param scope Target scope: DEFAULT, GLOBAL, or SESSION. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_description(duckdb_v2_option_handle option, - duckdb_v2_str *out_description, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_set_option_value(duckdb_v2_config_handle config, + const duckdb_v2_identifier_t *name, + duckdb_v2_value_handle value, + DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err); /*! - * Returns the option's target scope. + * Writes an option in this config from text, the form SQL `SET` accepts. * - * OPTION_TARGET_SCOPE_UNKNOWN for an option whose declaration carries no explicit scope target, which includes every - * extension option. + * `duckdb_v2_config_set_option_value()` with the text as a VARCHAR value: the engine casts it to the option's type, so + * this is the one-call form for a setting spelled the way SQL spells it, e.g. "1GB" for memory_limit. Scope handling + * and errors are `duckdb_v2_config_set_option_value()`'s. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_target_scope Receives the target scope. + * @param config The config to write to. + * @param name Option name (canonical or alias). + * @param setting The setting, in the textual form SQL `SET` accepts. + * @param scope Target scope: DEFAULT, GLOBAL, or SESSION. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_target_scope(duckdb_v2_option_handle option, - DUCKDB_V2_OPTION_TARGET_SCOPE *out_target_scope, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_set_option_text(duckdb_v2_config_handle config, + const duckdb_v2_identifier_t *name, + const duckdb_v2_str *setting, + DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of aliases registered for this option. + * Describes a config option registered on this config's instance, by name. * - * Zero for an extension option; extension options carry no aliases. + * Allocates an option descriptor: canonical name, default value, description, supported and default scopes, and + * aliases. Aliases resolve transparently — passing an alias returns the canonical option, with the alias listed in its + * alias array. An unknown name returns ERROR_INPUT_INVALID, which includes an option of an extension that has not + * loaded yet. The caller destroys the returned option. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param out_count Receives the alias count. + * @param config The config whose instance registered the options. + * @param name Option name (canonical or alias). + * @param out_option Receives the option descriptor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_alias_count(duckdb_v2_option_handle option, idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_get_option_by_name(duckdb_v2_config_handle config, + const duckdb_v2_identifier_t *name, + duckdb_v2_option_handle *out_option, + duckdb_v2_error_info_handle *err); /*! - * Borrows the alias name at the given index. + * Returns the number of config options registered on this config's instance. * - * An out-of-range index returns ERROR_INPUT_INVALID. + * Counts core options plus the extension options registered on this instance. Aliases are NOT counted separately; each + * one is reachable through its canonical option's alias array. * * history: * - stable: v2.0.0 * - * @param option The option. - * @param index The alias index, in [0, alias_count). - * @param out_alias Receives a borrowed view of the alias. Valid until the option is destroyed. + * @param config The config whose instance registered the options. + * @param out_count Receives the option count. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_alias(duckdb_v2_option_handle option, idx_t index, - duckdb_v2_identifier_t *out_alias, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_get_option_count(duckdb_v2_config_handle config, idx_t *out_count, + duckdb_v2_error_info_handle *err); /*! - * Reads a config option through a context. + * Describes the config option at the given index on this config's instance. * - * The context is a connection seen from inside DuckDB, so this reads the same cascade - * `duckdb_v2_connection_get_option_by_name()` does: the LOCAL override if the connection set one, otherwise the GLOBAL - * value, otherwise the static default. Aliases resolve transparently, and an unknown name returns ERROR_INPUT_INVALID. - * The caller destroys the returned option. A context is a read scope: options are written through an instance or - * connection. + * Index space: [0, core_count) addresses core options, [core_count, total) extension options. The mapping is stable for + * as long as no extension registers new options. An out-of-range index returns ERROR_INPUT_INVALID. The caller destroys + * the returned option. * * history: * - stable: v2.0.0 * - * @param ctx The context. - * @param name Option name (canonical or alias). - * @param out_option Receives the populated option handle. + * @param config The config whose instance registered the options. + * @param index The option index, in [0, option_count). + * @param out_option Receives the option descriptor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_option_by_name(duckdb_v2_context_handle ctx, - const duckdb_v2_identifier_t *name, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_config_get_option_by_index(duckdb_v2_config_handle config, idx_t index, duckdb_v2_option_handle *out_option, duckdb_v2_error_info_handle *err); /*! - * Returns the number of config options visible to this context. + * Destroys an option descriptor. * - * The same count `duckdb_v2_instance_get_option_count()` reports for the underlying instance: core plus extension - * options, aliases excluded. + * Frees the handle along with the strings it owns. On success the slot is set to null. Safe to call on an already-null + * slot. * * history: * - stable: v2.0.0 * - * @param ctx The context. - * @param out_count Receives the option count. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param option The option handle to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_option_count(duckdb_v2_context_handle ctx, idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_destroy(duckdb_v2_option_handle *option); /*! - * Reads the config option at the given index visible to this context. + * Borrows the option's canonical name. * - * Index space: [0, core_count) addresses core options, [core_count, total) the extension options visible from this - * context's instance. An out-of-range index returns ERROR_INPUT_INVALID. The caller destroys the returned option. + * When the option was resolved from an alias, this still reports the canonical name; the alias is reachable via + * `duckdb_v2_option_get_alias()`. * * history: * - stable: v2.0.0 * - * @param ctx The context. - * @param index The option index, in [0, option_count). - * @param out_option Receives the populated option handle. + * @param option The option. + * @param out_name Receives a borrowed view of the option name. Valid until the option is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_option_by_index(duckdb_v2_context_handle ctx, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - -/* --- Struct definitions for configuration --- */ - -/* ============================================================================ - * MODULE: custom type - * ============================================================================ */ - -/* --- Enums for custom type --- */ - -/* --- Struct forward declarations for custom type --- */ - -/* --- Types for custom type --- */ - -/*! - * An owned opaque handle to a custom type being built. Created with `duckdb_v2_custom_type_create_with_connection()` or - * `duckdb_v2_custom_type_create_with_extension()`, configured with `duckdb_v2_custom_type_set_name()` and - * `duckdb_v2_custom_type_set_base_type()`, made available with `duckdb_v2_custom_type_register()`, and destroyed with - * `duckdb_v2_custom_type_destroy()`. - */ -typedef struct _duckdb_v2_custom_type { - void *internal_ptr; -} * duckdb_v2_custom_type_handle; - -/* --- Constants for custom type --- */ - -/* --- Function pointer typedefs for custom type --- */ - -/* --- Functions for custom type --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_name(duckdb_v2_option_handle option, duckdb_v2_identifier_t *out_name, + duckdb_v2_error_info_handle *err); /*! - * Creates a new custom type that will be registered on the connection's database. + * Returns the option's static default, as a value of the option's type. * - * The type starts out empty: configure it with `duckdb_v2_custom_type_set_name()` and - * `duckdb_v2_custom_type_set_base_type()`, then make it available with `duckdb_v2_custom_type_register()`. The caller - * owns the returned handle and must destroy it with `duckdb_v2_custom_type_destroy()`, also after registration. + * A NULL value for an option whose declaration has no default. The caller destroys the returned value. * * history: * - stable: v2.0.0 * - * @param connection The connection to create the type in. - * @param type On success, receives the newly created custom type. Owned by the caller. + * @param option The option. + * @param out_value Receives the default value. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_custom_type_handle *type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_default_value(duckdb_v2_option_handle option, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Creates a new custom type that will be registered on the loading extension's database. - * - * Use this from an extension load callback, where an extension handle is available. The type starts out empty: - * configure it with `duckdb_v2_custom_type_set_name()` and `duckdb_v2_custom_type_set_base_type()`, then make it - * available with `duckdb_v2_custom_type_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_custom_type_destroy()`, also after registration. + * Borrows the option's human-readable description. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the type in. - * @param type On success, receives the newly created custom type. Owned by the caller. + * @param option The option. + * @param out_description Receives a borrowed view of the description. Valid until the option is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_custom_type_handle *type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_description(duckdb_v2_option_handle option, + duckdb_v2_str *out_description, + duckdb_v2_error_info_handle *err); /*! - * Sets the name of the custom type. + * Reports whether the option may be written at a scope. * - * This is the name the type is referred to by in SQL, and the alias carried by every logical type instance of it. The - * name is borrowed and copied. Calling this again replaces the previous name. A name must be set before registration. + * GLOBAL and SESSION are the scopes to ask about; DEFAULT is supported by every option that supports either. A write at + * an unsupported scope returns ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param type The type to set the name of. - * @param name The name to set. Borrowed and copied. + * @param option The option. + * @param scope The scope to ask about. + * @param out_supported Receives whether a write at the scope is accepted. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_set_name(duckdb_v2_custom_type_handle type, - const duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_supports_scope(duckdb_v2_option_handle option, + DUCKDB_V2_SETTING_SCOPE scope, bool *out_supported, + duckdb_v2_error_info_handle *err); /*! - * Sets the base type of the custom type. - * - * The custom type shares the base type's internal representation but is logically distinct, so it can carry its own - * cast functions. The type is borrowed and copied. Calling this again replaces the previous base type. A base type must - * be set before registration, and it must be a fully defined concrete type -- ANY is a signature wildcard, not - * something a registered type can be built on. + * Returns the scope a DEFAULT write of the option goes to: GLOBAL or SESSION. * * history: * - stable: v2.0.0 * - * @param type The type to set the base type of. - * @param base_type The logical type to base the custom type on. Borrowed for the call only. + * @param option The option. + * @param out_scope Receives the default scope. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_set_base_type(duckdb_v2_custom_type_handle type, - duckdb_v2_logical_type_handle base_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_default_scope(duckdb_v2_option_handle option, + DUCKDB_V2_SETTING_SCOPE *out_scope, + duckdb_v2_error_info_handle *err); /*! - * Registers the custom type, making it available for use in SQL queries. + * Returns the number of aliases registered for this option. * - * The type is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a name and a complete base type. The caller still owns the handle after registration and must - * destroy it with `duckdb_v2_custom_type_destroy()`, which does not affect the registered type. + * Zero for an extension option; extension options carry no aliases. * * history: * - stable: v2.0.0 * - * @param type The type to register. + * @param option The option. + * @param out_count Receives the alias count. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_register(duckdb_v2_custom_type_handle type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_alias_count(duckdb_v2_option_handle option, idx_t *out_count, + duckdb_v2_error_info_handle *err); /*! - * Destroys the custom type, releasing its resources. + * Borrows the alias name at the given index. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered type. + * An out-of-range index returns ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param type The type to destroy. + * @param option The option. + * @param index The alias index, in [0, alias_count). + * @param out_alias Receives a borrowed view of the alias. Valid until the option is destroyed. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_destroy(duckdb_v2_custom_type_handle *type); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_option_get_alias(duckdb_v2_option_handle option, idx_t index, + duckdb_v2_identifier_t *out_alias, + duckdb_v2_error_info_handle *err); -/* --- Struct definitions for custom type --- */ +/* --- Struct definitions for configuration --- */ /* ============================================================================ - * MODULE: data_chunk + * MODULE: connection * ============================================================================ */ -/* --- Enums for data_chunk --- */ +/* --- Enums for connection --- */ -/* --- Struct forward declarations for data_chunk --- */ +/* --- Struct forward declarations for connection --- */ -/* --- Types for data_chunk --- */ +/* --- Types for connection --- */ -/* --- Constants for data_chunk --- */ +/* --- Constants for connection --- */ -/* --- Function pointer typedefs for data_chunk --- */ +/* --- Function pointer typedefs for connection --- */ -/* --- Functions for data_chunk --- */ +/* --- Functions for connection --- */ /*! - * Creates an empty data chunk with the given column types. + * Opens a connection to an instance. * - * Allocates one FLAT vector per element of the types array, each at default capacity. Every vector starts at size 0; - * set it with vector_set_size once the vector is populated. The chunk is caller-owned and must be destroyed via - * data_chunk_destroy. + * Each connection carries its own client context and session-scoped (LOCAL) settings. Connections to the same instance + * share its catalog, buffer pool, and transaction manager. A connection may be created before any database is attached + * to the instance; until one is, only the system catalog and the connection's temporary catalog are visible. The caller + * destroys it via `duckdb_v2_connection_destroy()`. * * history: * - stable: v2.0.0 * - * @param types Pointer to an array of logical_type handles, one per column. - * @param column_count Number of elements in the types array. - * @param out_chunk Receives the new chunk handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param instance The instance to connect to. + * @param out_conn Receives the new connection handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_create(const duckdb_v2_logical_type_handle *types, idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create(duckdb_v2_instance_handle instance, + duckdb_v2_connection_handle *out_conn, duckdb_v2_error_info_handle *err); /*! - * Creates an empty data chunk with the given column types, drawing its allocator from the connection's context. + * Destroys the connection. * - * Like data_chunk_create, but the chunk's vectors are allocated through the connection's context instead of the default - * allocator, so the memory is accounted to that database. Allocates one FLAT vector per element of the types array, - * each at default capacity. Every vector starts at size 0; set it with vector_set_size once the vector is populated. - * The chunk is caller-owned and must be destroyed via data_chunk_destroy. + * Always succeeds. Releases the connection's reference on the underlying database instance, which survives for as long + * as anything else still references it. On success the handle is set to null. Safe to call on an already-null slot. * * history: * - stable: v2.0.0 * - * @param conn The connection whose context supplies the chunk's allocator. - * @param types Pointer to an array of logical_type handles, one per column. - * @param column_count Number of elements in the types array. - * @param out_chunk Receives the new chunk handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param conn The connection to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_create_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_logical_type_handle *types, - idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_destroy(duckdb_v2_connection_handle *conn); + +/*! + * Interrupts the query currently executing on the connection. + * + * The cross-thread (but not cross-connection) cancellation entry point for streaming results: safe to call from any + * thread within the execution of a query through a connection, including while another thread steps the query's result. + * A no-op when no query is active. Cancellation surfaces on the consuming side as step status CANCELLED + * (`duckdb_v2_result_step()`), or as ERROR_RUNTIME_INTERRUPT (`duckdb_v2_result_fetch_chunk()`). + * + * history: + * - stable: v2.0.0 + * + * @param conn The connection whose active query to interrupt. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_interrupt(duckdb_v2_connection_handle conn, + duckdb_v2_error_info_handle *err); /*! - * Creates an empty data chunk with the given column types, drawing its allocator from a context. + * Captures a snapshot of the active query's execution progress. + * + * Reads the percentage and row counts from one consistent snapshot of the query currently executing on the connection. + * Safe to call from any thread, including while another thread steps the query's result. * - * Like data_chunk_create, but the chunk's vectors are allocated through the given context instead of the default - * allocator, so the memory is accounted to that database. Use this from inside a callback or extension where a context - * is already in hand. Allocates one FLAT vector per element of the types array, each at default capacity. Every vector - * starts at size 0; set it with vector_set_size once the vector is populated. The chunk is caller-owned and must be - * destroyed via data_chunk_destroy. + * Progress is published only when the enable_progress_bar option is set; the bridge does not enable tracking itself. + * Both row counts are 0 when no information is available. The percentage is -1 when tracking is disabled, no query is + * active, or no progress has been published yet. * * history: * - stable: v2.0.0 * - * @param context The context whose allocator will be used for the chunk. - * @param types Pointer to an array of logical_type handles, one per column. - * @param column_count Number of elements in the types array. - * @param out_chunk Receives the new chunk handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param conn The connection. + * @param out_percentage Receives the percentage complete in [0, 100], -1 if tracking is disabled, no query is active, + * or no progress has been published yet. + * @param out_rows_processed Receives the number of rows processed so far. + * @param out_total_rows_to_process Receives the total number of rows the query will process. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_progress_get(duckdb_v2_connection_handle conn, double *out_percentage, + uint64_t *out_rows_processed, + uint64_t *out_total_rows_to_process, + duckdb_v2_error_info_handle *err); + +/* --- Struct definitions for connection --- */ + +/* ============================================================================ + * MODULE: custom type + * ============================================================================ */ + +/* --- Enums for custom type --- */ + +/* --- Struct forward declarations for custom type --- */ + +/* --- Types for custom type --- */ + +/*! + * An owned opaque handle to a custom type being built. Created with `duckdb_v2_custom_type_create()`, configured with + * `duckdb_v2_custom_type_set_name()` and `duckdb_v2_custom_type_set_base_type()`, made available with + * `duckdb_v2_connection_register_custom_type()` or `duckdb_v2_extension_register_custom_type()`, and destroyed with + * `duckdb_v2_custom_type_destroy()`. + */ +typedef struct _duckdb_v2_custom_type { + void *internal_ptr; +} * duckdb_v2_custom_type_handle; + +/* --- Constants for custom type --- */ + +/* --- Function pointer typedefs for custom type --- */ + +/* --- Functions for custom type --- */ + +/*! + * Creates a new custom type through a factory. + * + * The type starts out empty: configure it with `duckdb_v2_custom_type_set_name()` and + * `duckdb_v2_custom_type_set_base_type()`, then make it available with `duckdb_v2_connection_register_custom_type()` or + * `duckdb_v2_extension_register_custom_type()`. The caller owns the returned handle and must destroy it with + * `duckdb_v2_custom_type_destroy()`, also after registration. + * + * The custom type can only be registered on the database the factory belongs to. + * + * history: + * - stable: v2.0.0 + * + * @param factory The factory to create the custom type through. + * @param type On success, receives the newly created custom type. Owned by the caller. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_create(duckdb_v2_factory_handle factory, + duckdb_v2_custom_type_handle *type, + duckdb_v2_error_info_handle *err); + +/*! + * Sets the name of the custom type. + * + * This is the name the type is referred to by in SQL, and the alias carried by every logical type instance of it. The + * name is borrowed and copied. Calling this again replaces the previous name. A name must be set before registration. + * + * history: + * - stable: v2.0.0 + * + * @param type The type to set the name of. + * @param name The name to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_set_name(duckdb_v2_custom_type_handle type, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); + +/*! + * Sets the base type of the custom type. + * + * The custom type shares the base type's internal representation but is logically distinct, so it can carry its own + * cast functions. The type is borrowed and copied. Calling this again replaces the previous base type. A base type must + * be set before registration, and it must be a fully defined concrete type -- ANY is a signature wildcard, not + * something a registered type can be built on. + * + * history: + * - stable: v2.0.0 + * + * @param type The type to set the base type of. + * @param base_type The logical type to base the custom type on. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_set_base_type(duckdb_v2_custom_type_handle type, + duckdb_v2_logical_type_handle base_type, + duckdb_v2_error_info_handle *err); + +/*! + * Registers the custom type, making it available for use in SQL queries. + * + * The type is registered on the connection's database, visible to every connection, in the connection's transaction: + * inside an explicit transaction, it is invisible to other connections until the transaction commits, and a rollback + * undoes it. Registration requires a name and a complete base type. The custom type must have been created through a + * factory of the same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_custom_type_destroy()`, which does not affect the registered type. + * + * history: + * - stable: v2.0.0 + * + * @param conn The connection to register on. + * @param type The custom type to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_custom_type(duckdb_v2_connection_handle conn, + duckdb_v2_custom_type_handle type, + duckdb_v2_error_info_handle *err); + +/*! + * Registers the custom type, making it available for use in SQL queries. + * + * The type is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name and a complete base type. The custom type must have been + * created through a factory of the same database. The caller still owns the handle after registration and must destroy + * it with `duckdb_v2_custom_type_destroy()`, which does not affect the registered type. + * + * history: + * - stable: v2.0.0 + * + * @param extension The loading extension. + * @param type The custom type to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_create_with_context(duckdb_v2_context_handle context, - const duckdb_v2_logical_type_handle *types, - idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_custom_type(duckdb_v2_extension_handle extension, + duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err); /*! - * Creates a deep copy of a data chunk, drawing its allocator from the connection's context. + * Destroys the custom type, releasing its resources. * - * Allocates a new chunk with the source chunk's column types and copies all rows into it. The copy is flattened and - * owns all its data, so it stays valid after the source chunk or whatever backs it (such as a column data collection - * scan state) is destroyed. The returned chunk is caller-owned and must be destroyed via data_chunk_destroy. + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered type. * * history: * - stable: v2.0.0 * - * @param conn The connection whose context supplies the copy's allocator. - * @param chunk The chunk to copy. Left unchanged. + * @param type The type to destroy. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_type_destroy(duckdb_v2_custom_type_handle *type); + +/* --- Struct definitions for custom type --- */ + +/* ============================================================================ + * MODULE: data_chunk + * ============================================================================ */ + +/* --- Enums for data_chunk --- */ + +/* --- Struct forward declarations for data_chunk --- */ + +/* --- Types for data_chunk --- */ + +/* --- Constants for data_chunk --- */ + +/* --- Function pointer typedefs for data_chunk --- */ + +/* --- Functions for data_chunk --- */ + +/*! + * Creates an empty data chunk with the given column types, drawing its allocator from a factory. + * + * The chunk's vectors are allocated through the factory's database, so the memory is accounted to that database. + * Allocates one FLAT vector per element of the types array, each at default capacity. Every vector starts at size 0; + * set it with vector_set_size once the vector is populated. The chunk is caller-owned and must be destroyed via + * data_chunk_destroy. + * + * history: + * - stable: v2.0.0 + * + * @param factory The factory to create with. + * @param types Pointer to an array of logical_type handles, one per column. + * @param column_count Number of elements in the types array. * @param out_chunk Receives the new chunk handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_data_chunk_handle chunk, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_create(duckdb_v2_factory_handle factory, + const duckdb_v2_logical_type_handle *types, idx_t column_count, + duckdb_v2_data_chunk_handle *out_chunk, + duckdb_v2_error_info_handle *err); /*! - * Creates a deep copy of a data chunk, drawing its allocator from a context. + * Creates a deep copy of a data chunk, drawing its allocator from a factory. * - * Allocates a new chunk with the source chunk's column types and copies all rows into it. Use this from inside a - * callback or extension where a context is already in hand. The copy is flattened and owns all its data, so it stays - * valid after the source chunk or whatever backs it (such as a column data collection scan state) is destroyed. The - * returned chunk is caller-owned and must be destroyed via data_chunk_destroy. + * Allocates a new chunk with the source chunk's column types and copies all rows into it, through the factory's + * database. The copy is flattened and owns all its data, so it stays valid after the source chunk or whatever backs it + * (such as a column data collection scan state) is destroyed. The returned chunk is caller-owned and must be destroyed + * via data_chunk_destroy. * * history: * - stable: v2.0.0 * - * @param context The context whose allocator will be used for the copy. + * @param factory The factory to create with. * @param chunk The chunk to copy. Left unchanged. * @param out_chunk Receives the new chunk handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy_with_context(duckdb_v2_context_handle context, - duckdb_v2_data_chunk_handle chunk, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy(duckdb_v2_factory_handle factory, + duckdb_v2_data_chunk_handle chunk, + duckdb_v2_data_chunk_handle *out_chunk, + duckdb_v2_error_info_handle *err); /*! * Destroys a data chunk handle. @@ -2009,6 +2167,24 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy_with_context(duckdb_v2_co */ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_destroy(duckdb_v2_data_chunk_handle *chunk); +/*! + * Empties a data chunk so it can be filled again. + * + * The chunk's size becomes 0, and each of its vectors becomes a writable FLAT vector of its type again, as on creation, + * so a chunk can be reused for one batch after another instead of being created for each. Data, views and pointers + * taken from the chunk or its vectors beforehand do not survive this. Must not be called on a chunk DuckDB lends to a + * callback for reading. + * + * history: + * - stable: v2.0.0 + * + * @param chunk The chunk to clear. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_data_chunk_clear(duckdb_v2_data_chunk_handle chunk, + duckdb_v2_error_info_handle *err); + /*! * Returns the row count of a data chunk. * @@ -2312,41 +2488,119 @@ struct duckdb_v2_extension_input { }; /* ============================================================================ - * MODULE: file system + * MODULE: factory * ============================================================================ */ -/* --- Enums for file system --- */ - -/*! - * How `duckdb_v2_file_system_open()` opens a file. These are not a bitmask -- apply them one at a time with - * `duckdb_v2_file_open_options_set_flag()`, calling it once per behaviour you want, e.g. `FILE_FLAG_WRITE` then - * `FILE_FLAG_CREATE` to write to a file and create it when it does not exist. - */ -typedef enum DUCKDB_V2_FILE_FLAG { - //! Not a flag. The zero value, so that an uninitialized variable does not name a behaviour; applying it is an - //! error. - DUCKDB_V2_FILE_FLAG_INVALID = 0, - - //! Open the file with "read" capabilities. - DUCKDB_V2_FILE_FLAG_READ = 1, +/* --- Enums for factory --- */ - //! Open the file with "write" capabilities. - DUCKDB_V2_FILE_FLAG_WRITE = 2, +/* --- Struct forward declarations for factory --- */ - //! Create the file if it does not exist, and open it as it is if it does. - DUCKDB_V2_FILE_FLAG_CREATE = 3, +/* --- Types for factory --- */ - /*! - * Create the file if it does not exist, and truncate it to empty if it does. To fail instead of truncating, combine - * `FILE_FLAG_CREATE` with `FILE_FLAG_EXCLUSIVE_CREATE`. - */ - DUCKDB_V2_FILE_FLAG_CREATE_NEW = 4, +/* --- Constants for factory --- */ - //! Open the file in "append" mode. - DUCKDB_V2_FILE_FLAG_APPEND = 5, +/* --- Function pointer typedefs for factory --- */ - //! Fail if the file already exists. A modifier on `FILE_FLAG_CREATE`, and meaningless without it. - DUCKDB_V2_FILE_FLAG_EXCLUSIVE_CREATE = 6, +/* --- Functions for factory --- */ + +/*! + * Borrows the factory of an instance. + * + * The factory creates the built-in types and their values, casts with the built-in casts only, and allocates data + * chunks and column data collections through the instance. Anything that needs a catalog — catalog-registered types, + * qualified type names — returns ERROR_INPUT_INVALID. The returned handle is borrowed: it is valid for as long as the + * instance is, and must not be destroyed. + * + * history: + * - stable: v2.0.0 + * + * @param instance The instance. + * @param out_factory Receives the borrowed factory. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_factory(duckdb_v2_instance_handle instance, + duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err); + +/*! + * Borrows the factory of a connection. + * + * The factory reaches the connection's catalog and settings. Catalog lookups run in the connection's active + * transaction, or in a transaction of their own when none is open; nothing else opens a transaction. The returned + * handle is borrowed: it is valid for as long as the connection is, and must not be destroyed. + * + * history: + * - stable: v2.0.0 + * + * @param conn The connection. + * @param out_factory Receives the borrowed factory. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_factory(duckdb_v2_connection_handle conn, + duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err); + +/*! + * Borrows the factory of a context. + * + * The factory reaches the context's catalog and settings, within the transaction the context already has. The returned + * handle is borrowed: it is valid for as long as the context is, and must not be destroyed. + * + * history: + * - stable: v2.0.0 + * + * @param ctx The context. + * @param out_factory Receives the borrowed factory. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_factory(duckdb_v2_context_handle ctx, + duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err); + +/* --- Struct definitions for factory --- */ + +/* ============================================================================ + * MODULE: file system + * ============================================================================ */ + +/* --- Enums for file system --- */ + +/*! + * How `duckdb_v2_file_system_open()` opens a file. These are not a bitmask -- apply them one at a time with + * `duckdb_v2_file_open_options_set_flag()`, calling it once per behaviour you want, e.g. `FILE_FLAG_WRITE` then + * `FILE_FLAG_CREATE` to write to a file and create it when it does not exist. + */ +typedef enum DUCKDB_V2_FILE_FLAG { + //! Not a flag. The zero value, so that an uninitialized variable does not name a behaviour; applying it is an + //! error. + DUCKDB_V2_FILE_FLAG_INVALID = 0, + + //! Open the file with "read" capabilities. + DUCKDB_V2_FILE_FLAG_READ = 1, + + //! Open the file with "write" capabilities. + DUCKDB_V2_FILE_FLAG_WRITE = 2, + + //! Create the file if it does not exist, and open it as it is if it does. + DUCKDB_V2_FILE_FLAG_CREATE = 3, + + /*! + * Create the file if it does not exist, and truncate it to empty if it does. To fail instead of truncating, combine + * `FILE_FLAG_CREATE` with `FILE_FLAG_EXCLUSIVE_CREATE`. + */ + DUCKDB_V2_FILE_FLAG_CREATE_NEW = 4, + + //! Open the file in "append" mode. + DUCKDB_V2_FILE_FLAG_APPEND = 5, + + //! Fail if the file already exists. A modifier on `FILE_FLAG_CREATE`, and meaningless without it. + DUCKDB_V2_FILE_FLAG_EXCLUSIVE_CREATE = 6, /*! * The file will be read and written at explicit offsets from several threads at once. Pass it whenever @@ -2369,10 +2623,10 @@ typedef enum DUCKDB_V2_FILE_FLAG { /* --- Types for file system --- */ /*! - * A borrowed opaque handle to a file system. Obtained from `duckdb_v2_file_system_get_from_context()` or - * `duckdb_v2_file_system_get_from_connection()`, and used to open files with `duckdb_v2_file_system_open()`. Borrowed: - * the handle belongs to the context or connection it came from, is valid only for as long as that is, and must not be - * destroyed. + * A borrowed opaque handle to a file system. Obtained from `duckdb_v2_instance_get_file_system()`, + * `duckdb_v2_connection_get_file_system()` or `duckdb_v2_context_get_file_system()`, and used to open files with + * `duckdb_v2_file_system_open()`. Borrowed: the handle belongs to the instance, connection or context it came from, is + * valid only for as long as that is, and must not be destroyed. */ typedef struct _duckdb_v2_file_system { void *internal_ptr; @@ -2401,13 +2655,22 @@ typedef struct _duckdb_v2_file { /* --- Function pointer typedefs for file system --- */ +/*! + * Receives one path matched by `duckdb_v2_file_system_path_glob()`. + * + * The view is borrowed for the duration of the call only, and is not guaranteed to be null-terminated. Report an error + * through `err`, a live slot that must not be destroyed, to stop the glob: `duckdb_v2_file_system_path_glob()` then + * fails with that error. The callback must not throw or unwind across the boundary. + */ +typedef void (*duckdb_v2_file_system_path_glob_callback_fn)(const duckdb_v2_str *path, void *user_data, + duckdb_v2_error_info_handle *err); + /* --- Functions for file system --- */ /*! * Borrows the file system of a context. * - * Use this from inside a callback, where a context is in hand. The returned handle is borrowed: it is valid only for as - * long as the context is, and must not be destroyed. + * The returned handle is borrowed: it is valid only for as long as the context is, and must not be destroyed. * * history: * - stable: v2.0.0 @@ -2418,28 +2681,50 @@ typedef struct _duckdb_v2_file { * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_get_from_context(duckdb_v2_context_handle context, - duckdb_v2_file_system_handle *file_system, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_get_file_system(duckdb_v2_context_handle context, + duckdb_v2_file_system_handle *file_system, + duckdb_v2_error_info_handle *err); + +/*! + * Borrows the file system of an instance. + * + * Paths are resolved with the instance's GLOBAL settings and secrets, and file I/O is not attributed to any query. + * + * The returned handle is borrowed: it is valid only for as long as the instance is, and must not be destroyed. + * + * history: + * - stable: v2.0.0 + * + * @param instance The instance to take the file system from. + * @param file_system Receives the borrowed file system. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_file_system(duckdb_v2_instance_handle instance, + duckdb_v2_file_system_handle *file_system, + duckdb_v2_error_info_handle *err); /*! * Borrows the file system of a connection. * - * The connection form of `duckdb_v2_file_system_get_from_context()`, for callers outside a callback. The returned - * handle is borrowed: it is valid only for as long as the connection is, and must not be destroyed. + * Paths are resolved with the connection's settings and secrets, and file I/O is attributed to its running query, as + * through `duckdb_v2_context_get_file_system()` in a callback. + * + * The returned handle is borrowed: it is valid only for as long as the connection is, and must not be destroyed. * * history: * - stable: v2.0.0 * - * @param connection The connection to take the file system from. + * @param conn The connection to take the file system from. * @param file_system Receives the borrowed file system. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_get_from_connection(duckdb_v2_connection_handle connection, - duckdb_v2_file_system_handle *file_system, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_file_system(duckdb_v2_connection_handle conn, + duckdb_v2_file_system_handle *file_system, + duckdb_v2_error_info_handle *err); /*! * Creates a set of open options for a file system. @@ -2561,6 +2846,134 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_open(duckdb_v2_file_system_ha duckdb_v2_file_open_options_handle options, duckdb_v2_file_handle *file, duckdb_v2_error_info_handle *err); +/*! + * Reports whether a file exists at the given path. + * + * The path is routed the way `duckdb_v2_file_system_open()` routes it, so asking about a path handled by a remote file + * system may reach out to it. A directory is not a file, and reports false. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system to ask. + * @param path The path to check. Borrowed for the call only. + * @param out_exists Receives whether a file exists at the path. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_exists(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *path, bool *out_exists, + duckdb_v2_error_info_handle *err); + +/*! + * Reports whether a path names a remote file, e.g. one under `s3://`, `https://`, `gcs://` or `azure://`. + * + * Decided from the path alone: no file system is consulted, so it answers even when the extension providing the remote + * file system is not loaded. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system the path is meant for. + * @param path The path to check. Borrowed for the call only. + * @param out_is_remote Receives whether the path names a remote file. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_is_remote(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *path, bool *out_is_remote, + duckdb_v2_error_info_handle *err); + +/*! + * Expands a path as the file system does when opening it: a leading `~` becomes the home directory, as the + * `home_directory` setting configures it. + * + * A path without anything to expand is handed back as it is. The expanded path is handed to `sink` in a single call. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system whose settings expand the path. + * @param path The path to expand. Borrowed for the call only. + * @param sink Receives the expanded path in a single call. Borrowed for the duration of that call; see text_sink for + * the full contract. + * @param user_data Opaque pointer passed through to sink untouched. May be NULL. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_expand(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *path, duckdb_v2_text_sink_fn sink, + void *user_data, duckdb_v2_error_info_handle *err); + +/*! + * Finds the files matching a glob pattern, e.g. `*.csv` or `data_?.json` in a directory, handing each matched path to + * `callback`. + * + * The pattern is routed the way `duckdb_v2_file_system_open()` routes a path, so globbing a remote path may reach out + * to the file system handling it, and is only supported where that file system supports it. A pattern without glob + * characters matches the file it names, if there is one. Matching nothing is not an error. In which order the paths are + * handed over is up to the file system. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system to glob in. + * @param pattern The glob pattern. Borrowed for the call only. + * @param callback Invoked once per matched path. + * @param user_data Opaque pointer passed through to `callback` untouched. May be NULL. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_glob(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *pattern, + duckdb_v2_file_system_path_glob_callback_fn callback, + void *user_data, duckdb_v2_error_info_handle *err); + +/*! + * Moves, or renames, a file from one path to another. + * + * The move is handled by the file system `source` routes to, the way `duckdb_v2_file_system_open()` routes a path; + * moving a file between two file systems is not supported. Whether an existing file at `target` is replaced, and + * whether the move is atomic, is up to that file system. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system to move the file in. + * @param source The path of the file to move. Borrowed for the call only. + * @param target The path to move the file to. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_move(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *source, const duckdb_v2_str *target, + duckdb_v2_error_info_handle *err); + +/*! + * Removes the file at a path, if there is one. + * + * The path is routed the way `duckdb_v2_file_system_open()` routes it. A path with no file is not an error: + * `out_removed` then receives false. A file that exists but cannot be removed is. + * + * history: + * - stable: v2.0.0 + * + * @param file_system The file system to remove the file from. + * @param path The path of the file to remove. Borrowed for the call only. + * @param out_removed Optional. Receives whether a file was removed. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_file_system_path_remove(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *path, bool *out_removed, + duckdb_v2_error_info_handle *err); + /*! * Reads from the file into a caller-supplied buffer. * @@ -2873,8 +3286,8 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_function_bind_get_arg_type(duckdb_v2_func * Folds the argument at the given index to a constant value. * * Fails if the argument is not constant, e.g. a column reference, or if the index is out of bounds. The arguments of a - * table function are always constant. The resulting value may be NULL. The returned value is owned by the caller and - * must be destroyed via `duckdb_v2_value_destroy()`. + * table or pragma function are always constant. The resulting value may be NULL. The returned value is owned by the + * caller and must be destroyed via `duckdb_v2_value_destroy()`. * * history: * - stable: v2.0.0 @@ -2938,6 +3351,91 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_function_bind_get_arg_index(duckdb_v2_fun /* --- Struct definitions for function --- */ +/* ============================================================================ + * MODULE: function docs + * ============================================================================ */ + +/* --- Enums for function docs --- */ + +/* --- Struct forward declarations for function docs --- */ + +/* --- Types for function docs --- */ + +/*! + * A borrowed opaque handle to the documentation of a function overload. Taken from the function it documents with + * `duckdb_v2_scalar_function_get_docs()`, `duckdb_v2_aggregate_function_get_docs()`, + * `duckdb_v2_table_function_get_docs()`, `duckdb_v2_pragma_function_get_docs()` or + * `duckdb_v2_macro_function_get_docs()`, and valid for as long as that function handle is; the caller never destroys + * it. + */ +typedef struct _duckdb_v2_function_docs { + void *internal_ptr; +} * duckdb_v2_function_docs_handle; + +/* --- Constants for function docs --- */ + +/* --- Function pointer typedefs for function docs --- */ + +/* --- Functions for function docs --- */ + +/*! + * Sets the description of the function overload. + * + * The description is borrowed and copied. Calling this again replaces the previous description. + * + * history: + * - stable: v2.0.0 + * + * @param docs The documentation to set the description of. + * @param description The description to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_function_docs_set_description(duckdb_v2_function_docs_handle docs, + const duckdb_v2_str *description, + duckdb_v2_error_info_handle *err); + +/*! + * Adds an example of calling the function overload. + * + * The title and the example are borrowed and copied, and examples are reported in the order they were added. + * + * history: + * - stable: v2.0.0 + * + * @param docs The documentation to add the example to. + * @param title Optional. The title of the example; NULL or an empty view for none. Borrowed and copied. + * @param example The example, e.g. a SQL expression calling the function. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_function_docs_add_example(duckdb_v2_function_docs_handle docs, + const duckdb_v2_str *title, + const duckdb_v2_str *example, + duckdb_v2_error_info_handle *err); + +/*! + * Adds a category the function overload belongs to, e.g. "string" or "aggregate". + * + * The category is borrowed and copied, and categories are reported in the order they were added. + * + * history: + * - stable: v2.0.0 + * + * @param docs The documentation to add the category to. + * @param category The category. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_function_docs_add_category(duckdb_v2_function_docs_handle docs, + const duckdb_v2_str *category, + duckdb_v2_error_info_handle *err); + +/* --- Struct definitions for function docs --- */ + /* ============================================================================ * MODULE: function signature * ============================================================================ */ @@ -3115,11 +3613,12 @@ typedef struct _duckdb_v2_attach_options { /* --- Functions for instance --- */ /*! - * Creates an instance handle under the environment. + * Creates an instance under the environment with no startup options. * - * The handle starts with no database attached and with its instance not yet started; set startup options with - * `duckdb_v2_instance_set_option()`, then attach a database with `duckdb_v2_instance_attach()` and pick the default - * with `duckdb_v2_instance_set_default()`, or connect with `duckdb_v2_connection_create()`. The caller destroys it via + * The instance is ready on return, with no database attached: attach one with `duckdb_v2_instance_attach()` and pick + * the default with `duckdb_v2_instance_set_default()`, or connect with `duckdb_v2_connection_create()`. To choose an + * option that cannot change once the instance exists, such as access_mode, use + * `duckdb_v2_instance_create_with_options()` instead. The caller destroys the instance via * `duckdb_v2_instance_destroy()`. * * history: @@ -3135,6 +3634,37 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_create(duckdb_v2_environment_han duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); +/*! + * Creates an instance under the environment, configured with startup options. + * + * Behaves as `duckdb_v2_instance_create()`, applying the startup options first. + * + * Startup options are (name, value) pairs in two parallel arrays, the values in the textual form SQL `SET` accepts. + * They are the only way to choose an option that cannot change once the instance exists, such as access_mode or + * allow_unsigned_extensions; every other option can be written now or later through `duckdb_v2_instance_get_config()`. + * An option that needs a running instance (the logging options, allowed_directories, allowed_paths, + * disabled_filesystems) returns ERROR_INPUT_INVALID here: write it through the instance's config after creation. A name + * no core option or loaded extension defines is kept for an extension to consume while the instance starts up, and + * creation fails with the names none claimed. The caller destroys the instance via `duckdb_v2_instance_destroy()`. + * + * history: + * - stable: v2.0.0 + * + * @param env The environment. + * @param option_names Optional. An array of option_count option names (canonical or alias). Pass NULL when option_count + * is 0. + * @param option_values Optional. An array of option_count values, parallel to option_names, in the textual form SQL + * `SET` accepts. Pass NULL when option_count is 0. + * @param option_count The number of startup options. + * @param out_instance Receives the new instance handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_create_with_options( + duckdb_v2_environment_handle env, const duckdb_v2_str *option_names, const duckdb_v2_str *option_values, + idx_t option_count, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err); + /*! * Destroys the instance handle. * @@ -3151,27 +3681,32 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_create(duckdb_v2_environment_han DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_destroy(duckdb_v2_instance_handle *instance); /*! - * Attaches a database to the instance, starting it if it has not started yet. + * Attaches a database to the instance. * * Attaches the database at `path` exactly like `ATTACH 'path'`: - * - `:memory:` or an empty view attaches a fresh in-memory database named `memory`. - * - any other path attaches that file, creating it if it does not exist, under the name derived from the file's base - * name. `name` is the name to attach under, like `ATTACH ... AS name`; null, or an empty view, uses the name derived - * from the path. Naming is how two files with the same base name, or `:memory:` twice, attach side by side. `options` - * may be null; it carries the `(KEY value)` options of SQL `ATTACH`, for per-database options such as READ_ONLY, - * BLOCK_SIZE or ENCRYPTION_KEY, and must have been created from this instance handle. `make_default` makes the attached - * database the default for connections created afterwards, exactly as a `duckdb_v2_instance_set_default()` call right - * after the attach would; otherwise the default is left alone. A path that is already attached on this or any other - * instance of the environment returns ERROR_RESOURCE_IN_USE, and a name that is already attached fails. Options that - * only apply at startup must be set before the first attach or connection. + * - `:memory:` or an empty view attaches a fresh in-memory database. + * - any other path attaches that file, creating it if it does not exist. + * `name` is the name to attach under, like `ATTACH ... AS name`; it is how `duckdb_v2_instance_detach()` and + * `duckdb_v2_instance_set_default()` refer to the database. Null, or an empty view, derives the name from the path the + * way SQL `ATTACH` without `AS` does: + * - `memory` for `:memory:` or an empty view; + * - otherwise the file name up to its first `.`, without any `?query` parameters (`/data/sales.2024.db` attaches as + * `sales`), with `_db` appended when that is a reserved name such as `system` or `temp`. Naming is how two files with + * the same base name, or `:memory:` twice, attach side by side. `options` may be null; it carries the `(KEY value)` + * options of SQL `ATTACH`, for per-database options such as READ_ONLY, BLOCK_SIZE or ENCRYPTION_KEY, and must have been + * created from this instance handle. `make_default` makes the attached database the default for connections created + * afterwards, exactly as a `duckdb_v2_instance_set_default()` call right after the attach would; otherwise the default + * is left alone. A path that is already attached on this or any other instance of the environment returns + * ERROR_RESOURCE_IN_USE, and a name that is already attached fails. Options that only apply at startup are passed to + * `duckdb_v2_instance_create_with_options()`. * * history: * - stable: v2.0.0 * * @param instance The instance handle. - * @param path Path to the database file, or `:memory:` / an empty view for an in-memory database. * @param name Optional. The name to attach under; null or an empty view for the name derived from the path. Borrowed * and copied. + * @param path Path to the database file, or `:memory:` / an empty view for an in-memory database. * @param options Optional attach options created from `instance`, or null to attach with defaults under the derived * name. * @param make_default Whether to make the attached database the default for connections created afterwards. @@ -3179,55 +3714,54 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_destroy(duckdb_v2_instance_handl * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_attach(duckdb_v2_instance_handle instance, const duckdb_v2_str *path, - const duckdb_v2_identifier_t *name, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_attach(duckdb_v2_instance_handle instance, + const duckdb_v2_identifier_t *name, const duckdb_v2_str *path, duckdb_v2_attach_options_handle options, bool make_default, duckdb_v2_error_info_handle *err); /*! - * Detaches the database that was attached from `path`. + * Detaches the database attached under `name`. * * Detaches it like `DETACH`, checkpointing a file database first. Unlike SQL, the default database may be detached too; * new connections then start without a default until `duckdb_v2_instance_set_default()` names another, and existing - * connections bound to it keep it alive until their transaction on it ends and fail unqualified DDL afterwards. The - * path is matched against the attached databases as `duckdb_v2_instance_attach()` recorded it, so pass the path that - * was passed to `duckdb_v2_instance_attach()`. Returns ERROR_INPUT_INVALID when no database attached from that path - * exists. Connections that still reference the database keep it alive until they release it. + * connections bound to it keep it alive until their transaction on it ends and fail unqualified DDL afterwards. Returns + * ERROR_INPUT_INVALID when no database is attached under that name. Connections that still reference the database keep + * it alive until they release it. * * history: * - stable: v2.0.0 * * @param instance The instance handle. - * @param path The path the database was attached from, or the name it is attached under. + * @param name The name the database is attached under. Borrowed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_detach(duckdb_v2_instance_handle instance, const duckdb_v2_str *path, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_detach(duckdb_v2_instance_handle instance, + const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); /*! - * Makes the database that was attached from `path` the default database for connections created from now on. + * Makes the database attached under `name` the default database for connections created from now on. * * A connection binds to the default database when it is created, so this does not retarget existing connections: their * default stays what it was when they connected, or what they chose with `USE`. The default is where unqualified DDL * and unqualified table lookups that miss the temporary catalog go. It stays the default for new connections until * another `duckdb_v2_instance_set_default()` call or until it is detached; a connection whose default has been detached - * fails unqualified DDL with a message saying so until it selects another with `USE`. `path` is either the path that - * was passed to `duckdb_v2_instance_attach()` or the name the database is attached under; a name match wins. Returns - * ERROR_INPUT_INVALID when neither matches an attached database. + * fails unqualified DDL with a message saying so until it selects another with `USE`. Returns ERROR_INPUT_INVALID when + * no database is attached under that name. * * history: * - stable: v2.0.0 * * @param instance The instance handle. - * @param path The path the database was attached from, or the name it is attached under. + * @param name The name the database is attached under. Borrowed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_set_default(duckdb_v2_instance_handle instance, - const duckdb_v2_str *path, + const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err); /*! @@ -3287,105 +3821,17 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_attach_options_set(duckdb_v2_attach_optio DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_attach_options_destroy(duckdb_v2_attach_options_handle *options); /*! - * Sets a config option on the instance (GLOBAL scope). - * - * Before the instance has started (no `duckdb_v2_instance_attach()` or `duckdb_v2_connection_create()` yet), the option - * goes into the startup configuration: this is the only way to set an option that can only be chosen at startup, such - * as access_mode or enable_external_access. Unknown names are kept for an extension to consume at startup; if none - * does, startup fails with the unrecognized names. After startup, this is `SET GLOBAL name = setting` and an unknown - * name is rejected unless an extension that defines it can be autoloaded. Returns ERROR_INPUT_INVALID for an option - * declared LOCAL_ONLY, and for a legacy option with no global setter. + * Returns the version of the linked DuckDB library. * * history: * - stable: v2.0.0 * - * @param instance The instance handle. - * @param name Option name (canonical or alias). - * @param setting The setting, in the textual form SQL `SET` accepts. + * @param out_version The version string. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_set_option(duckdb_v2_instance_handle instance, - const duckdb_v2_identifier_t *name, - const duckdb_v2_str *setting, - duckdb_v2_error_info_handle *err); - -/*! - * Reads a config option from the instance (GLOBAL scope) by name. - * - * Allocates a fully populated option: canonical name, current GLOBAL setting, default setting, description, target - * scope, and aliases. Before startup the current setting is the staged startup value, or the default. Aliases resolve - * transparently — passing an alias returns the canonical option, with the alias listed in its alias array. An unknown - * name returns ERROR_INPUT_INVALID; before startup that includes an option of an extension that has not loaded yet, - * even if a setting for it has been staged. The caller destroys the returned option. - * - * history: - * - stable: v2.0.0 - * - * @param instance The instance handle. - * @param name Option name (canonical or alias). - * @param out_option Receives the populated option handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_option_by_name(duckdb_v2_instance_handle instance, - const duckdb_v2_identifier_t *name, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - -/*! - * Returns the number of config options registered on the instance. - * - * Counts core options plus the extension options registered on this instance. Aliases are NOT counted separately; each - * one is reachable through its canonical option's alias array. - * - * history: - * - stable: v2.0.0 - * - * @param instance The instance handle. - * @param out_count Receives the option count. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_option_count(duckdb_v2_instance_handle instance, idx_t *out_count, - duckdb_v2_error_info_handle *err); - -/*! - * Reads the config option at the given index from the instance. - * - * Index space: [0, core_count) addresses core options, [core_count, total) extension options. The mapping is stable for - * as long as no extension registers new options. An out-of-range index returns ERROR_INPUT_INVALID. The caller destroys - * the returned option. - * - * history: - * - stable: v2.0.0 - * - * @param instance The instance handle. - * @param index The option index, in [0, option_count). - * @param out_option Receives the populated option handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_get_option_by_index(duckdb_v2_instance_handle instance, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); - -/*! - * Returns the version of the linked DuckDB library. - * - * history: - * - stable: v2.0.0 - * - * @param out_version The version string. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_library_version(duckdb_v2_str *out_version, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_library_version(duckdb_v2_str *out_version, duckdb_v2_error_info_handle *err); /* --- Struct definitions for instance --- */ @@ -3452,6 +3898,50 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_log(duckdb_v2_context_handle ctx, const duckdb_v2_str *log_type, const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); +/*! + * Writes a message to DuckDB's log. + * + * The entry is attributed to the database scope: it carries no connection or query ids. Whether it is recorded at all + * is up to the database's log configuration: if logging is off, or level is below the configured threshold, or log_type + * is not among the enabled types, the call succeeds and writes nothing. An empty log_type selects the default type. + * Read entries back with `SELECT * FROM duckdb_logs`. + * + * history: + * - stable: v2.0.0 + * + * @param instance The instance to log through. + * @param level Severity of the message. + * @param log_type The log type to record under, matched case-sensitively. Empty selects the default type. + * @param message The message body. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_log(duckdb_v2_instance_handle instance, DUCKDB_V2_LOG_LEVEL level, + const duckdb_v2_str *log_type, const duckdb_v2_str *message, + duckdb_v2_error_info_handle *err); + +/*! + * Writes a message to DuckDB's log. + * + * The entry is attributed to the connection scope, so it carries the connection's ids. Whether it is recorded at all is + * up to the database's log configuration: if logging is off, or level is below the configured threshold, or log_type is + * not among the enabled types, the call succeeds and writes nothing. An empty log_type selects the default type. Read + * entries back with `SELECT * FROM duckdb_logs`. + * + * history: + * - stable: v2.0.0 + * + * @param conn The connection to log through. + * @param level Severity of the message. + * @param log_type The log type to record under, matched case-sensitively. Empty selects the default type. + * @param message The message body. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_log(duckdb_v2_connection_handle conn, DUCKDB_V2_LOG_LEVEL level, + const duckdb_v2_str *log_type, const duckdb_v2_str *message, + duckdb_v2_error_info_handle *err); + /* --- Struct definitions for logging --- */ /* ============================================================================ @@ -3854,2626 +4344,2388 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_token_iterator_destroy(duckdb_v2_token_it /* --- Struct definitions for tokenizer --- */ /* ============================================================================ - * MODULE: vector + * MODULE: aggregate * ============================================================================ */ -/* --- Enums for vector --- */ +/* --- Enums for aggregate --- */ + +/* --- Struct forward declarations for aggregate --- */ + +/* --- Types for aggregate --- */ /*! - * Internal representation of a vector, with FSST / SEQUENCE / SHREDDED collapsed into OTHER: the view getter rejects - * those and requires an explicit vector_flatten first. OTHER is the 0-value, so a zero-initialized out-param reads as - * "unspecified, needs flatten" rather than silently looking like FLAT. + * An owned opaque handle to a custom aggregate function being built. Created with + * `duckdb_v2_aggregate_function_create()`, configured with the setter functions (e.g. + * `duckdb_v2_aggregate_function_set_name()`, `duckdb_v2_aggregate_function_set_update_callback()`, etc.) and the + * signature obtained via `duckdb_v2_aggregate_function_get_signature()`, made available with + * `duckdb_v2_connection_register_aggregate_function()` or `duckdb_v2_extension_register_aggregate_function()`, and + * destroyed with `duckdb_v2_aggregate_function_destroy()`. */ -typedef enum DUCKDB_V2_VECTOR_TYPE { - //! Default for zero-init. Covers FSST / SEQUENCE / SHREDDED — call vector_flatten first. - DUCKDB_V2_VECTOR_TYPE_OTHER = 0, +typedef struct _duckdb_v2_aggregate_function { + void *internal_ptr; +} * duckdb_v2_aggregate_function_handle; - //! Standard per-row storage. - DUCKDB_V2_VECTOR_TYPE_FLAT = 1, +/*! + * A borrowed opaque handle to the result of an aggregate function's "bind" phase. The "bind" callback receives this + * handle next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments, the user data and the + * bind data, and can use it to set the return type of the call site being bound. + */ +typedef struct _duckdb_v2_aggregate_function_bind_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_bind_info_handle; - //! A single value applies to every row in the vector. - DUCKDB_V2_VECTOR_TYPE_CONSTANT = 2, +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the state sizing "size" phase. The + * "size" callback receives this handle and must use it to report the size of a single aggregate state in bytes. + */ +typedef struct _duckdb_v2_aggregate_function_size_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_size_info_handle; - //! Data + selection vector indirection into another vector. - DUCKDB_V2_VECTOR_TYPE_DICTIONARY = 3, - DUCKDB_V2_VECTOR_TYPE_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_VECTOR_TYPE; +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the state initialization "init" + * phase. The "init" callback receives this handle and can use it to access the array of aggregate states it must + * initialize in place. + */ +typedef struct _duckdb_v2_aggregate_function_init_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_init_info_handle; -/* --- Struct forward declarations for vector --- */ +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the "update" phase. The "update" + * callback receives this handle and can use it to access the input argument vectors, the number of rows to process, and + * the aggregate state each row must be aggregated into. + */ +typedef struct _duckdb_v2_aggregate_function_update_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_update_info_handle; -typedef struct duckdb_v2_vector_view duckdb_v2_vector_view; +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the "combine" phase. The "combine" + * callback receives this handle and can use it to access the source and target aggregate state arrays to merge. + */ +typedef struct _duckdb_v2_aggregate_function_combine_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_combine_info_handle; -typedef struct duckdb_v2_list_entry duckdb_v2_list_entry; +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the "finalize" phase. The + * "finalize" callback receives this handle and can use it to access the aggregate states and the result vector and + * offset to write their final values to. + */ +typedef struct _duckdb_v2_aggregate_function_finalize_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_finalize_info_handle; -typedef struct duckdb_v2_hugeint_t duckdb_v2_hugeint_t; +/*! + * A borrowed opaque handle to the arguments supplied to an aggregate function during the state destruction "destroy" + * phase. The "destroy" callback receives this handle and can use it to access the array of aggregate states whose + * resources must be released. + */ +typedef struct _duckdb_v2_aggregate_function_destroy_info { + void *internal_ptr; +} * duckdb_v2_aggregate_function_destroy_info_handle; -typedef struct duckdb_v2_uhugeint_t duckdb_v2_uhugeint_t; +/* --- Constants for aggregate --- */ -typedef struct duckdb_v2_interval_t duckdb_v2_interval_t; +/* --- Function pointer typedefs for aggregate --- */ -/* --- Types for vector --- */ +typedef void (*duckdb_v2_aggregate_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, + duckdb_v2_aggregate_function_bind_info_handle result, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); -/* --- Constants for vector --- */ +typedef void (*duckdb_v2_aggregate_function_size_callback_fn)(duckdb_v2_aggregate_function_size_info_handle info, + duckdb_v2_error_info_handle *err); -/* --- Function pointer typedefs for vector --- */ +typedef void (*duckdb_v2_aggregate_function_init_callback_fn)(duckdb_v2_aggregate_function_init_info_handle info, + duckdb_v2_error_info_handle *err); -/* --- Functions for vector --- */ +typedef void (*duckdb_v2_aggregate_function_update_callback_fn)(duckdb_v2_aggregate_function_update_info_handle info, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_aggregate_function_combine_callback_fn)(duckdb_v2_aggregate_function_combine_info_handle info, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_aggregate_function_finalize_callback_fn)( + duckdb_v2_aggregate_function_finalize_info_handle info, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_aggregate_function_destroy_callback_fn)(duckdb_v2_aggregate_function_destroy_info_handle info, + duckdb_v2_error_info_handle *err); + +/* --- Functions for aggregate --- */ /*! - * Returns the internal representation kind: flat, constant, or dictionary. + * Creates a new aggregate function through a factory. * - * Reports VECTOR_TYPE_OTHER for FSST / SEQUENCE / SHREDDED vectors, which must be passed through vector_flatten before - * vector_get_view will read them. + * The function starts out empty: configure it with the setter functions (e.g. + * `duckdb_v2_aggregate_function_set_name()`, `duckdb_v2_aggregate_function_set_update_callback()`, etc.) and the + * signature obtained via `duckdb_v2_aggregate_function_get_signature()`, then make it available with + * `duckdb_v2_connection_register_aggregate_function()` or `duckdb_v2_extension_register_aggregate_function()`. The + * caller owns the returned handle and must destroy it with `duckdb_v2_aggregate_function_destroy()`, also after + * registration. + * + * The aggregate function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_type Receives the representation kind. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param factory The factory to create the aggregate function through. + * @param function On success, receives the newly created aggregate function. Owned by the caller. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_vector_type(duckdb_v2_vector_handle vector, - DUCKDB_V2_VECTOR_TYPE *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_aggregate_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Returns the vector's logical type. + * Sets the name of the aggregate function. * - * The returned logical type is the same type that was passed to vector_reference, or the type that was set on creation. - * It is caller-owned; destroy it via logical_type_destroy. + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_type Receives the caller-owned logical type handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_logical_type(duckdb_v2_vector_handle vector, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_name(duckdb_v2_aggregate_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Reads a vector as a unified view of data, validity, selection, and count. - * - * Populates out_view with borrowed pointers, valid until the owning chunk is destroyed. view.data addresses the - * vector's elements, to be cast to the layout its logical type implies: a typed leaf array for the primitives, - * hugeint_t / uhugeint_t / interval_t for those kinds, bytes for VARCHAR / BLOB / BIT / BIGNUM, list_entry for LIST and - * MAP, and nothing at all for ARRAY / STRUCT / UNION, whose data lives in their children. - * - * DECIMAL and ENUM are typed leaf arrays too, over a storage tier that follows from the type; both mappings are - * contract. A DECIMAL of width <= 4 stores int16, <= 9 int32, <= 18 int64, and <= 38 int128, boundaries inclusive, with - * the width and scale available from logical_type_get_param. An ENUM stores its dictionary indices as uint8 for a - * dictionary of at most 255 entries, uint16 for at most 65535, and uint32 beyond that, boundaries inclusive and an - * empty dictionary uint8; logical_type_get_param resolves an index to its entry. + * Returns the documentation of the function so it can be filled in. * - * Rejects VECTOR_TYPE_OTHER; call vector_flatten first. - * - * Reading a DICTIONARY vector has one side effect: the dictionary's underlying child is flattened in place if it is not - * already FLAT. The parent vector stays DICTIONARY, and only the child's storage shape changes, but any pointer - * previously borrowed into that child — from an earlier vector_get_view on a vector aliasing the same buffer, say — is - * invalidated. This is the only mutation vector_get_view performs; compare vector_flatten, which materializes the whole - * vector to FLAT. + * Set a description with `duckdb_v2_function_docs_set_description()`, and add examples and categories with + * `duckdb_v2_function_docs_add_example()` and `duckdb_v2_function_docs_add_category()`. The documentation is modified + * in place, describes this overload only, and is registered with the function. Optional. * * history: * - stable: v2.0.0 * - * @param vector The vector to read. - * @param out_view Receives the view. Its pointers are borrowed and valid until the owning chunk is destroyed. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to get the documentation of. + * @param docs The returned documentation. Borrowed and valid for the lifetime of the function handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_view(duckdb_v2_vector_handle vector, duckdb_v2_vector_view *out_view, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_get_docs(duckdb_v2_aggregate_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of elements in the vector. + * Returns the function's signature so it can be configured. * - * The size is the number of logical elements the vector represents, which for a nested kind can differ from the size of - * its children. An ARRAY vector of size 10 whose array size is 3, for instance, has 10 logical elements while its - * single child vector has 30. + * Add parameters with `duckdb_v2_function_signature_add_parameter()` and set the return type with + * `duckdb_v2_function_signature_set_return_type()`. The signature is modified in place; the function must be given a + * signature with a return type before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_size Receives the number of elements. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to get the signature of. + * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_size(duckdb_v2_vector_handle vector, idx_t *out_size, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_get_signature(duckdb_v2_aggregate_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); /*! - * Sets the number of elements in the vector. + * Sets arbitrary user data on the aggregate function. * - * The counterpart of vector_get_size: it declares how many logical elements the vector now holds. It reserves enough - * space for size logical elements. + * Associates an opaque pointer with the function, retrievable from each callback via its user data accessor (e.g. + * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_aggregate_function_update_get_user_data()`, etc.). The opaque + * handle bundles the pointer with an optional destructor, invoked when the data is no longer needed. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param size The new number of logical elements. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_size(duckdb_v2_vector_handle vector, idx_t size, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_user_data(duckdb_v2_aggregate_function_handle function, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Reads one cell of a vector as an owned value. - * - * The total fallback reader. It reads any logical row of any vector representation without flattening — FLAT, CONSTANT, - * DICTIONARY, and the compressed kinds the view getter rejects — and covers every type kind, including those with no - * committed view layout (today VARIANT and GEOMETRY), where it is the only way to reach a cell. + * Sets a property on the aggregate function. * - * Each call allocates one owned value, which makes this a single-cell bridge rather than a per-row loop primitive; use - * vector_get_view on hot paths. The row index is logical and bounds-checked against vector_get_size. The returned value - * is caller-owned; destroy it via value_destroy. + * Configures a function property that influences planning and execution, such as stability, NULL handling, fallibility, + * collation handling, or the aggregate-specific order/DISTINCT dependence. The value must be one of the + * `DUCKDB_V2_FUNCTION_PROPERTY_VALUE` entries belonging to `key`; passing a value that does not belong to the key, or a + * key that is not valid for aggregate functions, results in an error. * * history: * - stable: v2.0.0 * - * @param vector The vector to read from. - * @param row The logical row index, in [0, vector_get_size). - * @param out_value Receives the owned cell value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the property of. + * @param key The property to set. + * @param value The value to set for the property. Must belong to `key`. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_value(duckdb_v2_vector_handle vector, idx_t row, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_property(duckdb_v2_aggregate_function_handle function, + DUCKDB_V2_FUNCTION_PROPERTY_KEY key, + DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, + duckdb_v2_error_info_handle *err); /*! - * Writes one cell of a FLAT vector from a value. - * - * The total fallback writer. It covers every type kind, including those with no committed view layout (today VARIANT - * and GEOMETRY), where it is the only way to write a cell. The value is borrowed and copied in, then cast to the - * vector's type through the non-strict default cast; a cast failure surfaces from the call. A NULL value clears the - * row's validity. + * Sets the optional bind callback of the aggregate function. * - * Requires a FLAT vector: constant, dictionary, and compressed representations are not row-addressable, so call - * vector_flatten first. Each call performs one value write, which makes this a single-cell bridge rather than a per-row - * loop primitive; use the typed mutable-data paths on hot paths. The row index is logical and bounds-checked against - * vector_get_size. + * The bind callback is invoked during query planning for each call site of the function. Through its + * `duckdb_v2_function_bind_info_handle` it can inspect the argument types and constant argument values and set "bind + * data" that is shared with the other callbacks. Through its `duckdb_v2_aggregate_function_bind_info_handle` it can set + * a concrete return type. * * history: * - stable: v2.0.0 * - * @param vector The vector to write to. - * @param row The logical row index, in [0, vector_get_size). - * @param value The borrowed value to write. Copied in; cast to the vector's type. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the bind callback of. + * @param callback The bind callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_value(duckdb_v2_vector_handle vector, idx_t row, - duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_bind_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_bind_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns a mutable pointer to the data of a FLAT or CONSTANT vector. - * - * Any other representation returns ERROR_INPUT_INVALID. The pointer is valid until the owning chunk is destroyed, and - * the vector's storage shape must not change — through a flatten, say — while it is in use. + * Sets the size callback of the aggregate function. * - * When writing VARCHAR values, the caller must ensure that the bytes contain valid UTF-8. + * The size callback reports the size of a single aggregate state in bytes via + * `duckdb_v2_aggregate_function_size_set_state_size()`; DuckDB uses it to allocate state memory. A size callback must + * be set before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_data Receives a mutable pointer to the data. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the size callback of. + * @param callback The size callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_data_mutable(duckdb_v2_vector_handle vector, void **out_data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_size_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_size_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Forces a vector into FLAT representation, in place. + * Sets the init callback of the aggregate function. * - * A no-op for a vector that is already FLAT. For a CONSTANT, DICTIONARY, FSST, SEQUENCE, or SHREDDED vector it - * materializes the per-row data and switches the representation to FLAT. Call it before vector_get_view if you would - * rather not handle CONSTANT and DICTIONARY semantics in the view; it is required for any vector reporting - * VECTOR_TYPE_OTHER. + * The init callback initializes freshly allocated aggregate states in place, obtained via + * `duckdb_v2_aggregate_function_init_get_states()`. An init callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector to flatten. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the init callback of. + * @param callback The init callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_flatten(duckdb_v2_vector_handle vector, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_init_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_init_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Repoints a vector at another vector's data, without copying. + * Sets the update callback of the aggregate function. * - * `vector` takes on the storage of `source`: no data moves, and the two alias the same buffers until one of them is - * reset or re-referenced. Works for any type, including nested types. The source's logical type must equal the - * vector's; a mismatch returns ERROR_INPUT_INVALID. The source's data must outlive every read of `vector`. Use it to - * hand an already-materialized vector straight to an output vector without a per-row copy. + * The update callback is invoked during query execution with a batch of input rows: row i of every argument vector must + * be aggregated into state i of the state array. An update callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector to repoint at the source's data. - * @param source The vector whose data to reference. Its logical type must equal the vector's. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the update callback of. + * @param callback The update callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_reference(duckdb_v2_vector_handle vector, duckdb_v2_vector_handle source, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_update_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_update_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Turns the vector into a CONSTANT vector holding the given value. + * Sets the combine callback of the aggregate function. * - * Afterwards the vector holds a single element that applies to every logical row; write it through - * vector_get_data_mutable, which returns a pointer to that one element. For a STRUCT vector the change propagates to - * every child. The value's logical type must equal the vector's; a mismatch returns ERROR_INPUT_INVALID. + * The combine callback merges partial aggregate states computed in parallel: source state i must be combined into + * target state i. A combine callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector to make constant. - * @param value The value every row takes. Its logical type must equal the vector's. - * @param count The number of elements in the vector. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the combine callback of. + * @param callback The combine callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_make_constant(duckdb_v2_vector_handle vector, - duckdb_v2_value_handle value, idx_t count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_combine_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_combine_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Sets a vector to a SEQUENCE representation. + * Sets the finalize callback of the aggregate function. * - * Afterwards the vector represents the arithmetic sequence start, start+increment, start+2*increment, ... over count - * elements. No data pointer is involved; the three parameters describe the sequence completely. + * The finalize callback produces the function's result: state i must be finalized into result row offset + i of the + * result vector. A finalize callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param vector The vector to turn into a sequence. - * @param start The first value of the sequence. - * @param increment The step between consecutive values. - * @param count The number of elements in the sequence. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the finalize callback of. + * @param callback The finalize callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_make_sequence(duckdb_v2_vector_handle vector, int64_t start, - int64_t increment, idx_t count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_finalize_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_finalize_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Sets one row of a FLAT vector to NULL, nested children included. - * - * Clears the row's validity and broadcasts the NULL recursively to every descendant element under that row — STRUCT - * fields, and ARRAY elements strided by the array size — maintaining DuckDB's nested NULL invariant: when a STRUCT or - * ARRAY row is NULL, every descendant element under it must be NULL too. LIST children are left untouched, since their - * consumers gate on the list's own validity. Which kinds propagate is normative DuckDB behavior that evolves with the - * type system, which makes this the total NULL write path: equivalent to vector_set_value with a NULL value, without - * the per-call value allocation. - * - * Requires a FLAT vector: constant, dictionary, and compressed representations are not row-addressable, so call - * vector_flatten first. The row index is logical and bounds-checked against vector_get_size. + * Sets the destroy callback for the aggregate function. Optional: only needed when aggregate states own resources that + * must be released. The callback runs on states that are discarded without being finalized; it must not fail, an error + * reported through its error slot is ignored. * * history: * - stable: v2.0.0 * - * @param vector The vector to write to. - * @param row The logical row index, in [0, vector_get_size). - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the destroy callback of. + * @param callback The destroy callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_null(duckdb_v2_vector_handle vector, idx_t row, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_destroy_callback( + duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_destroy_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns a mutable pointer to the validity mask of a FLAT vector. - * - * Allocates the mask on first use, so the returned pointer is always non-null. Each uint64_t word covers 64 rows: bit N - * of word W is row W*64+N. A set bit means valid; a cleared bit means NULL. + * Sets the concrete return type of the call site being bound. * - * A raw mask write marks only this vector's rows. Clearing a STRUCT or ARRAY parent bit leaves the descendant elements - * untouched, which violates DuckDB's nested NULL invariant — a NULL STRUCT or ARRAY row requires NULL descendants — so - * set nested rows NULL through vector_set_null instead. When writing nested masks raw anyway, invalidate the child - * masks up front and mark elements valid as the values are written. + * Overrides the return type declared in the signature. Register the function with an ANY return type and set the + * concrete type here, derived from the argument types, to build a function whose result type depends on its input. The + * type is borrowed and copied. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_validity Receives a mutable pointer to the validity mask. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The bind info handle. + * @param return_type The return type to set. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_flat_get_validity_mutable(duckdb_v2_vector_handle vector, - uint64_t **out_validity, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_bind_set_return_type( + duckdb_v2_aggregate_function_bind_info_handle info, duckdb_v2_logical_type_handle return_type, + duckdb_v2_error_info_handle *err); /*! - * Sets the validity of a CONSTANT vector. - * - * A constant vector holds a single element, so one flag decides whether every row reads as the value or as NULL. + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param vector The constant vector. - * @param validity True to mark the vector's single element valid, false to mark it NULL. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The size info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_constant_set_valid(duckdb_v2_vector_handle vector, bool validity, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_get_user_data( + duckdb_v2_aggregate_function_size_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Borrows a string-backed vector's arena for writing. - * - * Allocates the arena on first use. Valid for VARCHAR, BLOB, BIT, and BIGNUM vectors, and returns ERROR_INPUT_INVALID - * for anything else. This call is the single string-ness check, which is why arena_allocate itself performs none. The - * arena is borrowed — never destroy it — and stays valid until the vector is flattened, reallocated, or destroyed. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param vector The string-backed vector whose arena to borrow. - * @param out_arena Receives the borrowed arena handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The size info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_arena(duckdb_v2_vector_handle vector, - duckdb_v2_arena_handle *out_arena, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_get_bind_data( + duckdb_v2_aggregate_function_size_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Returns the number of child vectors a nested vector exposes. + * Sets the size of a single aggregate state, in bytes. The size callback must set this; DuckDB uses it to allocate + * state memory. * - * 1 for LIST, MAP and ARRAY; the field count for STRUCT; member_count + 1 for UNION, the tag plus the members; and 0 - * for any non-nested kind. vector_get_child documents what each index addresses. + * history: + * - stable: v2.0.0 + * + * @param info The size info handle. + * @param size The size of a single aggregate state, in bytes. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_set_state_size( + duckdb_v2_aggregate_function_size_info_handle info, idx_t size, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param vector The vector. - * @param out_count Receives the number of child vectors. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The init info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_child_count(duckdb_v2_vector_handle vector, idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_user_data( + duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Borrows a child vector by index. + * Retrieves the bind data set by the function's bind callback. * - * What each index addresses, by kind: LIST and ARRAY expose [0] = the elements; MAP [0] = the entries, a STRUCT(key, - * value) whose children are the keys and the values; STRUCT and TUPLE [i] = field i; and UNION [0] = the tag with - * [1..N] = the members. Note that value_get_child diverges on UNION, exposing only the active member. + * history: + * - stable: v2.0.0 * - * The returned child is borrowed and lives as long as the owning chunk. Returns ERROR_INPUT_INVALID if the vector has - * no children, or if the index is out of range. + * @param info The init info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_bind_data( + duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); + +/*! + * Returns how many aggregate states this invocation must initialize. This is the length of the array returned by + * `duckdb_v2_aggregate_function_init_get_states()`. * * history: * - stable: v2.0.0 * - * @param vector The nested vector to descend into. - * @param index The child index, in [0, vector_get_child_count). - * @param out_child Receives the borrowed child vector. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The init info handle. + * @param count Receives the number of states. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_child(duckdb_v2_vector_handle vector, idx_t index, - duckdb_v2_vector_handle *out_child, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_state_count( + duckdb_v2_aggregate_function_init_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Decodes BIGNUM storage bytes into a caller-supplied magnitude buffer + sign flag. + * Returns the array of aggregate states to initialize, one pointer per state. Each state points to uninitialized memory + * of the size reported by the size callback; the init callback must initialize all of them in place. * - * in_data and in_length are the value's raw storage bytes, exactly as they sit in a vector's bignum payload or as - * value_get_blob reports them for a BIGNUM value. Those bytes are opaque: the storage encoding is not a committed part - * of this API, and this function is the only thing that interprets them. + * history: + * - stable: v2.0.0 * - * Writes the big-endian magnitude bytes into out_data and the sign flag into out_is_negative; reconstruct the integer - * as (-1)**out_is_negative * unsigned_big_endian(out_data[0..out_length]). Materializing the bytes is unavoidable, - * since a negative bignum stores its magnitude bit-inverted, but the buffer belongs to the caller: this function never - * allocates and hands back nothing to free. + * @param info The init info handle. + * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_states( + duckdb_v2_aggregate_function_init_info_handle info, void ***states, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * - * Pass out_data = NULL to size the buffer without decoding. out_length then receives the exact number of magnitude - * bytes and out_is_negative the sign, and out_capacity is ignored. The size comes straight out of the storage header, - * so the query is O(1) and exact — a size call followed by a decode call never needs a retry loop. + * history: + * - stable: v2.0.0 * - * With out_data != NULL, out_capacity must be at least out_length bytes, or the call returns ERROR_INPUT_OBJECT_SIZE - * with out_length set to the required size and out_data left untouched. On success exactly out_length bytes are - * written, and the rest of the buffer is not modified. + * @param info The update info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_user_data( + duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the bind data set by the function's bind callback. * - * Returns ERROR_INPUT_INVALID if in_data is NULL, or if in_length is too short to hold a bignum header. + * history: + * - stable: v2.0.0 + * + * @param info The update info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_bind_data( + duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); + +/*! + * Returns how many input rows this invocation carries. This is the length of the argument vectors and of the state + * array: row i of every argument vector must be aggregated into state i. * * history: * - stable: v2.0.0 * - * @param in_data The value's raw BIGNUM storage bytes. - * @param in_length Size of in_data in bytes. - * @param out_data Caller-owned buffer receiving the magnitude bytes, or NULL to only report the required size in - * out_length. - * @param out_capacity Bytes available in out_data. Ignored when out_data is NULL. - * @param out_length Receives the magnitude size in bytes, written on success and on ERROR_INPUT_OBJECT_SIZE. - * @param out_is_negative Receives true if the value is negative, false otherwise. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The update info handle. + * @param count Receives the number of rows. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_bignum_decode(const uint8_t *in_data, idx_t in_length, uint8_t *out_data, - idx_t out_capacity, idx_t *out_length, bool *out_is_negative, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_row_count( + duckdb_v2_aggregate_function_update_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Encodes a magnitude + sign flag into BIGNUM storage bytes. + * Returns how many argument vectors this invocation carries, split into the four parts of the argument list. * - * The inverse of bignum_decode. in_data and in_length are the big-endian magnitude bytes and is_negative the sign; - * out_data receives the raw storage bytes for that integer, in the form a BIGNUM vector payload expects. Those bytes - * are opaque: the storage encoding is not a committed part of this API. + * The parts and their order are those the bind callback saw through `duckdb_v2_function_bind_get_arg_count()`, and the + * vectors are at the same indices. Valid indices for `duckdb_v2_aggregate_function_update_get_arg()` are [0, the sum of + * the four counts). Every out-parameter may be NULL, in which case nothing is written to it. * - * Pass out_data = NULL to size the buffer without encoding. out_length then receives the exact number of storage bytes, - * and out_capacity is ignored. The size is a fixed function of in_length, so the query is O(1) and exact. + * history: + * - stable: v2.0.0 * - * With out_data != NULL, out_capacity must be at least out_length bytes, or the call returns ERROR_INPUT_OBJECT_SIZE - * with out_length set to the required size and out_data left untouched. On success exactly out_length bytes are - * written, and the rest of the buffer is not modified. + * @param info The update info handle. + * @param positional_fixed Optional. Receives the number of positional-only and standard parameters. + * @param positional_variadic Optional. Receives the number of arguments `*args` received, 0 when the signature has + * none. + * @param named_fixed Optional. Receives the number of named-only parameters. + * @param named_variadic Optional. Receives the number of arguments `**kwargs` received, 0 when the signature has none. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_arg_count( + duckdb_v2_aggregate_function_update_info_handle info, idx_t *positional_fixed, idx_t *positional_variadic, + idx_t *named_fixed, idx_t *named_variadic, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the argument vector at the given index. * - * The magnitude must be canonical, matching what bignum_decode produces: in_length >= 1 with no leading zero bytes, so - * the value zero is the single byte 0x00 with is_negative = false. A NULL in_data, an in_length of 0, a leading zero - * byte, or a negative zero returns ERROR_INPUT_INVALID; a magnitude beyond the maximum bignum width returns - * ERROR_INPUT_OUT_OF_RANGE. + * The index is the one the bind callback used for the argument, e.g. the index + * `duckdb_v2_function_bind_get_arg_index()` found for a name. The vector holds the argument's values for the current + * batch; use `duckdb_v2_aggregate_function_update_get_row_count()` for the number of rows. Fails if the index is out of + * bounds. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param in_data Big-endian magnitude bytes, with no leading zero bytes. - * @param in_length Size of in_data in bytes. Must be >= 1. - * @param is_negative True to encode a negative value. - * @param out_data Caller-owned buffer receiving the storage bytes, or NULL to only report the required size in - * out_length. - * @param out_capacity Bytes available in out_data. Ignored when out_data is NULL. - * @param out_length Receives the storage size in bytes — written on success and on ERROR_INPUT_OBJECT_SIZE. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The update info handle. + * @param index The index of the argument. + * @param vector Receives the borrowed argument vector. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_bignum_encode(const uint8_t *in_data, idx_t in_length, bool is_negative, - uint8_t *out_data, idx_t out_capacity, idx_t *out_length, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR +duckdb_v2_aggregate_function_update_get_arg(duckdb_v2_aggregate_function_update_info_handle info, idx_t index, + duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); /*! - * Validates all text->len bytes as UTF-8, including bytes after embedded NUL characters. + * Returns the array of aggregate states to update, one pointer per input row: row i of every argument vector must be + * aggregated into state i. Different rows may point to the same state. * - * Returns ERROR_INPUT_INVALID if any of: - * - text is NULL. - * - text->ptr is NULL and text->len is nonzero. - * - The input contains malformed UTF-8. + * history: + * - stable: v2.0.0 * - * A view with a NULL ptr and zero length is valid. + * @param info The update info handle. + * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_states( + duckdb_v2_aggregate_function_update_info_handle info, void ***states, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param text The bytes to validate. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The combine info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_validate_utf8(const duckdb_v2_str *text, duckdb_v2_error_info_handle *err); - -/* --- Struct definitions for vector --- */ - -struct duckdb_v2_vector_view { - const void *data; - const uint64_t *validity; - const duckdb_v2_sel_t *sel; - idx_t count; -}; - -struct duckdb_v2_list_entry { - idx_t offset; - idx_t length; -}; - -struct duckdb_v2_hugeint_t { - uint64_t lower; - int64_t upper; -}; - -struct duckdb_v2_uhugeint_t { - uint64_t lower; - uint64_t upper; -}; - -struct duckdb_v2_interval_t { - int32_t months; - int32_t days; - int64_t micros; -}; - -/* ============================================================================ - * MODULE: aggregate - * ============================================================================ */ - -/* --- Enums for aggregate --- */ - -/* --- Struct forward declarations for aggregate --- */ - -/* --- Types for aggregate --- */ - -/*! - * An owned opaque handle to a custom aggregate function being built. Created with - * `duckdb_v2_aggregate_function_create_with_connection()` or `duckdb_v2_aggregate_function_create_with_extension()`, - * configured with the setter functions (e.g. `duckdb_v2_aggregate_function_set_name()`, - * `duckdb_v2_aggregate_function_set_update_callback()`, etc.) and the signature obtained via - * `duckdb_v2_aggregate_function_get_signature()`, made available with `duckdb_v2_aggregate_function_register()`, and - * destroyed with `duckdb_v2_aggregate_function_destroy()`. - */ -typedef struct _duckdb_v2_aggregate_function { - void *internal_ptr; -} * duckdb_v2_aggregate_function_handle; - -/*! - * A borrowed opaque handle to the result of an aggregate function's "bind" phase. The "bind" callback receives this - * handle next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments, the user data and the - * bind data, and can use it to set the return type of the call site being bound. - */ -typedef struct _duckdb_v2_aggregate_function_bind_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_bind_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the state sizing "size" phase. The - * "size" callback receives this handle and must use it to report the size of a single aggregate state in bytes. - */ -typedef struct _duckdb_v2_aggregate_function_size_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_size_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the state initialization "init" - * phase. The "init" callback receives this handle and can use it to access the array of aggregate states it must - * initialize in place. - */ -typedef struct _duckdb_v2_aggregate_function_init_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_init_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the "update" phase. The "update" - * callback receives this handle and can use it to access the input argument vectors, the number of rows to process, and - * the aggregate state each row must be aggregated into. - */ -typedef struct _duckdb_v2_aggregate_function_update_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_update_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the "combine" phase. The "combine" - * callback receives this handle and can use it to access the source and target aggregate state arrays to merge. - */ -typedef struct _duckdb_v2_aggregate_function_combine_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_combine_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the "finalize" phase. The - * "finalize" callback receives this handle and can use it to access the aggregate states and the result vector and - * offset to write their final values to. - */ -typedef struct _duckdb_v2_aggregate_function_finalize_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_finalize_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to an aggregate function during the state destruction "destroy" - * phase. The "destroy" callback receives this handle and can use it to access the array of aggregate states whose - * resources must be released. - */ -typedef struct _duckdb_v2_aggregate_function_destroy_info { - void *internal_ptr; -} * duckdb_v2_aggregate_function_destroy_info_handle; - -/* --- Constants for aggregate --- */ - -/* --- Function pointer typedefs for aggregate --- */ - -typedef void (*duckdb_v2_aggregate_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, - duckdb_v2_aggregate_function_bind_info_handle result, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_size_callback_fn)(duckdb_v2_aggregate_function_size_info_handle info, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_init_callback_fn)(duckdb_v2_aggregate_function_init_info_handle info, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_update_callback_fn)(duckdb_v2_aggregate_function_update_info_handle info, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_combine_callback_fn)(duckdb_v2_aggregate_function_combine_info_handle info, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_finalize_callback_fn)( - duckdb_v2_aggregate_function_finalize_info_handle info, duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_aggregate_function_destroy_callback_fn)(duckdb_v2_aggregate_function_destroy_info_handle info, - duckdb_v2_error_info_handle *err); - -/* --- Functions for aggregate --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_user_data( + duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a new aggregate function that will be registered on the connection's database. - * - * The function starts out empty: configure it with the setter functions (e.g. - * `duckdb_v2_aggregate_function_set_name()`, `duckdb_v2_aggregate_function_set_update_callback()`, etc.) and the - * signature obtained via `duckdb_v2_aggregate_function_get_signature()`, then make it available with - * `duckdb_v2_aggregate_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_aggregate_function_destroy()`, also after registration. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created aggregate function. Owned by the caller. + * @param info The combine info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create_with_connection( - duckdb_v2_connection_handle connection, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_bind_data( + duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a new aggregate function that will be registered on the loading extension's database. - * - * Use this from an extension load callback, where an extension handle is available. The function starts out empty: - * configure it with the setter functions (e.g. `duckdb_v2_aggregate_function_set_name()`, - * `duckdb_v2_aggregate_function_set_update_callback()`, etc.) and the signature obtained via - * `duckdb_v2_aggregate_function_get_signature()`, then make it available with - * `duckdb_v2_aggregate_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_aggregate_function_destroy()`, also after registration. + * Returns how many source/target state pairs this invocation must combine. This is the length of both the sources and + * the targets array. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the function in. - * @param function On success, receives the newly created aggregate function. Owned by the caller. + * @param info The combine info handle. + * @param count Receives the number of state pairs. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create_with_extension( - duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_state_count( + duckdb_v2_aggregate_function_combine_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Sets the name of the aggregate function. - * - * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before - * registration. + * Returns the array of source aggregate states, one pointer per pair. Source i must be combined into target i; the + * source must not be modified. * * history: * - stable: v2.0.0 * - * @param function The function to set the name of. - * @param name The name to set. Borrowed and copied. + * @param info The combine info handle. + * @param states Receives the array of source aggregate state pointers. Borrowed; valid only for the duration of the + * callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_name(duckdb_v2_aggregate_function_handle function, - const duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_sources( + duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); /*! - * Returns the function's signature so it can be configured. - * - * Add parameters with `duckdb_v2_function_signature_add_parameter()` and set the return type with - * `duckdb_v2_function_signature_set_return_type()`. The signature is modified in place; the function must be given a - * signature with a return type before registration. + * Returns the array of target aggregate states, one pointer per pair. Source i must be combined into target i. * * history: * - stable: v2.0.0 * - * @param function The function to get the signature of. - * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. + * @param info The combine info handle. + * @param states Receives the array of target aggregate state pointers. Borrowed; valid only for the duration of the + * callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_get_signature(duckdb_v2_aggregate_function_handle function, - duckdb_v2_function_signature_handle *sig, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_targets( + duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); /*! - * Sets arbitrary user data on the aggregate function. - * - * Associates an opaque pointer with the function, retrievable from each callback via its user data accessor (e.g. - * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_aggregate_function_update_get_user_data()`, etc.). The opaque - * handle bundles the pointer with an optional destructor, invoked when the data is no longer needed. + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to set the user data of. - * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param info The finalize info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_user_data(duckdb_v2_aggregate_function_handle function, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_user_data( + duckdb_v2_aggregate_function_finalize_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Sets a property on the aggregate function. - * - * Configures a function property that influences planning and execution, such as stability, NULL handling, fallibility, - * collation handling, or the aggregate-specific order/DISTINCT dependence. The value must be one of the - * `DUCKDB_V2_FUNCTION_PROPERTY_VALUE` entries belonging to `key`; passing a value that does not belong to the key, or a - * key that is not valid for aggregate functions, results in an error. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the property of. - * @param key The property to set. - * @param value The value to set for the property. Must belong to `key`. + * @param info The finalize info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_property(duckdb_v2_aggregate_function_handle function, - DUCKDB_V2_FUNCTION_PROPERTY_KEY key, - DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_bind_data( + duckdb_v2_aggregate_function_finalize_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the optional bind callback of the aggregate function. - * - * The bind callback is invoked during query planning for each call site of the function. Through its - * `duckdb_v2_function_bind_info_handle` it can inspect the argument types and constant argument values and set "bind - * data" that is shared with the other callbacks. Through its `duckdb_v2_aggregate_function_bind_info_handle` it can set - * a concrete return type. + * Returns how many aggregate states this invocation must finalize. This is the length of the state array and the number + * of rows to write to the result vector. * * history: * - stable: v2.0.0 * - * @param function The function to set the bind callback of. - * @param callback The bind callback to set. + * @param info The finalize info handle. + * @param count Receives the number of states. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_bind_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_bind_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_state_count( + duckdb_v2_aggregate_function_finalize_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Sets the size callback of the aggregate function. - * - * The size callback reports the size of a single aggregate state in bytes via - * `duckdb_v2_aggregate_function_size_set_state_size()`; DuckDB uses it to allocate state memory. A size callback must - * be set before registration. + * Returns the array of aggregate states to finalize, one pointer per state. State i must be finalized into result row + * offset + i. * * history: * - stable: v2.0.0 * - * @param function The function to set the size callback of. - * @param callback The size callback to set. + * @param info The finalize info handle. + * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_size_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_size_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_states( + duckdb_v2_aggregate_function_finalize_info_handle info, void ***states, duckdb_v2_error_info_handle *err); /*! - * Sets the init callback of the aggregate function. + * Retrieves the result vector the finalize callback must write into. * - * The init callback initializes freshly allocated aggregate states in place, obtained via - * `duckdb_v2_aggregate_function_init_get_states()`. An init callback must be set before registration. + * State i must be finalized into result row offset + i, with the offset from + * `duckdb_v2_aggregate_function_finalize_get_result_offset()`. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the init callback of. - * @param callback The init callback to set. + * @param info The finalize info handle. + * @param vector Receives the borrowed result vector to write into. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_init_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_init_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR +duckdb_v2_aggregate_function_finalize_get_result(duckdb_v2_aggregate_function_finalize_info_handle info, + duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); /*! - * Sets the update callback of the aggregate function. - * - * The update callback is invoked during query execution with a batch of input rows: row i of every argument vector must - * be aggregated into state i of the state array. An update callback must be set before registration. + * Returns the offset in the result vector at which to start writing: state i must be finalized into result row offset + + * i. * * history: * - stable: v2.0.0 * - * @param function The function to set the update callback of. - * @param callback The update callback to set. + * @param info The finalize info handle. + * @param offset Receives the result offset. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_update_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_update_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_result_offset( + duckdb_v2_aggregate_function_finalize_info_handle info, idx_t *offset, duckdb_v2_error_info_handle *err); /*! - * Sets the combine callback of the aggregate function. - * - * The combine callback merges partial aggregate states computed in parallel: source state i must be combined into - * target state i. A combine callback must be set before registration. + * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to set the combine callback of. - * @param callback The combine callback to set. + * @param info The destroy info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_combine_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_combine_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_user_data( + duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the finalize callback of the aggregate function. - * - * The finalize callback produces the function's result: state i must be finalized into result row offset + i of the - * result vector. A finalize callback must be set before registration. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the finalize callback of. - * @param callback The finalize callback to set. + * @param info The destroy info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_finalize_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_finalize_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_bind_data( + duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the destroy callback for the aggregate function. Optional: only needed when aggregate states own resources that - * must be released. The callback runs on states that are discarded without being finalized; it must not fail, an error - * reported through its error slot is ignored. + * Returns how many aggregate states this invocation must destroy. This is the length of the state array. * * history: * - stable: v2.0.0 * - * @param function The function to set the destroy callback of. - * @param callback The destroy callback to set. + * @param info The destroy info handle. + * @param count Receives the number of states. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_destroy_callback( - duckdb_v2_aggregate_function_handle function, duckdb_v2_aggregate_function_destroy_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_state_count( + duckdb_v2_aggregate_function_destroy_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Sets the concrete return type of the call site being bound. - * - * Overrides the return type declared in the signature. Register the function with an ANY return type and set the - * concrete type here, derived from the argument types, to build a function whose result type depends on its input. The - * type is borrowed and copied. + * Returns the array of aggregate states to destroy, one pointer per state. The callback must release any resources the + * states own. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param return_type The return type to set. Borrowed for the call only. + * @param info The destroy info handle. + * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_bind_set_return_type( - duckdb_v2_aggregate_function_bind_info_handle info, duckdb_v2_logical_type_handle return_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_states( + duckdb_v2_aggregate_function_destroy_info_handle info, void ***states, duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. + * Registers the aggregate function, making it available for use in SQL queries. + * + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. Registration requires a name, the size, init, update, combine and finalize callbacks, and a + * signature with a complete return type; an ANY return type is accepted only together with a bind callback that sets + * the concrete type per call site. The aggregate function must have been created through a factory of the same + * database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_aggregate_function_destroy()`, which does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The size info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param conn The connection to register on. + * @param function The aggregate function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_get_user_data( - duckdb_v2_aggregate_function_size_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_aggregate_function( + duckdb_v2_connection_handle conn, duckdb_v2_aggregate_function_handle function, duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Registers the aggregate function, making it available for use in SQL queries. + * + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name, the size, init, update, combine and finalize callbacks, and + * a signature with a complete return type; an ANY return type is accepted only together with a bind callback that sets + * the concrete type per call site. The aggregate function must have been created through a factory of the same + * database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_aggregate_function_destroy()`, which does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The size info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param extension The loading extension. + * @param function The aggregate function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_get_bind_data( - duckdb_v2_aggregate_function_size_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_aggregate_function( + duckdb_v2_extension_handle extension, duckdb_v2_aggregate_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Sets the size of a single aggregate state, in bytes. The size callback must set this; DuckDB uses it to allocate - * state memory. + * Destroys the aggregate function, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The size info handle. - * @param size The size of a single aggregate state, in bytes. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param function The function to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_size_set_state_size( - duckdb_v2_aggregate_function_size_info_handle info, idx_t size, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy(duckdb_v2_aggregate_function_handle *function); + +/* --- Struct definitions for aggregate --- */ + +/* ============================================================================ + * MODULE: catalog + * ============================================================================ */ + +/* --- Enums for catalog --- */ + +/* --- Struct forward declarations for catalog --- */ + +/* --- Types for catalog --- */ /*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. + * An owned snapshot of one base table taken at creation: where the name resolved, the table's columns, and per-column + * catalog facts. Later DDL does not update it. Create with `duckdb_v2_connection_describe_table()`, read the resolved + * location via `duckdb_v2_table_description_get_qname()`, and the columns via + * `duckdb_v2_table_description_get_column_count()` and `duckdb_v2_table_description_get_column()`. Destroy via + * `duckdb_v2_table_description_destroy()`. + */ +typedef struct _duckdb_v2_table_description { + void *internal_ptr; +} * duckdb_v2_table_description_handle; + +/*! + * An owned snapshot of one column of a described table: its name, type, and catalog facts. Obtain with + * `duckdb_v2_table_description_get_column()`, read with `duckdb_v2_column_description_get_name()`, + * `duckdb_v2_column_description_get_type()`, `duckdb_v2_column_description_has_default()` and + * `duckdb_v2_column_description_has_generated()`, and destroy with `duckdb_v2_column_description_destroy()`. + */ +typedef struct _duckdb_v2_column_description { + void *internal_ptr; +} * duckdb_v2_column_description_handle; + +/* --- Constants for catalog --- */ + +/* --- Function pointer typedefs for catalog --- */ + +/* --- Functions for catalog --- */ + +/*! + * Resolves a table name and snapshots its description. + * + * Resolves name in the connection's catalogs and returns an owned description of the table it names. name may be + * partial: an unqualified or schema-qualified name resolves through the connection's search path, exactly as the same + * name resolves in SQL. A two-part name tries the first part as a schema and as an attached database, as SQL does, and + * is rejected when both readings exist. A name that resolves to nothing is rejected with the engine's missing-table + * error; a name that resolves to a view is rejected with the engine's not-a-table error, since a description snapshots + * a base table. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param conn The connection whose catalogs and search path resolve the name. + * @param name The possibly partial table name to resolve. Borrowed for the call only. + * @param desc On success, receives the owned description. Destroy via `duckdb_v2_table_description_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_user_data( - duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_describe_table(duckdb_v2_connection_handle conn, + duckdb_v2_qname_handle name, + duckdb_v2_table_description_handle *desc, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Returns the resolved qualified name of the described table. + * + * Returns an owned copy of the fully resolved name: the catalog, schema, and table the lookup landed on, with the + * casing the table was created with, never an echo of the requested name. Rendering it produces SQL that pins the + * described table regardless of search path. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param desc The description. + * @param name Receives the owned resolved name. Destroy via `duckdb_v2_qname_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_bind_data( - duckdb_v2_aggregate_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_qname(duckdb_v2_table_description_handle desc, + duckdb_v2_qname_handle *name, + duckdb_v2_error_info_handle *err); /*! - * Returns how many aggregate states this invocation must initialize. This is the length of the array returned by - * `duckdb_v2_aggregate_function_init_get_states()`. + * Returns whether the described table's catalog is read-only. + * + * True when the catalog the name resolved into was attached read-only, in which case no write to the table can succeed. + * False does not by itself prove a write will succeed; it clears the catalog-level check only. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param count Receives the number of states. + * @param desc The description. + * @param readonly Receives whether the catalog is read-only. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_state_count( - duckdb_v2_aggregate_function_init_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_is_readonly(duckdb_v2_table_description_handle desc, + bool *readonly, duckdb_v2_error_info_handle *err); /*! - * Returns the array of aggregate states to initialize, one pointer per state. Each state points to uninitialized memory - * of the size reported by the size callback; the init callback must initialize all of them in place. + * Returns the number of columns of the described table. + * + * Counts every column in declared order, generated columns included. Valid indices for + * `duckdb_v2_table_description_get_column()` are [0, count). * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. + * @param desc The description. + * @param count Receives the column count. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_init_get_states( - duckdb_v2_aggregate_function_init_info_handle info, void ***states, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_column_count(duckdb_v2_table_description_handle desc, + idx_t *count, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. + * Returns an owned description of the column at index. + * + * Columns are numbered in declared order, generated columns included. An out-of-range index is rejected with + * `ERROR_INPUT_OUT_OF_RANGE`. * * history: * - stable: v2.0.0 * - * @param info The update info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param desc The description. + * @param index Zero-based column index. + * @param column Receives the owned column description. Destroy via `duckdb_v2_column_description_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_user_data( - duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_column(duckdb_v2_table_description_handle desc, + idx_t index, + duckdb_v2_column_description_handle *column, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Destroys the table description, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Column descriptions obtained from it are independent and stay valid. * * history: * - stable: v2.0.0 * - * @param info The update info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param desc The description to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_bind_data( - duckdb_v2_aggregate_function_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_destroy(duckdb_v2_table_description_handle *desc); /*! - * Returns how many input rows this invocation carries. This is the length of the argument vectors and of the state - * array: row i of every argument vector must be aggregated into state i. + * Borrows the name of the column. + * + * The name carries the casing the column was declared with. * * history: * - stable: v2.0.0 * - * @param info The update info handle. - * @param count Receives the number of rows. + * @param column The column description. + * @param name Receives a borrowed view of the column name. Valid until the column description is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_row_count( - duckdb_v2_aggregate_function_update_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_get_name(duckdb_v2_column_description_handle column, + duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Returns how many argument vectors this invocation carries, split into the four parts of the argument list. - * - * The parts and their order are those the bind callback saw through `duckdb_v2_function_bind_get_arg_count()`, and the - * vectors are at the same indices. Valid indices for `duckdb_v2_aggregate_function_update_get_arg()` are [0, the sum of - * the four counts). Every out-parameter may be NULL, in which case nothing is written to it. + * Borrows the type of the column. * * history: * - stable: v2.0.0 * - * @param info The update info handle. - * @param positional_fixed Optional. Receives the number of positional-only and standard parameters. - * @param positional_variadic Optional. Receives the number of arguments `*args` received, 0 when the signature has - * none. - * @param named_fixed Optional. Receives the number of named-only parameters. - * @param named_variadic Optional. Receives the number of arguments `**kwargs` received, 0 when the signature has none. + * @param column The column description. + * @param type Receives the borrowed column type. Valid until the column description is destroyed; do not destroy it. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_arg_count( - duckdb_v2_aggregate_function_update_info_handle info, idx_t *positional_fixed, idx_t *positional_variadic, - idx_t *named_fixed, idx_t *named_variadic, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_get_type(duckdb_v2_column_description_handle column, + duckdb_v2_logical_type_handle *type, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the argument vector at the given index. + * Returns whether the column has a default value. * - * The index is the one the bind callback used for the argument, e.g. the index - * `duckdb_v2_function_bind_get_arg_index()` found for a name. The vector holds the argument's values for the current - * batch; use `duckdb_v2_aggregate_function_update_get_row_count()` for the number of rows. Fails if the index is out of - * bounds. Borrowed; valid only for the duration of the callback. + * True when the column declares a default expression; the engine evaluates it for rows that omit the column. Generated + * columns report false. * * history: * - stable: v2.0.0 * - * @param info The update info handle. - * @param index The index of the argument. - * @param vector Receives the borrowed argument vector. + * @param column The column description. + * @param has_default Receives whether the column has a default value. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR -duckdb_v2_aggregate_function_update_get_arg(duckdb_v2_aggregate_function_update_info_handle info, idx_t index, - duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_has_default(duckdb_v2_column_description_handle column, + bool *has_default, + duckdb_v2_error_info_handle *err); /*! - * Returns the array of aggregate states to update, one pointer per input row: row i of every argument vector must be - * aggregated into state i. Different rows may point to the same state. - * - * history: - * - stable: v2.0.0 + * Returns whether the column is generated. * - * @param info The update info handle. - * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_update_get_states( - duckdb_v2_aggregate_function_update_info_handle info, void ***states, duckdb_v2_error_info_handle *err); - -/*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. + * True when the column is a generated column, computed by the engine from a generation expression and not writable. * * history: * - stable: v2.0.0 * - * @param info The combine info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param column The column description. + * @param has_generated Receives whether the column is generated. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_user_data( - duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_has_generated(duckdb_v2_column_description_handle column, + bool *has_generated, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Destroys the column description, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Any name or type borrowed from it becomes dangling. * * history: * - stable: v2.0.0 * - * @param info The combine info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param column The column description to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_bind_data( - duckdb_v2_aggregate_function_combine_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_destroy(duckdb_v2_column_description_handle *column); + +/* --- Struct definitions for catalog --- */ + +/* ============================================================================ + * MODULE: copy + * ============================================================================ */ + +/* --- Enums for copy --- */ + +/* --- Struct forward declarations for copy --- */ + +/* --- Types for copy --- */ /*! - * Returns how many source/target state pairs this invocation must combine. This is the length of both the sources and - * the targets array. - * - * history: - * - stable: v2.0.0 - * - * @param info The combine info handle. - * @param count Receives the number of state pairs. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * An owned opaque handle to a custom copy function being built: an output format for `COPY ... TO`, an input format for + * `COPY ... FROM`, or both. Created with `duckdb_v2_copy_function_create()`, configured with the setter functions (e.g. + * `duckdb_v2_copy_function_set_name()`, `duckdb_v2_copy_to_set_batch_callback()`, + * `duckdb_v2_copy_from_set_exec_callback()`, etc.), made available with `duckdb_v2_connection_register_copy_function()` + * or `duckdb_v2_extension_register_copy_function()`, and destroyed with `duckdb_v2_copy_function_destroy()`. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_state_count( - duckdb_v2_aggregate_function_combine_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_function { + void *internal_ptr; +} * duckdb_v2_copy_function_handle; /*! - * Returns the array of source aggregate states, one pointer per pair. Source i must be combined into target i; the - * source must not be modified. - * - * history: - * - stable: v2.0.0 - * - * @param info The combine info handle. - * @param states Receives the array of source aggregate state pointers. Borrowed; valid only for the duration of the - * callback. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the query preparation "bind" phase of a + * `COPY ... TO` statement. The "bind" callback receives this handle and can use it to e.g. inspect the names and types + * of the columns being written, read the statement's options and initialize some constant state. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_sources( - duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_bind_info { + void *internal_ptr; +} * duckdb_v2_copy_to_bind_info_handle; /*! - * Returns the array of target aggregate states, one pointer per pair. Source i must be combined into target i. - * - * history: - * - stable: v2.0.0 - * - * @param info The combine info handle. - * @param states Receives the array of target aggregate state pointers. Borrowed; valid only for the duration of the - * callback. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the "batch size" phase of a `COPY ... + * TO` statement. The "batch size" callback receives this handle and must use it to report how many rows a batch should + * carry. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_combine_get_targets( - duckdb_v2_aggregate_function_combine_info_handle info, void ***states, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_batch_size_info { + void *internal_ptr; +} * duckdb_v2_copy_to_batch_size_info_handle; /*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. - * - * history: - * - stable: v2.0.0 - * - * @param info The finalize info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the per-file "init" phase of a `COPY ... + * TO` statement. The "init" callback receives this handle and can use it to e.g. read the path of the file being + * written and set up the state shared by every batch written to that file. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_user_data( - duckdb_v2_aggregate_function_finalize_info_handle info, void **data, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_init_info { + void *internal_ptr; +} * duckdb_v2_copy_to_init_info_handle; /*! - * Retrieves the bind data set by the function's bind callback. - * - * history: - * - stable: v2.0.0 - * - * @param info The finalize info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the "batch" phase of a `COPY ... TO` + * statement. The "batch" callback receives this handle and can use it to take ownership of the rows of the batch and to + * set the prepared form of the batch handed to the "flush" callback. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_bind_data( - duckdb_v2_aggregate_function_finalize_info_handle info, void **data, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_batch_info { + void *internal_ptr; +} * duckdb_v2_copy_to_batch_info_handle; /*! - * Returns how many aggregate states this invocation must finalize. This is the length of the state array and the number - * of rows to write to the result vector. - * - * history: - * - stable: v2.0.0 - * - * @param info The finalize info handle. - * @param count Receives the number of states. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the "flush" phase of a `COPY ... TO` + * statement. The "flush" callback receives this handle and can use it to access the prepared batch and write it to the + * output. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_state_count( - duckdb_v2_aggregate_function_finalize_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_flush_info { + void *internal_ptr; +} * duckdb_v2_copy_to_flush_info_handle; /*! - * Returns the array of aggregate states to finalize, one pointer per state. State i must be finalized into result row - * offset + i. - * - * history: - * - stable: v2.0.0 - * - * @param info The finalize info handle. - * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function during the per-file "finalize" phase of a `COPY + * ... TO` statement. The "finalize" callback receives this handle and can use it to e.g. close the file after every + * batch has been flushed to it. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_states( - duckdb_v2_aggregate_function_finalize_info_handle info, void ***states, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_finalize_info { + void *internal_ptr; +} * duckdb_v2_copy_to_finalize_info_handle; /*! - * Retrieves the result vector the finalize callback must write into. - * - * State i must be finalized into result row offset + i, with the offset from - * `duckdb_v2_aggregate_function_finalize_get_result_offset()`. Borrowed; valid only for the duration of the callback. - * - * history: - * - stable: v2.0.0 - * - * @param info The finalize info handle. - * @param vector Receives the borrowed result vector to write into. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a copy function when a `COPY ... TO` statement reports + * statistics about the files it wrote (e.g. with `RETURN_STATS`). The "statistics" callback receives this handle and + * uses it to report the statistics of one written file. */ -DUCKDB_C_API DUCKDB_V2_ERROR -duckdb_v2_aggregate_function_finalize_get_result(duckdb_v2_aggregate_function_finalize_info_handle info, - duckdb_v2_vector_handle *vector, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_copy_to_statistics_info { + void *internal_ptr; +} * duckdb_v2_copy_to_statistics_info_handle; /*! - * Returns the offset in the result vector at which to start writing: state i must be finalized into result row offset + - * i. + * A borrowed opaque handle to the arguments supplied to a copy function during the query preparation "bind" phase of a + * `COPY ... FROM` statement. The "bind" callback receives this handle and can use it to e.g. read the path of the file + * to read, inspect the names and types of the columns the target table expects, read the statement's options, hint at + * the number of rows the read will produce and initialize some constant state. + */ +typedef struct _duckdb_v2_copy_from_bind_info { + void *internal_ptr; +} * duckdb_v2_copy_from_bind_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a copy function during the global state initialization "init + * global" phase of a `COPY ... FROM` statement. The "init global" callback receives this handle and can use it to e.g. + * set up the state shared by every thread reading the file, and to declare how many threads may read it. + */ +typedef struct _duckdb_v2_copy_from_init_global_info { + void *internal_ptr; +} * duckdb_v2_copy_from_init_global_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a copy function during the local state initialization "init + * local" phase of a `COPY ... FROM` statement. The "init local" callback receives this handle and can use it to e.g. + * set up worker-local state, typically by claiming work from the shared global state. + */ +typedef struct _duckdb_v2_copy_from_init_local_info { + void *internal_ptr; +} * duckdb_v2_copy_from_init_local_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a copy function during the execution "exec" phase of a `COPY + * ... FROM` statement. The "exec" callback receives this handle and can use it to e.g. access the global and local + * state and write the next batch of rows to the output chunk. + */ +typedef struct _duckdb_v2_copy_from_exec_info { + void *internal_ptr; +} * duckdb_v2_copy_from_exec_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a copy function during the "progress" phase of a `COPY ... + * FROM` statement. The "progress" callback receives this handle and can use it to report how far the read has advanced. + */ +typedef struct _duckdb_v2_copy_from_progress_info { + void *internal_ptr; +} * duckdb_v2_copy_from_progress_info_handle; + +/* --- Constants for copy --- */ + +/* --- Function pointer typedefs for copy --- */ + +typedef void (*duckdb_v2_copy_to_bind_callback_fn)(duckdb_v2_copy_to_bind_info_handle info, + duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_batch_size_callback_fn)(duckdb_v2_copy_to_batch_size_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_init_callback_fn)(duckdb_v2_copy_to_init_info_handle info, + duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_batch_callback_fn)(duckdb_v2_copy_to_batch_info_handle info, + duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_flush_callback_fn)(duckdb_v2_copy_to_flush_info_handle info, + duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_finalize_callback_fn)(duckdb_v2_copy_to_finalize_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_to_statistics_callback_fn)(duckdb_v2_copy_to_statistics_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_from_bind_callback_fn)(duckdb_v2_copy_from_bind_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_from_init_global_callback_fn)(duckdb_v2_copy_from_init_global_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_from_init_local_callback_fn)(duckdb_v2_copy_from_init_local_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_from_exec_callback_fn)(duckdb_v2_copy_from_exec_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_copy_from_progress_callback_fn)(duckdb_v2_copy_from_progress_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +/* --- Functions for copy --- */ + +/*! + * Creates a new copy function through a factory. + * + * A copy function implements a file format for `COPY`: once registered, SQL reaches it with `COPY ... TO 'path' (FORMAT + * name)` and `COPY table FROM 'path' (FORMAT name)`. The function starts out empty: configure it with the setter + * functions (e.g. `duckdb_v2_copy_function_set_name()`, the `copy_to_set_*` callbacks for writing, the + * `copy_from_set_*` callbacks for reading, or both), then make it available with + * `duckdb_v2_connection_register_copy_function()` or `duckdb_v2_extension_register_copy_function()`. The caller owns + * the returned handle and must destroy it with `duckdb_v2_copy_function_destroy()`, also after registration. + * + * The copy function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param info The finalize info handle. - * @param offset Receives the result offset. + * @param factory The factory to create the copy function through. + * @param function On success, receives the newly created copy function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_finalize_get_result_offset( - duckdb_v2_aggregate_function_finalize_info_handle info, idx_t *offset, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_copy_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_aggregate_function_set_user_data()`. + * Sets the name of the copy function. + * + * The name is the format SQL selects the function with: `COPY ... TO 'path' (FORMAT name)` or `COPY table FROM 'path' + * (FORMAT name)`. It is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param info The destroy info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_user_data( - duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_set_name(duckdb_v2_copy_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Sets the file extension of the files the copy function writes, without the leading dot, e.g. `parquet`. + * + * DuckDB uses it when it names files itself, e.g. the files of `COPY ... TO 'dir' (PARTITION_BY ...)` or + * `(PER_THREAD_OUTPUT)`. A statement can override it with the `FILE_EXTENSION` option. An extension starting with a dot + * is rejected. The extension is borrowed and copied. Calling this again replaces the previous extension. Optional, but + * without it the names DuckDB gives files end in a bare dot, e.g. `data_0.`. * * history: * - stable: v2.0.0 * - * @param info The destroy info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param function The function to set the file extension of. + * @param extension The extension to set, without the leading dot. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_bind_data( - duckdb_v2_aggregate_function_destroy_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_set_file_extension(duckdb_v2_copy_function_handle function, + const duckdb_v2_str *extension, + duckdb_v2_error_info_handle *err); /*! - * Returns how many aggregate states this invocation must destroy. This is the length of the state array. + * Sets arbitrary user data on the copy function. + * + * Associates an opaque pointer with the function, retrievable from each callback of either side via its user data + * accessor (e.g. `duckdb_v2_copy_to_bind_get_user_data()`, `duckdb_v2_copy_from_exec_get_user_data()`, etc.). The + * opaque handle bundles the pointer with an optional destructor, invoked when the data is no longer needed. * * history: * - stable: v2.0.0 * - * @param info The destroy info handle. - * @param count Receives the number of states. + * @param function The function to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_state_count( - duckdb_v2_aggregate_function_destroy_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_set_user_data(duckdb_v2_copy_function_handle function, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Returns the array of aggregate states to destroy, one pointer per state. The callback must release any resources the - * states own. + * Sets the optional bind callback of the `COPY ... TO` side. + * + * The bind callback is invoked during query planning for each `COPY ... TO` statement that uses the function. It can + * inspect the names and types of the columns being written, read the statement's options and set "bind data" that is + * shared with the other `COPY ... TO` callbacks. * * history: * - stable: v2.0.0 * - * @param info The destroy info handle. - * @param states Receives the array of aggregate state pointers. Borrowed; valid only for the duration of the callback. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_states( - duckdb_v2_aggregate_function_destroy_info_handle info, void ***states, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_bind_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_to_bind_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Registers the aggregate function, making it available for use in SQL queries. + * Sets the optional batch size callback of the `COPY ... TO` side. * - * The function is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a name, the size, init, update, combine and finalize callbacks, and a signature with a complete - * return type; an ANY return type is accepted only together with a bind callback that sets the concrete type per call - * site. The caller still owns the handle after registration and must destroy it with - * `duckdb_v2_aggregate_function_destroy()`, which does not affect the registered function. + * The batch size callback is invoked during query planning, after the bind callback, for each `COPY ... TO` statement + * that does not set `BATCH_SIZE` itself. It should report how many rows a batch should carry via + * `duckdb_v2_copy_to_batch_size_set_target()`; the engine then cuts the rows being written into batches of that size + * and hands each to the batch callback. Without a batch size from either the statement or the callback, a batch is cut + * for every chunk of rows sunk, i.e. a vector at a time. A batch may still be smaller than the reported size (the last + * one of a file, or when `BATCH_SIZE_BYTES` cuts it first). * * history: * - stable: v2.0.0 * - * @param function The function to register. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_register(duckdb_v2_aggregate_function_handle function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_batch_size_callback( + duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_batch_size_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Destroys the aggregate function, releasing its resources. + * Sets the optional init callback of the `COPY ... TO` side. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered function. + * The init callback is invoked once per output file, before any batch destined for that file is prepared. It can read + * the path of the file via `duckdb_v2_copy_to_init_get_file_path()` and set "init data" that is shared with the batch, + * flush and finalize callbacks of that file. A statement may write several files, e.g. when its output is partitioned; + * each gets its own init data. * * history: * - stable: v2.0.0 * - * @param function The function to destroy. + * @param function The function to set the callback of. + * @param callback The callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy(duckdb_v2_aggregate_function_handle *function); - -/* --- Struct definitions for aggregate --- */ - -/* ============================================================================ - * MODULE: catalog - * ============================================================================ */ - -/* --- Enums for catalog --- */ - -/* --- Struct forward declarations for catalog --- */ - -/* --- Types for catalog --- */ - -/*! - * An owned snapshot of one base table taken at creation: where the name resolved, the table's columns, and per-column - * catalog facts. Later DDL does not update it. Create with `duckdb_v2_connection_describe_table()`, read the resolved - * location via `duckdb_v2_table_description_get_qname()`, and the columns via - * `duckdb_v2_table_description_get_column_count()` and `duckdb_v2_table_description_get_column()`. Destroy via - * `duckdb_v2_table_description_destroy()`. - */ -typedef struct _duckdb_v2_table_description { - void *internal_ptr; -} * duckdb_v2_table_description_handle; - -/*! - * An owned snapshot of one column of a described table: its name, type, and catalog facts. Obtain with - * `duckdb_v2_table_description_get_column()`, read with `duckdb_v2_column_description_get_name()`, - * `duckdb_v2_column_description_get_type()`, `duckdb_v2_column_description_has_default()` and - * `duckdb_v2_column_description_has_generated()`, and destroy with `duckdb_v2_column_description_destroy()`. - */ -typedef struct _duckdb_v2_column_description { - void *internal_ptr; -} * duckdb_v2_column_description_handle; - -/* --- Constants for catalog --- */ - -/* --- Function pointer typedefs for catalog --- */ - -/* --- Functions for catalog --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_init_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_to_init_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Resolves a table name and snapshots its description. + * Sets the batch callback of the `COPY ... TO` side. * - * Resolves name in the connection's catalogs and returns an owned description of the table it names. name may be - * partial: an unqualified or schema-qualified name resolves through the connection's search path, exactly as the same - * name resolves in SQL. A two-part name tries the first part as a schema and as an attached database, as SQL does, and - * is rejected when both readings exist. A name that resolves to nothing is rejected with the engine's missing-table - * error; a name that resolves to a view is rejected with the engine's not-a-table error, since a description snapshots - * a base table. + * The batch callback is invoked during query execution with a batch of the rows being written, taken via + * `duckdb_v2_copy_to_batch_take_input()`. It prepares the batch for writing, e.g. by encoding it into the output + * format, and sets "batch data" via `duckdb_v2_copy_to_batch_set_batch_data()` that is handed to the flush callback. + * Batches may be prepared by several threads at once, so the callback must synchronize its own access to the init data. + * A batch callback must be set for the `COPY ... TO` side to be registered. * * history: * - stable: v2.0.0 * - * @param conn The connection whose catalogs and search path resolve the name. - * @param name The possibly partial table name to resolve. Borrowed for the call only. - * @param desc On success, receives the owned description. Destroy via `duckdb_v2_table_description_destroy()`. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_describe_table(duckdb_v2_connection_handle conn, - duckdb_v2_qname_handle name, - duckdb_v2_table_description_handle *desc, - duckdb_v2_error_info_handle *err); - +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_batch_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_to_batch_callback_fn callback, + duckdb_v2_error_info_handle *err); + /*! - * Returns the resolved qualified name of the described table. + * Sets the flush callback of the `COPY ... TO` side. * - * Returns an owned copy of the fully resolved name: the catalog, schema, and table the lookup landed on, with the - * casing the table was created with, never an echo of the requested name. Rendering it produces SQL that pins the - * described table regardless of search path. + * The flush callback is invoked once per prepared batch to write its batch data, available via + * `duckdb_v2_copy_to_flush_get_batch_data()`, to the output. Flushes of the same file never run concurrently. A flush + * callback must be set for the `COPY ... TO` side to be registered. * * history: * - stable: v2.0.0 * - * @param desc The description. - * @param name Receives the owned resolved name. Destroy via `duckdb_v2_qname_destroy()`. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_qname(duckdb_v2_table_description_handle desc, - duckdb_v2_qname_handle *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_flush_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_to_flush_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns whether the described table's catalog is read-only. + * Sets the optional finalize callback of the `COPY ... TO` side. * - * True when the catalog the name resolved into was attached read-only, in which case no write to the table can succeed. - * False does not by itself prove a write will succeed; it clears the catalog-level check only. + * The finalize callback is invoked once per output file after the last batch destined for that file has been flushed. + * It can e.g. write a footer and close the file. * * history: * - stable: v2.0.0 * - * @param desc The description. - * @param readonly Receives whether the catalog is read-only. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_is_readonly(duckdb_v2_table_description_handle desc, - bool *readonly, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_finalize_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_to_finalize_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns of the described table. - * - * Counts every column in declared order, generated columns included. Valid indices for - * `duckdb_v2_table_description_get_column()` are [0, count). + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param desc The description. - * @param count Receives the column count. + * @param info The bind info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_column_count(duckdb_v2_table_description_handle desc, - idx_t *count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_user_data(duckdb_v2_copy_to_bind_info_handle info, void **data, + duckdb_v2_error_info_handle *err); /*! - * Returns an owned description of the column at index. + * Sets the function's "bind data" from the bind callback. * - * Columns are numbered in declared order, generated columns included. An out-of-range index is rejected with - * `ERROR_INPUT_OUT_OF_RANGE`. + * The bind data is stored with the bound statement and retrievable from the other callbacks of the same side. The + * opaque handle bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an + * optional equality callback used when comparing two bound statements; without one, pointer equality is used. * * history: * - stable: v2.0.0 * - * @param desc The description. - * @param index Zero-based column index. - * @param column Receives the owned column description. Destroy via `duckdb_v2_column_description_destroy()`. + * @param info The bind info handle. + * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_get_column(duckdb_v2_table_description_handle desc, - idx_t index, - duckdb_v2_column_description_handle *column, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_set_bind_data(duckdb_v2_copy_to_bind_info_handle info, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Destroys the table description, releasing its resources. + * Retrieves the path the `COPY ... TO` statement writes to, as written in the statement. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Column descriptions obtained from it are independent and stay valid. + * This is the target before the engine's own rewrites: the path of each file actually being written, e.g. a temporary + * name or a per-partition path, is only known to the init callback via `duckdb_v2_copy_to_init_get_file_path()`. The + * path is borrowed and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param desc The description to destroy. + * @param info The bind info handle. + * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_description_destroy(duckdb_v2_table_description_handle *desc); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_file_path(duckdb_v2_copy_to_bind_info_handle info, + duckdb_v2_str *path, + duckdb_v2_error_info_handle *err); /*! - * Borrows the name of the column. + * Returns the number of columns being written, i.e. the columns of the rows every batch carries. * - * The name carries the casing the column was declared with. + * Valid indices for `duckdb_v2_copy_to_bind_get_column_type()` and `duckdb_v2_copy_to_bind_get_column_name()` are [0, + * count). * * history: * - stable: v2.0.0 * - * @param column The column description. - * @param name Receives a borrowed view of the column name. Valid until the column description is destroyed. + * @param info The bind info handle. + * @param count Receives the number of columns. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_get_name(duckdb_v2_column_description_handle column, - duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_count(duckdb_v2_copy_to_bind_info_handle info, + idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Borrows the type of the column. + * Retrieves the type of the column at the given index. + * + * Fails if the index is out of bounds. The returned type is owned by the caller and must be destroyed via + * `duckdb_v2_logical_type_destroy()`. * * history: * - stable: v2.0.0 * - * @param column The column description. - * @param type Receives the borrowed column type. Valid until the column description is destroyed; do not destroy it. + * @param info The bind info handle. + * @param index The index of the column to get the type of. + * @param type Receives the column type. Owned by the caller; destroy via `duckdb_v2_logical_type_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_get_type(duckdb_v2_column_description_handle column, - duckdb_v2_logical_type_handle *type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_type(duckdb_v2_copy_to_bind_info_handle info, + idx_t index, duckdb_v2_logical_type_handle *type, + duckdb_v2_error_info_handle *err); /*! - * Returns whether the column has a default value. + * Retrieves the name of the column at the given index. * - * True when the column declares a default expression; the engine evaluates it for rows that omit the column. Generated - * columns report false. + * Fails if the index is out of bounds. The name is borrowed and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param column The column description. - * @param has_default Receives whether the column has a default value. + * @param info The bind info handle. + * @param index The index of the column to get the name of. + * @param name Receives a borrowed view of the column name. Valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_has_default(duckdb_v2_column_description_handle column, - bool *has_default, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_name(duckdb_v2_copy_to_bind_info_handle info, + idx_t index, duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Returns whether the column is generated. + * Returns the number of options the `COPY` statement passed to the function. * - * True when the column is a generated column, computed by the engine from a generation expression and not writable. + * The engine's own options (e.g. `USE_TMP_FILE` or `BATCH_SIZE`) are handled before the function sees the statement and + * are not included. Valid indices for `duckdb_v2_copy_to_bind_get_option_name()` and + * `duckdb_v2_copy_to_bind_get_option_value()` are [0, count). The options are ordered by name. * * history: * - stable: v2.0.0 * - * @param column The column description. - * @param has_generated Receives whether the column is generated. + * @param info The bind info handle. + * @param count Receives the number of options. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_has_generated(duckdb_v2_column_description_handle column, - bool *has_generated, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_count(duckdb_v2_copy_to_bind_info_handle info, + idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Destroys the column description, releasing its resources. + * Retrieves the name of the option at the given index. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Any name or type borrowed from it becomes dangling. + * Option names are SQL identifiers, matched case-insensitively. Fails if the index is out of bounds. The name is + * borrowed and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param column The column description to destroy. + * @param info The bind info handle. + * @param index The index of the option to get the name of. + * @param name Receives a borrowed view of the option name. Valid only for the duration of the callback. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_column_description_destroy(duckdb_v2_column_description_handle *column); - -/* --- Struct definitions for catalog --- */ - -/* ============================================================================ - * MODULE: connection - * ============================================================================ */ - -/* --- Enums for connection --- */ - -/* --- Struct forward declarations for connection --- */ - -/* --- Types for connection --- */ - -/* --- Constants for connection --- */ - -/* --- Function pointer typedefs for connection --- */ - -/* --- Functions for connection --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_name(duckdb_v2_copy_to_bind_info_handle info, + idx_t index, duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Opens a connection to an instance, starting it if it has not started yet. + * Retrieves the value of the option at the given index. * - * Each connection carries its own client context and session-scoped (LOCAL) settings. Connections to the same instance - * share its catalog, buffer pool, and transaction manager. A connection may be created before any database is attached - * to the instance; until one is, only the system catalog and the connection's temporary catalog are visible. The caller - * destroys it via `duckdb_v2_connection_destroy()`. + * An option written with a single value (e.g. `DELIM ','`) yields that value. An option written as a bare name (e.g. + * `HEADER`) yields the BOOLEAN `true`. An option written with a parenthesized list (e.g. `KEYS (a, b)`) yields a tuple: + * an unnamed STRUCT with one field per element, in order. Fails if the index is out of bounds. The returned value is + * owned by the caller and must be destroyed via `duckdb_v2_value_destroy()`. * * history: * - stable: v2.0.0 * - * @param instance The instance to connect to. - * @param out_conn Receives the new connection handle. + * @param info The bind info handle. + * @param index The index of the option. + * @param value Receives the option's value. Owned by the caller; destroy via `duckdb_v2_value_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create(duckdb_v2_instance_handle instance, - duckdb_v2_connection_handle *out_conn, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_value(duckdb_v2_copy_to_bind_info_handle info, + idx_t index, duckdb_v2_value_handle *value, + duckdb_v2_error_info_handle *err); /*! - * Destroys the connection. - * - * Always succeeds. Releases the connection's reference on the underlying database instance, which survives for as long - * as anything else still references it. On success the handle is set to null. Safe to call on an already-null slot. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection to destroy. + * @param info The batch size info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_destroy(duckdb_v2_connection_handle *conn); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_get_user_data(duckdb_v2_copy_to_batch_size_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets a config option through the connection. - * - * `scope` chooses the destination, mirroring SQL: - * - AUTOMATIC resolves it from the option's target scope, like a bare `SET name = setting`. - * - GLOBAL writes through to the instance, visible to all connections, like `SET GLOBAL`. - * - LOCAL writes to this connection's session only, like `SET LOCAL` / `SET SESSION`. - * A disallowed combination returns ERROR_INPUT_INVALID: GLOBAL against a LOCAL_ONLY option, LOCAL against a GLOBAL_ONLY - * one, and the legacy analogues. An unknown name is rejected unless an extension that defines it can be autoloaded; to - * stage a setting for an extension before startup, use `duckdb_v2_instance_set_option()`. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: * - stable: v2.0.0 * - * @param conn The connection. - * @param name Option name (canonical or alias). - * @param setting The setting, in the textual form SQL `SET` accepts. - * @param scope Target scope: AUTOMATIC, GLOBAL, or LOCAL. + * @param info The batch size info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_set_option(duckdb_v2_connection_handle conn, - const duckdb_v2_identifier_t *name, - const duckdb_v2_str *setting, - DUCKDB_V2_SETTING_SCOPE scope, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_get_bind_data(duckdb_v2_copy_to_batch_size_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Reads a config option through the connection. - * - * Returns the option's effective setting at the connection's scope: the LOCAL override if this connection set one, - * otherwise the GLOBAL value, otherwise the static default. The remaining fields are populated exactly as by - * `duckdb_v2_instance_get_option_by_name()`. Aliases resolve transparently, and an unknown name returns - * ERROR_INPUT_INVALID. The caller destroys the returned option. + * Sets the number of rows a batch should carry, as the target the engine cuts batches at. The batch size callback must + * set this to a value greater than 0; the statement fails otherwise. DuckDB defines the `BATCH_SIZE` on omission of + * calling the function. * * history: * - stable: v2.0.0 * - * @param conn The connection. - * @param name Option name (canonical or alias). - * @param out_option Receives the populated option handle. + * @param info The batch size info handle. + * @param rows The number of rows a batch should carry. Must be greater than 0. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_option_by_name(duckdb_v2_connection_handle conn, - const duckdb_v2_identifier_t *name, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_set_target(duckdb_v2_copy_to_batch_size_info_handle info, + idx_t rows, duckdb_v2_error_info_handle *err); /*! - * Returns the number of config options visible to this connection. - * - * The same count `duckdb_v2_instance_get_option_count()` reports for the underlying instance: core plus extension - * options, aliases excluded. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection. - * @param out_count Receives the option count. + * @param info The init info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_option_count(duckdb_v2_connection_handle conn, idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_user_data(duckdb_v2_copy_to_init_info_handle info, void **data, + duckdb_v2_error_info_handle *err); /*! - * Reads the config option at the given index visible to this connection. - * - * Index space: [0, core_count) addresses core options, [core_count, total) the extension options visible from this - * connection's instance. An out-of-range index returns ERROR_INPUT_INVALID. The caller destroys the returned option. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: * - stable: v2.0.0 * - * @param conn The connection. - * @param index The option index, in [0, option_count). - * @param out_option Receives the populated option handle. + * @param info The init info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_get_option_by_index(duckdb_v2_connection_handle conn, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_bind_data(duckdb_v2_copy_to_init_info_handle info, void **data, + duckdb_v2_error_info_handle *err); /*! - * Interrupts the query currently executing on the connection. + * Retrieves the path of the file the init callback is preparing. * - * The cross-thread (but not cross-connection) cancellation entry point for streaming results: safe to call from any - * thread within the execution of a query through a connection, including while another thread steps the query's result. - * A no-op when no query is active. Cancellation surfaces on the consuming side as step status CANCELLED - * (`duckdb_v2_result_step()`), or as ERROR_RUNTIME_INTERRUPT (`duckdb_v2_result_fetch_chunk()`). + * This is the path the batches of this file are to be written to, after the engine has applied its own rewrites (e.g. a + * temporary name while the file is being written, or a per-partition path). The path is borrowed and valid only for the + * duration of the callback. * * history: * - stable: v2.0.0 * - * @param conn The connection whose active query to interrupt. + * @param info The init info handle. + * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_interrupt(duckdb_v2_connection_handle conn, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_file_path(duckdb_v2_copy_to_init_info_handle info, + duckdb_v2_str *path, + duckdb_v2_error_info_handle *err); /*! - * Captures a snapshot of the active query's execution progress. - * - * Reads the percentage and row counts from one consistent snapshot of the query currently executing on the connection. - * Safe to call from any thread, including while another thread steps the query's result. + * Sets the function's "init data" from the init callback. * - * Progress is published only when the enable_progress_bar option is set; the bridge does not enable tracking itself. - * Both row counts are 0 when no information is available. The percentage is -1 when tracking is disabled, no query is - * active, or no progress has been published yet. + * The init data lives for the duration of the file being written and is retrievable from the batch, flush and finalize + * callbacks of that file. Batches may be prepared by several threads at once, so the function must synchronize its own + * access to it from the batch callback. The opaque handle bundles the pointer with an optional destructor, invoked when + * the file is done with the data. * * history: * - stable: v2.0.0 * - * @param conn The connection. - * @param out_percentage Receives the percentage complete in [0, 100], -1 if tracking is disabled, no query is active, - * or no progress has been published yet. - * @param out_rows_processed Receives the number of rows processed so far. - * @param out_total_rows_to_process Receives the total number of rows the query will process. + * @param info The init info handle. + * @param data Opaque handle bundling the init data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_progress_get(duckdb_v2_connection_handle conn, double *out_percentage, - uint64_t *out_rows_processed, - uint64_t *out_total_rows_to_process, - duckdb_v2_error_info_handle *err); - -/* --- Struct definitions for connection --- */ - -/* ============================================================================ - * MODULE: copy - * ============================================================================ */ - -/* --- Enums for copy --- */ - -/* --- Struct forward declarations for copy --- */ - -/* --- Types for copy --- */ - -/*! - * An owned opaque handle to a custom copy function being built: an output format for `COPY ... TO`, an input format for - * `COPY ... FROM`, or both. Created with `duckdb_v2_copy_function_create_with_connection()` or - * `duckdb_v2_copy_function_create_with_extension()`, configured with the setter functions (e.g. - * `duckdb_v2_copy_function_set_name()`, `duckdb_v2_copy_to_set_batch_callback()`, - * `duckdb_v2_copy_from_set_exec_callback()`, etc.), made available with `duckdb_v2_copy_function_register()`, and - * destroyed with `duckdb_v2_copy_function_destroy()`. - */ -typedef struct _duckdb_v2_copy_function { - void *internal_ptr; -} * duckdb_v2_copy_function_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the query preparation "bind" phase of a - * `COPY ... TO` statement. The "bind" callback receives this handle and can use it to e.g. inspect the names and types - * of the columns being written, read the statement's options and initialize some constant state. - */ -typedef struct _duckdb_v2_copy_to_bind_info { - void *internal_ptr; -} * duckdb_v2_copy_to_bind_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the "batch size" phase of a `COPY ... - * TO` statement. The "batch size" callback receives this handle and must use it to report how many rows a batch should - * carry. - */ -typedef struct _duckdb_v2_copy_to_batch_size_info { - void *internal_ptr; -} * duckdb_v2_copy_to_batch_size_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the per-file "init" phase of a `COPY ... - * TO` statement. The "init" callback receives this handle and can use it to e.g. read the path of the file being - * written and set up the state shared by every batch written to that file. - */ -typedef struct _duckdb_v2_copy_to_init_info { - void *internal_ptr; -} * duckdb_v2_copy_to_init_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the "batch" phase of a `COPY ... TO` - * statement. The "batch" callback receives this handle and can use it to take ownership of the rows of the batch and to - * set the prepared form of the batch handed to the "flush" callback. - */ -typedef struct _duckdb_v2_copy_to_batch_info { - void *internal_ptr; -} * duckdb_v2_copy_to_batch_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the "flush" phase of a `COPY ... TO` - * statement. The "flush" callback receives this handle and can use it to access the prepared batch and write it to the - * output. - */ -typedef struct _duckdb_v2_copy_to_flush_info { - void *internal_ptr; -} * duckdb_v2_copy_to_flush_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the per-file "finalize" phase of a `COPY - * ... TO` statement. The "finalize" callback receives this handle and can use it to e.g. close the file after every - * batch has been flushed to it. - */ -typedef struct _duckdb_v2_copy_to_finalize_info { - void *internal_ptr; -} * duckdb_v2_copy_to_finalize_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function when a `COPY ... TO` statement reports - * statistics about the files it wrote (e.g. with `RETURN_STATS`). The "statistics" callback receives this handle and - * uses it to report the statistics of one written file. - */ -typedef struct _duckdb_v2_copy_to_statistics_info { - void *internal_ptr; -} * duckdb_v2_copy_to_statistics_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the query preparation "bind" phase of a - * `COPY ... FROM` statement. The "bind" callback receives this handle and can use it to e.g. read the path of the file - * to read, inspect the names and types of the columns the target table expects, read the statement's options, hint at - * the number of rows the read will produce and initialize some constant state. - */ -typedef struct _duckdb_v2_copy_from_bind_info { - void *internal_ptr; -} * duckdb_v2_copy_from_bind_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the global state initialization "init - * global" phase of a `COPY ... FROM` statement. The "init global" callback receives this handle and can use it to e.g. - * set up the state shared by every thread reading the file, and to declare how many threads may read it. - */ -typedef struct _duckdb_v2_copy_from_init_global_info { - void *internal_ptr; -} * duckdb_v2_copy_from_init_global_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the local state initialization "init - * local" phase of a `COPY ... FROM` statement. The "init local" callback receives this handle and can use it to e.g. - * set up worker-local state, typically by claiming work from the shared global state. - */ -typedef struct _duckdb_v2_copy_from_init_local_info { - void *internal_ptr; -} * duckdb_v2_copy_from_init_local_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the execution "exec" phase of a `COPY - * ... FROM` statement. The "exec" callback receives this handle and can use it to e.g. access the global and local - * state and write the next batch of rows to the output chunk. - */ -typedef struct _duckdb_v2_copy_from_exec_info { - void *internal_ptr; -} * duckdb_v2_copy_from_exec_info_handle; - -/*! - * A borrowed opaque handle to the arguments supplied to a copy function during the "progress" phase of a `COPY ... - * FROM` statement. The "progress" callback receives this handle and can use it to report how far the read has advanced. - */ -typedef struct _duckdb_v2_copy_from_progress_info { - void *internal_ptr; -} * duckdb_v2_copy_from_progress_info_handle; - -/* --- Constants for copy --- */ - -/* --- Function pointer typedefs for copy --- */ - -typedef void (*duckdb_v2_copy_to_bind_callback_fn)(duckdb_v2_copy_to_bind_info_handle info, - duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_batch_size_callback_fn)(duckdb_v2_copy_to_batch_size_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_init_callback_fn)(duckdb_v2_copy_to_init_info_handle info, - duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_batch_callback_fn)(duckdb_v2_copy_to_batch_info_handle info, - duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_flush_callback_fn)(duckdb_v2_copy_to_flush_info_handle info, - duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_finalize_callback_fn)(duckdb_v2_copy_to_finalize_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_to_statistics_callback_fn)(duckdb_v2_copy_to_statistics_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_from_bind_callback_fn)(duckdb_v2_copy_from_bind_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_from_init_global_callback_fn)(duckdb_v2_copy_from_init_global_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_from_init_local_callback_fn)(duckdb_v2_copy_from_init_local_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_from_exec_callback_fn)(duckdb_v2_copy_from_exec_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_copy_from_progress_callback_fn)(duckdb_v2_copy_from_progress_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -/* --- Functions for copy --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_set_init_data(duckdb_v2_copy_to_init_info_handle info, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Creates a new copy function that will be registered on the connection's database. - * - * A copy function implements a file format for `COPY`: once registered, SQL reaches it with `COPY ... TO 'path' (FORMAT - * name)` and `COPY table FROM 'path' (FORMAT name)`. The function starts out empty: configure it with the setter - * functions (e.g. `duckdb_v2_copy_function_set_name()`, the `copy_to_set_*` callbacks for writing, the - * `copy_from_set_*` callbacks for reading, or both), then make it available with `duckdb_v2_copy_function_register()`. - * The caller owns the returned handle and must destroy it with `duckdb_v2_copy_function_destroy()`, also after - * registration. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created copy function. Owned by the caller. + * @param info The batch info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_user_data(duckdb_v2_copy_to_batch_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a new copy function that will be registered on the loading extension's database. - * - * A copy function implements a file format for `COPY`: once registered, SQL reaches it with `COPY ... TO 'path' (FORMAT - * name)` and `COPY table FROM 'path' (FORMAT name)`. Use this from an extension load callback, where an extension - * handle is available. The function starts out empty: configure it with the setter functions (e.g. - * `duckdb_v2_copy_function_set_name()`, the `copy_to_set_*` callbacks for writing, the `copy_from_set_*` callbacks for - * reading, or both), then make it available with `duckdb_v2_copy_function_register()`. The caller owns the returned - * handle and must destroy it with `duckdb_v2_copy_function_destroy()`, also after registration. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the function in. - * @param function On success, receives the newly created copy function. Owned by the caller. + * @param info The batch info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_bind_data(duckdb_v2_copy_to_batch_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the name of the copy function. - * - * The name is the format SQL selects the function with: `COPY ... TO 'path' (FORMAT name)` or `COPY table FROM 'path' - * (FORMAT name)`. It is borrowed and copied. Calling this again replaces the previous name. A name must be set before - * registration. + * Retrieves the init data set by the function's `COPY ... TO` init callback for the file this batch belongs to. * * history: * - stable: v2.0.0 * - * @param function The function to set the name of. - * @param name The name to set. Borrowed and copied. + * @param info The batch info handle. + * @param data Receives the init data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_set_name(duckdb_v2_copy_function_handle function, - const duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_init_data(duckdb_v2_copy_to_batch_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets arbitrary user data on the copy function. + * Takes ownership of the rows of the batch to prepare. * - * Associates an opaque pointer with the function, retrievable from each callback of either side via its user data - * accessor (e.g. `duckdb_v2_copy_to_bind_get_user_data()`, `duckdb_v2_copy_from_exec_get_user_data()`, etc.). The - * opaque handle bundles the pointer with an optional destructor, invoked when the data is no longer needed. + * The collection holds one column per column reported by `duckdb_v2_copy_to_bind_get_column_count()`, in the same + * order, and can be scanned with `duckdb_v2_column_data_collection_scan()`. The batch can only be taken once: a second + * call fails. Once taken, the caller owns the collection and must destroy it via + * `duckdb_v2_column_data_collection_destroy()`, e.g. by keeping it as the batch data with that destructor. A batch that + * is never taken is destroyed when the callback returns. * * history: * - stable: v2.0.0 * - * @param function The function to set the user data of. - * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param info The batch info handle. + * @param collection Receives the collection holding the rows of the batch. Owned by the caller; destroy via + * `duckdb_v2_column_data_collection_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_set_user_data(duckdb_v2_copy_function_handle function, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_take_input(duckdb_v2_copy_to_batch_info_handle info, + duckdb_v2_column_data_collection_handle *collection, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional bind callback of the `COPY ... TO` side. + * Sets the prepared "batch data" from the batch callback. * - * The bind callback is invoked during query planning for each `COPY ... TO` statement that uses the function. It can - * inspect the names and types of the columns being written, read the statement's options and set "bind data" that is - * shared with the other `COPY ... TO` callbacks. + * The batch data is the prepared form of the batch and is handed to the flush callback via + * `duckdb_v2_copy_to_flush_get_batch_data()`. The opaque handle bundles the pointer with an optional destructor, + * invoked once the batch has been flushed. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The batch info handle. + * @param data Opaque handle bundling the batch data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_bind_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_to_bind_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_set_batch_data(duckdb_v2_copy_to_batch_info_handle info, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional batch size callback of the `COPY ... TO` side. - * - * The batch size callback is invoked during query planning, after the bind callback, for each `COPY ... TO` statement - * that does not set `BATCH_SIZE` itself. It should report how many rows a batch should carry via - * `duckdb_v2_copy_to_batch_size_set_target()`; the engine then cuts the rows being written into batches of that size - * and hands each to the batch callback. Without a batch size from either the statement or the callback, a batch is cut - * for every chunk of rows sunk, i.e. a vector at a time. A batch may still be smaller than the reported size (the last - * one of a file, or when `BATCH_SIZE_BYTES` cuts it first). + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The flush info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_batch_size_callback( - duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_batch_size_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_user_data(duckdb_v2_copy_to_flush_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the optional init callback of the `COPY ... TO` side. - * - * The init callback is invoked once per output file, before any batch destined for that file is prepared. It can read - * the path of the file via `duckdb_v2_copy_to_init_get_file_path()` and set "init data" that is shared with the batch, - * flush and finalize callbacks of that file. A statement may write several files, e.g. when its output is partitioned; - * each gets its own init data. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The flush info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_init_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_to_init_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_bind_data(duckdb_v2_copy_to_flush_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the batch callback of the `COPY ... TO` side. - * - * The batch callback is invoked during query execution with a batch of the rows being written, taken via - * `duckdb_v2_copy_to_batch_take_input()`. It prepares the batch for writing, e.g. by encoding it into the output - * format, and sets "batch data" via `duckdb_v2_copy_to_batch_set_batch_data()` that is handed to the flush callback. - * Batches may be prepared by several threads at once, so the callback must synchronize its own access to the init data. - * A batch callback must be set for the `COPY ... TO` side to be registered. + * Retrieves the init data set by the function's `COPY ... TO` init callback for the file this batch belongs to. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The flush info handle. + * @param data Receives the init data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_batch_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_to_batch_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_init_data(duckdb_v2_copy_to_flush_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the flush callback of the `COPY ... TO` side. - * - * The flush callback is invoked once per prepared batch to write its batch data, available via - * `duckdb_v2_copy_to_flush_get_batch_data()`, to the output. Flushes of the same file never run concurrently. A flush - * callback must be set for the `COPY ... TO` side to be registered. + * Retrieves the batch data set by the function's batch callback for the batch being flushed. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The flush info handle. + * @param data Receives the batch data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_flush_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_to_flush_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_batch_data(duckdb_v2_copy_to_flush_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the optional finalize callback of the `COPY ... TO` side. - * - * The finalize callback is invoked once per output file after the last batch destined for that file has been flushed. - * It can e.g. write a footer and close the file. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The finalize info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_finalize_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_to_finalize_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_user_data(duckdb_v2_copy_to_finalize_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The finalize info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_user_data(duckdb_v2_copy_to_bind_info_handle info, void **data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_bind_data(duckdb_v2_copy_to_finalize_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the function's "bind data" from the bind callback. - * - * The bind data is stored with the bound statement and retrievable from the other callbacks of the same side. The - * opaque handle bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an - * optional equality callback used when comparing two bound statements; without one, pointer equality is used. + * Retrieves the init data set by the function's `COPY ... TO` init callback for the file being finalized. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. + * @param info The finalize info handle. + * @param data Receives the init data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_set_bind_data(duckdb_v2_copy_to_bind_info_handle info, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_init_data(duckdb_v2_copy_to_finalize_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the path the `COPY ... TO` statement writes to, as written in the statement. + * Sets the bind callback of the `COPY ... FROM` side. * - * This is the target before the engine's own rewrites: the path of each file actually being written, e.g. a temporary - * name or a per-partition path, is only known to the init callback via `duckdb_v2_copy_to_init_get_file_path()`. The - * path is borrowed and valid only for the duration of the callback. + * The bind callback is invoked during query planning for each `COPY ... FROM` statement that uses the function. It can + * read the path of the file to read and the statement's options, inspect the names and types of the columns the target + * table expects, hint at the number of rows the read will produce, and set "bind data" that is shared with the other + * `COPY ... FROM` callbacks. The columns are fixed by the target table: the function must produce them as they are. A + * bind callback must be set for the `COPY ... FROM` side to be registered. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_file_path(duckdb_v2_copy_to_bind_info_handle info, - duckdb_v2_str *path, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_bind_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_from_bind_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns being written, i.e. the columns of the rows every batch carries. + * Sets the optional global init callback of the `COPY ... FROM` side. * - * Valid indices for `duckdb_v2_copy_to_bind_get_column_type()` and `duckdb_v2_copy_to_bind_get_column_name()` are [0, - * count). + * The global init callback is invoked once per statement, at the start of execution. It can set "global state" shared + * by every thread reading the file, and declare how many threads may read it in parallel via + * `duckdb_v2_copy_from_init_global_set_max_threads()`. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param count Receives the number of columns. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_count(duckdb_v2_copy_to_bind_info_handle info, - idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_init_global_callback( + duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_init_global_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the type of the column at the given index. + * Sets the optional local init callback of the `COPY ... FROM` side. * - * Fails if the index is out of bounds. The returned type is owned by the caller and must be destroyed via - * `duckdb_v2_logical_type_destroy()`. + * The local init callback is invoked once per thread that will read the file. It can set worker-local "local state", + * retrievable from the exec callback, typically derived from the shared global state. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the column to get the type of. - * @param type Receives the column type. Owned by the caller; destroy via `duckdb_v2_logical_type_destroy()`. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_type(duckdb_v2_copy_to_bind_info_handle info, - idx_t index, duckdb_v2_logical_type_handle *type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_init_local_callback( + duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_init_local_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the name of the column at the given index. + * Sets the exec callback of the `COPY ... FROM` side. * - * Fails if the index is out of bounds. The name is borrowed and valid only for the duration of the callback. + * The exec callback implements the read: it is invoked repeatedly during query execution and writes the next batch of + * rows to the output chunk, until it produces an empty batch to signal the end of the read. An exec callback must be + * set for the `COPY ... FROM` side to be registered. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the column to get the name of. - * @param name Receives a borrowed view of the column name. Valid only for the duration of the callback. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_column_name(duckdb_v2_copy_to_bind_info_handle info, - idx_t index, duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_exec_callback(duckdb_v2_copy_function_handle function, + duckdb_v2_copy_from_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of options the `COPY` statement passed to the function. + * Sets the optional progress callback of the `COPY ... FROM` side. * - * The engine's own options (e.g. `USE_TMP_FILE` or `BATCH_SIZE`) are handled before the function sees the statement and - * are not included. Valid indices for `duckdb_v2_copy_to_bind_get_option_name()` and - * `duckdb_v2_copy_to_bind_get_option_value()` are [0, count). The options are ordered by name. + * The progress callback is invoked on demand during execution to report how far the read has advanced, which the engine + * surfaces as the query's progress. It is invoked concurrently with the exec callback, so it must read the global state + * in a thread-safe way. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param count Receives the number of options. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_count(duckdb_v2_copy_to_bind_info_handle info, - idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_progress_callback( + duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_progress_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the name of the option at the given index. - * - * Option names are SQL identifiers, matched case-insensitively. Fails if the index is out of bounds. The name is - * borrowed and valid only for the duration of the callback. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * * @param info The bind info handle. - * @param index The index of the option to get the name of. - * @param name Receives a borrowed view of the option name. Valid only for the duration of the callback. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_name(duckdb_v2_copy_to_bind_info_handle info, - idx_t index, duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_user_data(duckdb_v2_copy_from_bind_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the value of the option at the given index. + * Sets the function's "bind data" from the bind callback. * - * An option written with a single value (e.g. `DELIM ','`) yields that value. An option written as a bare name (e.g. - * `HEADER`) yields the BOOLEAN `true`. An option written with a parenthesized list (e.g. `KEYS (a, b)`) yields a tuple: - * an unnamed STRUCT with one field per element, in order. Fails if the index is out of bounds. The returned value is - * owned by the caller and must be destroyed via `duckdb_v2_value_destroy()`. + * The bind data is stored with the bound statement and retrievable from the other callbacks of the same side. The + * opaque handle bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an + * optional equality callback used when comparing two bound statements; without one, pointer equality is used. * * history: * - stable: v2.0.0 * * @param info The bind info handle. - * @param index The index of the option. - * @param value Receives the option's value. Owned by the caller; destroy via `duckdb_v2_value_destroy()`. + * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_bind_get_option_value(duckdb_v2_copy_to_bind_info_handle info, - idx_t index, duckdb_v2_value_handle *value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_set_bind_data(duckdb_v2_copy_from_bind_info_handle info, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Retrieves the path the `COPY ... FROM` statement reads from, as written in the statement. + * + * The engine does not expand globs or check that the file exists; both are left to the function. The path is borrowed + * and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param info The batch size info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The bind info handle. + * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_get_user_data(duckdb_v2_copy_to_batch_size_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_file_path(duckdb_v2_copy_from_bind_info_handle info, + duckdb_v2_str *path, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. + * Returns the number of columns the target table expects, i.e. the columns every batch the exec callback produces must + * carry, in this order. + * + * Valid indices for `duckdb_v2_copy_from_bind_get_column_type()` and `duckdb_v2_copy_from_bind_get_column_name()` are + * [0, count). * * history: * - stable: v2.0.0 * - * @param info The batch size info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param info The bind info handle. + * @param count Receives the number of columns. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_get_bind_data(duckdb_v2_copy_to_batch_size_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_count(duckdb_v2_copy_from_bind_info_handle info, + idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Sets the number of rows a batch should carry, as the target the engine cuts batches at. The batch size callback must - * set this to a value greater than 0; the statement fails otherwise. DuckDB defines the `BATCH_SIZE` on omission of - * calling the function. + * Retrieves the type of the column at the given index. + * + * Fails if the index is out of bounds. The returned type is owned by the caller and must be destroyed via + * `duckdb_v2_logical_type_destroy()`. * * history: * - stable: v2.0.0 * - * @param info The batch size info handle. - * @param rows The number of rows a batch should carry. Must be greater than 0. + * @param info The bind info handle. + * @param index The index of the column to get the type of. + * @param type Receives the column type. Owned by the caller; destroy via `duckdb_v2_logical_type_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_size_set_target(duckdb_v2_copy_to_batch_size_info_handle info, - idx_t rows, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_type(duckdb_v2_copy_from_bind_info_handle info, + idx_t index, duckdb_v2_logical_type_handle *type, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Retrieves the name of the column at the given index. + * + * Fails if the index is out of bounds. The name is borrowed and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The bind info handle. + * @param index The index of the column to get the name of. + * @param name Receives a borrowed view of the column name. Valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_user_data(duckdb_v2_copy_to_init_info_handle info, void **data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_name(duckdb_v2_copy_from_bind_info_handle info, + idx_t index, duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. + * Returns the number of options the `COPY` statement passed to the function. + * + * Every option other than `FORMAT` is included. Valid indices for `duckdb_v2_copy_from_bind_get_option_name()` and + * `duckdb_v2_copy_from_bind_get_option_value()` are [0, count). The options are ordered by name. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param info The bind info handle. + * @param count Receives the number of options. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_bind_data(duckdb_v2_copy_to_init_info_handle info, void **data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_count(duckdb_v2_copy_from_bind_info_handle info, + idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Retrieves the path of the file the init callback is preparing. + * Retrieves the name of the option at the given index. * - * This is the path the batches of this file are to be written to, after the engine has applied its own rewrites (e.g. a - * temporary name while the file is being written, or a per-partition path). The path is borrowed and valid only for the - * duration of the callback. + * Option names are SQL identifiers, matched case-insensitively. Fails if the index is out of bounds. The name is + * borrowed and valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. + * @param info The bind info handle. + * @param index The index of the option to get the name of. + * @param name Receives a borrowed view of the option name. Valid only for the duration of the callback. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_get_file_path(duckdb_v2_copy_to_init_info_handle info, - duckdb_v2_str *path, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_name(duckdb_v2_copy_from_bind_info_handle info, + idx_t index, duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's "init data" from the init callback. + * Retrieves the value of the option at the given index. * - * The init data lives for the duration of the file being written and is retrievable from the batch, flush and finalize - * callbacks of that file. Batches may be prepared by several threads at once, so the function must synchronize its own - * access to it from the batch callback. The opaque handle bundles the pointer with an optional destructor, invoked when - * the file is done with the data. + * An option written with a single value (e.g. `DELIM ','`) yields that value. An option written as a bare name (e.g. + * `HEADER`) yields the BOOLEAN `true`. An option written with a parenthesized list (e.g. `KEYS (a, b)`) yields a tuple: + * an unnamed STRUCT with one field per element, in order. Fails if the index is out of bounds. The returned value is + * owned by the caller and must be destroyed via `duckdb_v2_value_destroy()`. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Opaque handle bundling the init data pointer plus an optional destructor. + * @param info The bind info handle. + * @param index The index of the option. + * @param value Receives the option's value. Owned by the caller; destroy via `duckdb_v2_value_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_init_set_init_data(duckdb_v2_copy_to_init_info_handle info, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_value(duckdb_v2_copy_from_bind_info_handle info, + idx_t index, duckdb_v2_value_handle *value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Reports the estimated number of rows the read will produce. + * + * The estimate is a hint for the optimizer, not a limit: producing a different number of rows is not an error. Without + * it, the optimizer falls back on its own defaults. * * history: * - stable: v2.0.0 * - * @param info The batch info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The bind info handle. + * @param cardinality The estimated number of rows. + * @param is_exact Whether the estimate is exact, which also makes it an upper bound on the row count. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_user_data(duckdb_v2_copy_to_batch_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_set_cardinality(duckdb_v2_copy_from_bind_info_handle info, + idx_t cardinality, bool is_exact, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param info The batch info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param info The global init info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_bind_data(duckdb_v2_copy_to_batch_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_get_user_data( + duckdb_v2_copy_from_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the init data set by the function's `COPY ... TO` init callback for the file this batch belongs to. + * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. * * history: * - stable: v2.0.0 * - * @param info The batch info handle. - * @param data Receives the init data pointer, or null if none was set. + * @param info The global init info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_get_init_data(duckdb_v2_copy_to_batch_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_get_bind_data( + duckdb_v2_copy_from_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Takes ownership of the rows of the batch to prepare. + * Sets the function's "global state" from the global init callback. * - * The collection holds one column per column reported by `duckdb_v2_copy_to_bind_get_column_count()`, in the same - * order, and can be scanned with `duckdb_v2_column_data_collection_scan()`. The batch can only be taken once: a second - * call fails. Once taken, the caller owns the collection and must destroy it via - * `duckdb_v2_column_data_collection_destroy()`, e.g. by keeping it as the batch data with that destructor. A batch that - * is never taken is destroyed when the callback returns. + * The global state lives for the duration of the read and is retrievable from the local init, exec and progress + * callbacks. Every thread reading the file shares it, so the function must synchronize its own access to it. The opaque + * handle bundles the pointer with an optional destructor, invoked when the read is done with the state. * * history: * - stable: v2.0.0 * - * @param info The batch info handle. - * @param collection Receives the collection holding the rows of the batch. Owned by the caller; destroy via - * `duckdb_v2_column_data_collection_destroy()`. + * @param info The global init info handle. + * @param data Opaque handle bundling the global state pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_take_input(duckdb_v2_copy_to_batch_info_handle info, - duckdb_v2_column_data_collection_handle *collection, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_set_global_state( + duckdb_v2_copy_from_init_global_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Sets the prepared "batch data" from the batch callback. + * Sets how many threads may read the file in parallel. * - * The batch data is the prepared form of the batch and is handed to the flush callback via - * `duckdb_v2_copy_to_flush_get_batch_data()`. The opaque handle bundles the pointer with an optional destructor, - * invoked once the batch has been flushed. + * Defaults to 1, a single-threaded read. The engine creates at most this many local states, and therefore runs at most + * this many exec callbacks concurrently. It is an upper bound, not a request: the engine may use fewer threads. * * history: * - stable: v2.0.0 * - * @param info The batch info handle. - * @param data Opaque handle bundling the batch data pointer plus an optional destructor. + * @param info The global init info handle. + * @param max_threads The maximum number of threads. Must be at least 1. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_set_batch_data(duckdb_v2_copy_to_batch_info_handle info, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_set_max_threads( + duckdb_v2_copy_from_init_global_info_handle info, idx_t max_threads, duckdb_v2_error_info_handle *err); /*! * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. @@ -6481,59 +6733,66 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_batch_set_batch_data(duckdb_v2_co * history: * - stable: v2.0.0 * - * @param info The flush info handle. + * @param info The local init info handle. * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_user_data(duckdb_v2_copy_to_flush_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_user_data( + duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. + * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. * * history: * - stable: v2.0.0 * - * @param info The flush info handle. + * @param info The local init info handle. * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_bind_data(duckdb_v2_copy_to_flush_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_bind_data( + duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the init data set by the function's `COPY ... TO` init callback for the file this batch belongs to. + * Retrieves the global state set by the function's global init callback. + * + * Shared with every other thread reading the file; access to it must be synchronized by the function. * * history: * - stable: v2.0.0 * - * @param info The flush info handle. - * @param data Receives the init data pointer, or null if none was set. + * @param info The local init info handle. + * @param data Receives the global state pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_init_data(duckdb_v2_copy_to_flush_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_global_state( + duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the batch data set by the function's batch callback for the batch being flushed. + * Sets the function's worker-local "local state" from the local init callback. + * + * The local state is associated with the executing thread for the duration of the read and retrievable from the exec + * callback via `duckdb_v2_copy_from_exec_get_local_state()`. No other thread observes it, so it needs no + * synchronization. The opaque handle bundles the pointer with an optional destructor, invoked when the local state is + * no longer needed. * * history: * - stable: v2.0.0 * - * @param info The flush info handle. - * @param data Receives the batch data pointer, or null if none was set. + * @param info The local init info handle. + * @param data Opaque handle bundling the local state pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_batch_data(duckdb_v2_copy_to_flush_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_set_local_state( + duckdb_v2_copy_from_init_local_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. @@ -6541,786 +6800,698 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_flush_get_batch_data(duckdb_v2_co * history: * - stable: v2.0.0 * - * @param info The finalize info handle. + * @param info The exec info handle. * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_user_data(duckdb_v2_copy_to_finalize_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_user_data(duckdb_v2_copy_from_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. + * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. * * history: * - stable: v2.0.0 * - * @param info The finalize info handle. + * @param info The exec info handle. * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_bind_data(duckdb_v2_copy_to_finalize_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_bind_data(duckdb_v2_copy_from_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the init data set by the function's `COPY ... TO` init callback for the file being finalized. + * Retrieves the global state set by the function's global init callback. + * + * Shared with every other thread reading the file; access to it must be synchronized by the function. * * history: * - stable: v2.0.0 * - * @param info The finalize info handle. - * @param data Receives the init data pointer, or null if none was set. + * @param info The exec info handle. + * @param data Receives the global state pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_finalize_get_init_data(duckdb_v2_copy_to_finalize_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_global_state(duckdb_v2_copy_from_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the bind callback of the `COPY ... FROM` side. - * - * The bind callback is invoked during query planning for each `COPY ... FROM` statement that uses the function. It can - * read the path of the file to read and the statement's options, inspect the names and types of the columns the target - * table expects, hint at the number of rows the read will produce, and set "bind data" that is shared with the other - * `COPY ... FROM` callbacks. The columns are fixed by the target table: the function must produce them as they are. A - * bind callback must be set for the `COPY ... FROM` side to be registered. + * Retrieves the worker-local local state set by the function's local init callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The exec info handle. + * @param data Receives the local state pointer for the executing thread, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_bind_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_from_bind_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_local_state(duckdb_v2_copy_from_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the optional global init callback of the `COPY ... FROM` side. + * Retrieves the output chunk the exec callback must write the next batch of rows into. * - * The global init callback is invoked once per statement, at the start of execution. It can set "global state" shared - * by every thread reading the file, and declare how many threads may read it in parallel via - * `duckdb_v2_copy_from_init_global_set_max_threads()`. + * The chunk holds one vector per column reported by `duckdb_v2_copy_from_bind_get_column_count()`, in the same order; + * reach them with `duckdb_v2_data_chunk_get_vector()`. The chunk starts out empty on every invocation: write the rows, + * then declare how many there are with `duckdb_v2_vector_set_size()` on the first vector, which the engine takes as the + * batch's row count and propagates to the other vectors. Producing an empty batch signals the end of the read, after + * which the callback is not invoked again on that thread. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The exec info handle. + * @param chunk Receives the borrowed output chunk to write into. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_init_global_callback( - duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_init_global_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_output_chunk(duckdb_v2_copy_from_exec_info_handle info, + duckdb_v2_data_chunk_handle *chunk, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional local init callback of the `COPY ... FROM` side. - * - * The local init callback is invoked once per thread that will read the file. It can set worker-local "local state", - * retrievable from the exec callback, typically derived from the shared global state. + * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The progress info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_init_local_callback( - duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_init_local_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_user_data(duckdb_v2_copy_from_progress_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the exec callback of the `COPY ... FROM` side. - * - * The exec callback implements the read: it is invoked repeatedly during query execution and writes the next batch of - * rows to the output chunk, until it produces an empty batch to signal the end of the read. An exec callback must be - * set for the `COPY ... FROM` side to be registered. + * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The progress info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_exec_callback(duckdb_v2_copy_function_handle function, - duckdb_v2_copy_from_exec_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_bind_data(duckdb_v2_copy_from_progress_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets the optional progress callback of the `COPY ... FROM` side. + * Retrieves the global state set by the function's global init callback. * - * The progress callback is invoked on demand during execution to report how far the read has advanced, which the engine - * surfaces as the query's progress. It is invoked concurrently with the exec callback, so it must read the global state - * in a thread-safe way. + * The progress callback runs concurrently with the exec callbacks reading the file, so it must read the global state in + * a thread-safe way. * * history: * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The callback to set. + * @param info The progress info handle. + * @param data Receives the global state pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_set_progress_callback( - duckdb_v2_copy_function_handle function, duckdb_v2_copy_from_progress_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_global_state( + duckdb_v2_copy_from_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Reports how far the read has advanced. + * + * A fraction between 0.0 (nothing read yet) and 1.0 (done); values outside that range are clamped. A progress callback + * that returns without calling this reports no progress. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The progress info handle. + * @param progress The fraction of the read that is complete, in [0.0, 1.0]. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_user_data(duckdb_v2_copy_from_bind_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_set_progress(duckdb_v2_copy_from_progress_info_handle info, + double progress, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's "bind data" from the bind callback. + * Registers the copy function, making it available as a `COPY` format. * - * The bind data is stored with the bound statement and retrievable from the other callbacks of the same side. The - * opaque handle bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an - * optional equality callback used when comparing two bound statements; without one, pointer equality is used. + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. Registration requires a name and at least one configured side: a `COPY ... TO` side needs its + * batch and flush callbacks, a `COPY ... FROM` side needs its bind and exec callbacks. A statement in a direction the + * function does not implement fails with an error. The copy function must have been created through a factory of the + * same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_copy_function_destroy()`, which does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. + * @param conn The connection to register on. + * @param function The copy function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_set_bind_data(duckdb_v2_copy_from_bind_info_handle info, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_copy_function(duckdb_v2_connection_handle conn, + duckdb_v2_copy_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the path the `COPY ... FROM` statement reads from, as written in the statement. + * Registers the copy function, making it available as a `COPY` format. * - * The engine does not expand globs or check that the file exists; both are left to the function. The path is borrowed - * and valid only for the duration of the callback. + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name and at least one configured side: a `COPY ... TO` side needs + * its batch and flush callbacks, a `COPY ... FROM` side needs its bind and exec callbacks. A statement in a direction + * the function does not implement fails with an error. The copy function must have been created through a factory of + * the same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_copy_function_destroy()`, which does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param path Receives a borrowed view of the file path. Valid only for the duration of the callback. + * @param extension The loading extension. + * @param function The copy function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_file_path(duckdb_v2_copy_from_bind_info_handle info, - duckdb_v2_str *path, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_copy_function(duckdb_v2_extension_handle extension, + duckdb_v2_copy_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the target table expects, i.e. the columns every batch the exec callback produces must - * carry, in this order. + * Destroys the copy function, releasing its resources. * - * Valid indices for `duckdb_v2_copy_from_bind_get_column_type()` and `duckdb_v2_copy_from_bind_get_column_name()` are - * [0, count). + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param count Receives the number of columns. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param function The function to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_count(duckdb_v2_copy_from_bind_info_handle info, - idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_destroy(duckdb_v2_copy_function_handle *function); +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the type of the column at the given index. + * Sets the optional statistics callback of the `COPY ... TO` side. * - * Fails if the index is out of bounds. The returned type is owned by the caller and must be destroyed via - * `duckdb_v2_logical_type_destroy()`. + * The statistics callback reports statistics about a written file, such as the number of rows and bytes it holds, which + * `COPY ... TO` returns when the statement asks for them with `RETURN_STATS`. It is invoked once per output file, after + * the finalize callback of that file, and only when the statement asks for the statistics. Without a statistics + * callback, `RETURN_STATS` is rejected for the function. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the column to get the type of. - * @param type Receives the column type. Owned by the caller; destroy via `duckdb_v2_logical_type_destroy()`. + * @param function The function to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_type(duckdb_v2_copy_from_bind_info_handle info, - idx_t index, duckdb_v2_logical_type_handle *type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_statistics_callback( + duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_statistics_callback_fn callback, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the name of the column at the given index. - * - * Fails if the index is out of bounds. The name is borrowed and valid only for the duration of the callback. + * Retrieves the user data set on the copy function via `duckdb_v2_copy_function_set_user_data()`. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the column to get the name of. - * @param name Receives a borrowed view of the column name. Valid only for the duration of the callback. + * @param info The statistics info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_column_name(duckdb_v2_copy_from_bind_info_handle info, - idx_t index, duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_user_data(duckdb_v2_copy_to_statistics_info_handle info, + void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Returns the number of options the `COPY` statement passed to the function. - * - * Every option other than `FORMAT` is included. Valid indices for `duckdb_v2_copy_from_bind_get_option_name()` and - * `duckdb_v2_copy_from_bind_get_option_value()` are [0, count). The options are ordered by name. + * Retrieves the bind data set by the function's `COPY ... TO` bind callback. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param count Receives the number of options. + * @param info The statistics info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_count(duckdb_v2_copy_from_bind_info_handle info, - idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_bind_data(duckdb_v2_copy_to_statistics_info_handle info, + void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the name of the option at the given index. - * - * Option names are SQL identifiers, matched case-insensitively. Fails if the index is out of bounds. The name is - * borrowed and valid only for the duration of the callback. + * Retrieves the init data set by the function's `COPY ... TO` init callback for the file being reported. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the option to get the name of. - * @param name Receives a borrowed view of the option name. Valid only for the duration of the callback. + * @param info The statistics info handle. + * @param data Receives the init data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_name(duckdb_v2_copy_from_bind_info_handle info, - idx_t index, duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_init_data(duckdb_v2_copy_to_statistics_info_handle info, + void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the value of the option at the given index. - * - * An option written with a single value (e.g. `DELIM ','`) yields that value. An option written as a bare name (e.g. - * `HEADER`) yields the BOOLEAN `true`. An option written with a parenthesized list (e.g. `KEYS (a, b)`) yields a tuple: - * an unnamed STRUCT with one field per element, in order. Fails if the index is out of bounds. The returned value is - * owned by the caller and must be destroyed via `duckdb_v2_value_destroy()`. + * Reports how many rows were written to the file. Reported as 0 when the callback does not set it. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param index The index of the option. - * @param value Receives the option's value. Owned by the caller; destroy via `duckdb_v2_value_destroy()`. + * @param info The statistics info handle. + * @param row_count The number of rows written to the file. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_get_option_value(duckdb_v2_copy_from_bind_info_handle info, - idx_t index, duckdb_v2_value_handle *value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_set_row_count(duckdb_v2_copy_to_statistics_info_handle info, + idx_t row_count, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Reports the estimated number of rows the read will produce. - * - * The estimate is a hint for the optimizer, not a limit: producing a different number of rows is not an error. Without - * it, the optimizer falls back on its own defaults. + * Reports the size of the written file in bytes. Reported as 0 when the callback does not set it. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param info The bind info handle. - * @param cardinality The estimated number of rows. - * @param is_exact Whether the estimate is exact, which also makes it an upper bound on the row count. + * @param info The statistics info handle. + * @param file_size_bytes The size of the file in bytes. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_bind_set_cardinality(duckdb_v2_copy_from_bind_info_handle info, - idx_t cardinality, bool is_exact, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_set_file_size(duckdb_v2_copy_to_statistics_info_handle info, + idx_t file_size_bytes, + duckdb_v2_error_info_handle *err); +#endif -/*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. - * - * history: - * - stable: v2.0.0 - * - * @param info The global init info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR +/* --- Struct definitions for copy --- */ + +/* ============================================================================ + * MODULE: custom_option + * ============================================================================ */ + +/* --- Enums for custom_option --- */ + +/* --- Struct forward declarations for custom_option --- */ + +/* --- Types for custom_option --- */ + +/*! + * An owned opaque handle to a custom option being built. Created with `duckdb_v2_custom_option_create()`, configured + * with the setter functions (e.g. `duckdb_v2_custom_option_set_name()`, `duckdb_v2_custom_option_set_type()`, etc.), + * made available with `duckdb_v2_connection_register_custom_option()` or + * `duckdb_v2_extension_register_custom_option()`, and destroyed with `duckdb_v2_custom_option_destroy()`. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_get_user_data( - duckdb_v2_copy_from_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_custom_option { + void *internal_ptr; +} * duckdb_v2_custom_option_handle; /*! - * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. - * - * history: - * - stable: v2.0.0 - * - * @param info The global init info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to an update of a custom option, received by its update callback. It gives access to the new + * value, the scope it is written at and the user data, and can replace the value. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_get_bind_data( - duckdb_v2_copy_from_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_custom_option_update_info { + void *internal_ptr; +} * duckdb_v2_custom_option_update_info_handle; + +/* --- Constants for custom_option --- */ + +/* --- Function pointer typedefs for custom_option --- */ + +typedef void (*duckdb_v2_custom_option_update_callback_fn)(duckdb_v2_custom_option_update_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +/* --- Functions for custom_option --- */ /*! - * Sets the function's "global state" from the global init callback. + * Creates a new custom option through a factory. * - * The global state lives for the duration of the read and is retrievable from the local init, exec and progress - * callbacks. Every thread reading the file shares it, so the function must synchronize its own access to it. The opaque - * handle bundles the pointer with an optional destructor, invoked when the read is done with the state. + * The option starts out empty: configure it with the setter functions, then make it available with + * `duckdb_v2_connection_register_custom_option()` or `duckdb_v2_extension_register_custom_option()`. The caller owns + * the returned handle and must destroy it with `duckdb_v2_custom_option_destroy()`, also after registration. + * + * The custom option can only be registered on the instance the factory belongs to. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param data Opaque handle bundling the global state pointer plus an optional destructor. + * @param factory The factory to create the custom option through. + * @param option On success, receives the newly created custom option. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_set_global_state( - duckdb_v2_copy_from_init_global_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_create(duckdb_v2_factory_handle factory, + duckdb_v2_custom_option_handle *option, + duckdb_v2_error_info_handle *err); /*! - * Sets how many threads may read the file in parallel. + * Sets the name of the custom option, as `SET` and the config functions refer to it. * - * Defaults to 1, a single-threaded read. The engine creates at most this many local states, and therefore runs at most - * this many exec callbacks concurrently. It is an upper bound, not a request: the engine may use fewer threads. + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param max_threads The maximum number of threads. Must be at least 1. + * @param option The option to set the name of. + * @param name The name to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_global_set_max_threads( - duckdb_v2_copy_from_init_global_info_handle info, idx_t max_threads, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_name(duckdb_v2_custom_option_handle option, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Sets the type of the custom option. Every value written to the option is cast to it, as SQL `SET` does. + * + * The type is borrowed and copied. Optional when a default value is set, whose type the option then takes. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param option The option to set the type of. + * @param type The type to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_user_data( - duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_type(duckdb_v2_custom_option_handle option, + duckdb_v2_logical_type_handle type, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. + * Sets the default value of the custom option: its value until it is set, and the value `RESET` restores. + * + * The value is borrowed and copied, and cast to the option's type at registration, which fails when the cast does. + * Without a default value the option reads as NULL until it is set. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param option The option to set the default value of. + * @param value The default value. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_bind_data( - duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_default_value(duckdb_v2_custom_option_handle option, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Sets the description of the custom option, as `duckdb_v2_option_get_description()` and `duckdb_settings()` report it. * - * Shared with every other thread reading the file; access to it must be synchronized by the function. + * The description is borrowed and copied. Calling this again replaces the previous description. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the global state pointer, or null if none was set. + * @param option The option to set the description of. + * @param description The description to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_get_global_state( - duckdb_v2_copy_from_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_description(duckdb_v2_custom_option_handle option, + const duckdb_v2_str *description, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's worker-local "local state" from the local init callback. + * Sets the scope a write at `SETTING_SCOPE_DEFAULT` goes to, like SQL `SET name = value`. * - * The local state is associated with the executing thread for the duration of the read and retrievable from the exec - * callback via `duckdb_v2_copy_from_exec_get_local_state()`. No other thread observes it, so it needs no - * synchronization. The opaque handle bundles the pointer with an optional destructor, invoked when the local state is - * no longer needed. + * Must be GLOBAL or SESSION, and a scope the option supports. Defaults to SESSION, or to GLOBAL when the option does + * not support SESSION. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Opaque handle bundling the local state pointer plus an optional destructor. + * @param option The option to set the default scope of. + * @param scope The default scope, GLOBAL or SESSION. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_init_local_set_local_state( - duckdb_v2_copy_from_init_local_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_default_scope(duckdb_v2_custom_option_handle option, + DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Sets whether the custom option can be written at a scope, as `duckdb_v2_option_supports_scope()` reports it. + * + * A custom option supports both GLOBAL and SESSION unless told otherwise. A write at a scope it does not support fails, + * from SQL as from `duckdb_v2_config_set_option_value()`. Registration fails when the option supports neither scope. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param option The option to configure. + * @param scope The scope, GLOBAL or SESSION. + * @param supported Whether the option can be written at the scope. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_user_data(duckdb_v2_copy_from_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_supports_scope(duckdb_v2_custom_option_handle option, + DUCKDB_V2_SETTING_SCOPE scope, bool supported, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. + * Sets arbitrary user data on the custom option. + * + * Associates an opaque pointer with the option, retrievable from the update callback via + * `duckdb_v2_custom_option_update_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, + * invoked when the data is no longer needed: once the custom option handle is destroyed and, if it was registered, the + * instance is too. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param option The option to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_bind_data(duckdb_v2_copy_from_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_user_data(duckdb_v2_custom_option_handle option, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Sets the optional update callback of the custom option. * - * Shared with every other thread reading the file; access to it must be synchronized by the function. + * The callback is invoked whenever the option is set or reset, from SQL as from `duckdb_v2_config_set_option_value()`, + * after the new value is cast to the option's type and before it is stored. It may replace the value with + * `duckdb_v2_custom_option_update_set_value()`, or refuse it by reporting an error. It is not invoked for the default + * value at registration, nor for a value given to the instance at startup. The callback must not write options itself. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the global state pointer, or null if none was set. + * @param option The option to set the update callback of. + * @param callback The update callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_global_state(duckdb_v2_copy_from_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_set_update_callback( + duckdb_v2_custom_option_handle option, duckdb_v2_custom_option_update_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the worker-local local state set by the function's local init callback. + * Retrieves the user data set via `duckdb_v2_custom_option_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the local state pointer for the executing thread, or null if none was set. + * @param info The update info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_local_state(duckdb_v2_copy_from_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_user_data( + duckdb_v2_custom_option_update_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Retrieves the output chunk the exec callback must write the next batch of rows into. - * - * The chunk holds one vector per column reported by `duckdb_v2_copy_from_bind_get_column_count()`, in the same order; - * reach them with `duckdb_v2_data_chunk_get_vector()`. The chunk starts out empty on every invocation: write the rows, - * then declare how many there are with `duckdb_v2_vector_set_size()` on the first vector, which the engine takes as the - * batch's row count and propagates to the other vectors. Producing an empty batch signals the end of the read, after - * which the callback is not invoked again on that thread. Borrowed; valid only for the duration of the callback. + * Retrieves the value the option is updated to, already cast to the option's type. On a reset this is the default + * value, which may be NULL. The returned value is owned by the caller and must be destroyed via + * `duckdb_v2_value_destroy()`. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param chunk Receives the borrowed output chunk to write into. + * @param info The update info handle. + * @param value Receives the new value. Owned by the caller; destroy via `duckdb_v2_value_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_exec_get_output_chunk(duckdb_v2_copy_from_exec_info_handle info, - duckdb_v2_data_chunk_handle *chunk, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_value(duckdb_v2_custom_option_update_info_handle info, + duckdb_v2_value_handle *value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_copy_function_set_user_data()`. + * Replaces the value the option is updated to, e.g. to normalize it. The value is borrowed and copied, and cast to the + * option's type, which fails when the cast does. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param info The update info handle. + * @param value The value to store instead. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_user_data(duckdb_v2_copy_from_progress_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_update_set_value(duckdb_v2_custom_option_update_info_handle info, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's `COPY ... FROM` bind callback. + * Retrieves the scope the value is written at: GLOBAL or SESSION, a DEFAULT write already resolved to the option's + * default scope. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param info The update info handle. + * @param scope Receives the scope. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_bind_data(duckdb_v2_copy_from_progress_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_scope(duckdb_v2_custom_option_update_info_handle info, + DUCKDB_V2_SETTING_SCOPE *scope, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. - * - * The progress callback runs concurrently with the exec callbacks reading the file, so it must read the global state in - * a thread-safe way. + * Retrieves whether the update is a reset to the default value, like SQL `RESET`. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the global state pointer, or null if none was set. + * @param info The update info handle. + * @param is_reset Receives whether the update is a reset. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_get_global_state( - duckdb_v2_copy_from_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_update_is_reset(duckdb_v2_custom_option_update_info_handle info, + bool *is_reset, duckdb_v2_error_info_handle *err); /*! - * Reports how far the read has advanced. + * Registers the custom option on the connection's instance, visible to every connection. * - * A fraction between 0.0 (nothing read yet) and 1.0 (done); values outside that range are clamped. A progress callback - * that returns without calling this reports no progress. + * Registration is not transactional: it takes effect at once, also inside an explicit transaction, and a rollback does + * not undo it. Registration requires a name and a type, or a default value to take the type from, and fails when an + * option of the same name already exists. A value given for the option at startup is picked up. The custom option must + * have been created through a factory of the same instance. The caller still owns the handle after registration and + * must destroy it with `duckdb_v2_custom_option_destroy()`, which does not affect the registered option. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param progress The fraction of the read that is complete, in [0.0, 1.0]. + * @param conn The connection to register on. + * @param option The custom option to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_set_progress(duckdb_v2_copy_from_progress_info_handle info, - double progress, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_custom_option(duckdb_v2_connection_handle conn, + duckdb_v2_custom_option_handle option, + duckdb_v2_error_info_handle *err); /*! - * Registers the copy function, making it available as a `COPY` format. + * Registers the custom option on the loading extension's instance, visible to every connection. * - * The function is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a name and at least one configured side: a `COPY ... TO` side needs its batch and flush - * callbacks, a `COPY ... FROM` side needs its bind and exec callbacks. A statement in a direction the function does not - * implement fails with an error. The caller still owns the handle after registration and must destroy it with - * `duckdb_v2_copy_function_destroy()`, which does not affect the registered function. + * Use this from the extension's load callback. Registration is not transactional, requires a name and a type, or a + * default value to take the type from, and fails when an option of the same name already exists. A value given for the + * option at startup is picked up. The custom option must have been created through a factory of the same instance. The + * caller still owns the handle after registration and must destroy it with `duckdb_v2_custom_option_destroy()`, which + * does not affect the registered option. * * history: * - stable: v2.0.0 * - * @param function The function to register. + * @param extension The loading extension. + * @param option The custom option to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_register(duckdb_v2_copy_function_handle function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_custom_option(duckdb_v2_extension_handle extension, + duckdb_v2_custom_option_handle option, + duckdb_v2_error_info_handle *err); /*! - * Destroys the copy function, releasing its resources. + * Destroys the custom option, releasing its resources. * * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered function. + * double-destruction. Destroying the handle after registration does not affect the registered option. * * history: * - stable: v2.0.0 * - * @param function The function to destroy. + * @param option The option to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_function_destroy(duckdb_v2_copy_function_handle *function); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_custom_option_destroy(duckdb_v2_custom_option_handle *option); -#if DUCKDB_V2_API_ALLOW_UNSTABLE -/*! - * Sets the optional statistics callback of the `COPY ... TO` side. - * - * The statistics callback reports statistics about a written file, such as the number of rows and bytes it holds, which - * `COPY ... TO` returns when the statement asks for them with `RETURN_STATS`. It is invoked once per output file, after - * the finalize callback of that file, and only when the statement asks for the statistics. Without a statistics - * callback, `RETURN_STATS` is rejected for the function. - * - * history: - * - unstable: v2.0.0 - * - * @param function The function to set the callback of. - * @param callback The callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_set_statistics_callback( - duckdb_v2_copy_function_handle function, duckdb_v2_copy_to_statistics_callback_fn callback, - duckdb_v2_error_info_handle *err); -#endif +/* --- Struct definitions for custom_option --- */ -#if DUCKDB_V2_API_ALLOW_UNSTABLE -/*! - * Retrieves the user data set on the copy function via `duckdb_v2_copy_function_set_user_data()`. - * - * history: - * - unstable: v2.0.0 - * - * @param info The statistics info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_user_data(duckdb_v2_copy_to_statistics_info_handle info, - void **data, duckdb_v2_error_info_handle *err); -#endif +/* ============================================================================ + * MODULE: expression + * ============================================================================ */ -#if DUCKDB_V2_API_ALLOW_UNSTABLE -/*! - * Retrieves the bind data set by the function's `COPY ... TO` bind callback. - * - * history: - * - unstable: v2.0.0 - * - * @param info The statistics info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_bind_data(duckdb_v2_copy_to_statistics_info_handle info, - void **data, duckdb_v2_error_info_handle *err); -#endif +/* --- Enums for expression --- */ -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the init data set by the function's `COPY ... TO` init callback for the file being reported. - * - * history: - * - unstable: v2.0.0 + * The type of a bound expression node. The values mirror the engine's own expression types, restricted to the node + * types a filter predicate can contain; every other node type is reported as `EXPRESSION_TYPE_INVALID`. * - * @param info The statistics info handle. - * @param data Receives the init data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR + * Comparisons, `BETWEEN` and casts are regular scalar function calls once bound: they have the function's children and + * name, and only their type tells them apart from any other function. A `CAST` therefore has one child, the value being + * cast, and its target type is the node's return type. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_get_init_data(duckdb_v2_copy_to_statistics_info_handle info, - void **data, duckdb_v2_error_info_handle *err); -#endif - -#if DUCKDB_V2_API_ALLOW_UNSTABLE -/*! - * Reports how many rows were written to the file. Reported as 0 when the callback does not set it. - * - * history: - * - unstable: v2.0.0 - * - * @param info The statistics info handle. - * @param row_count The number of rows written to the file. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_set_row_count(duckdb_v2_copy_to_statistics_info_handle info, - idx_t row_count, - duckdb_v2_error_info_handle *err); -#endif - -#if DUCKDB_V2_API_ALLOW_UNSTABLE -/*! - * Reports the size of the written file in bytes. Reported as 0 when the callback does not set it. - * - * history: - * - unstable: v2.0.0 - * - * @param info The statistics info handle. - * @param file_size_bytes The size of the file in bytes. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_copy_to_statistics_set_file_size(duckdb_v2_copy_to_statistics_info_handle info, - idx_t file_size_bytes, - duckdb_v2_error_info_handle *err); -#endif - -/* --- Struct definitions for copy --- */ - -/* ============================================================================ - * MODULE: expression - * ============================================================================ */ - -/* --- Enums for expression --- */ - -/*! - * The type of a bound expression node. The values mirror the engine's own expression types, restricted to the node - * types a filter predicate can contain; every other node type is reported as `EXPRESSION_TYPE_INVALID`. - * - * Comparisons, `BETWEEN` and casts are regular scalar function calls once bound: they have the function's children and - * name, and only their type tells them apart from any other function. A `CAST` therefore has one child, the value being - * cast, and its target type is the node's return type. - */ -typedef enum DUCKDB_V2_EXPRESSION_TYPE { - //! A node type this API does not model. Its children can still be walked. - DUCKDB_V2_EXPRESSION_TYPE_INVALID = 0, +typedef enum DUCKDB_V2_EXPRESSION_TYPE { + //! A node type this API does not model. Its children can still be walked. + DUCKDB_V2_EXPRESSION_TYPE_INVALID = 0, /*! * A cast. One child; the target type is the node's return type and `duckdb_v2_expression_cast_get_mode()` tells a @@ -7596,6 +7767,301 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_expression_cast_get_mode(duckdb_v2_expres /* --- Struct definitions for expression --- */ +/* ============================================================================ + * MODULE: log sink + * ============================================================================ */ + +/* --- Enums for log sink --- */ + +//! The scope a log entry was written in, which decides which of its ids are present. +typedef enum DUCKDB_V2_LOG_SCOPE { + //! Written for the database as a whole, carrying no connection, transaction or query id. + DUCKDB_V2_LOG_SCOPE_DATABASE = 10, + + //! Written by a connection, carrying its connection id and, inside one, the transaction and query ids. + DUCKDB_V2_LOG_SCOPE_CONNECTION = 20, + + //! Written by a thread executing a query, carrying the thread id next to the connection's ids. + DUCKDB_V2_LOG_SCOPE_THREAD = 30, + DUCKDB_V2_LOG_SCOPE_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_LOG_SCOPE; + +/* --- Struct forward declarations for log sink --- */ + +/* --- Types for log sink --- */ + +/*! + * An owned opaque handle to a log sink being built. Created with `duckdb_v2_log_sink_create()`, configured with + * `duckdb_v2_log_sink_set_name()`, `duckdb_v2_log_sink_set_write_callback()` and the other setters, made available with + * `duckdb_v2_instance_register_log_sink()` or `duckdb_v2_extension_register_log_sink()`, and destroyed with + * `duckdb_v2_log_sink_destroy()`. + */ +typedef struct _duckdb_v2_log_sink { + void *internal_ptr; +} * duckdb_v2_log_sink_handle; + +/*! + * A borrowed opaque handle to the context of a log entry, received by a log sink's write callback next to the entry + * itself, and valid only for the duration of that call. Read the ids `duckdb_logs` reports for the entry through its + * getters, e.g. `duckdb_v2_log_sink_write_info_get_connection_id()`. + */ +typedef struct _duckdb_v2_log_sink_write_info { + void *internal_ptr; +} * duckdb_v2_log_sink_write_info_handle; + +/*! + * A borrowed opaque handle received by a log sink's flush callback, and valid only for the duration of that call. It + * carries nothing yet. + */ +typedef struct _duckdb_v2_log_sink_flush_info { + void *internal_ptr; +} * duckdb_v2_log_sink_flush_info_handle; + +/* --- Constants for log sink --- */ + +/* --- Function pointer typedefs for log sink --- */ + +typedef void (*duckdb_v2_log_sink_write_callback_fn)(duckdb_v2_log_sink_write_info_handle info, void *user_data, + int64_t timestamp, DUCKDB_V2_LOG_LEVEL level, + DUCKDB_V2_LOG_SCOPE scope, const duckdb_v2_str *type, + const duckdb_v2_str *message, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_log_sink_flush_callback_fn)(duckdb_v2_log_sink_flush_info_handle info, void *user_data, + duckdb_v2_error_info_handle *err); + +/* --- Functions for log sink --- */ + +/*! + * Creates a new log sink through a factory. + * + * The log sink starts out empty: configure it with the setter functions, then make it available with + * `duckdb_v2_instance_register_log_sink()` or `duckdb_v2_extension_register_log_sink()`. The caller owns the returned + * handle and must destroy it with `duckdb_v2_log_sink_destroy()`, also after registration. + * + * The log sink can only be registered on the instance the factory belongs to. + * + * history: + * - stable: v2.0.0 + * + * @param factory The factory to create the log sink through. + * @param sink On success, receives the newly created log sink. Owned by the caller. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_create(duckdb_v2_factory_handle factory, + duckdb_v2_log_sink_handle *sink, + duckdb_v2_error_info_handle *err); + +/*! + * Sets the name the logging configuration refers to the log sink by. + * + * Names are matched case-insensitively. The name is borrowed and copied. Calling this again replaces the previous name. + * A name must be set before registration. + * + * history: + * - stable: v2.0.0 + * + * @param sink The log sink to set the name of. + * @param name The name to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_set_name(duckdb_v2_log_sink_handle sink, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); + +/*! + * Sets arbitrary user data on the log sink. + * + * Associates an opaque pointer with the sink, handed to its write and flush callbacks. The opaque handle bundles the + * pointer with an optional destructor, invoked when the data is no longer needed: when the instance is destroyed if the + * sink was registered, else when the log sink handle is. + * + * history: + * - stable: v2.0.0 + * + * @param sink The log sink to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_set_user_data(duckdb_v2_log_sink_handle sink, duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); + +/*! + * Sets the write callback of the log sink, invoked for every entry the logging configuration directs to the sink. + * + * The callback must return quickly, must be safe to run concurrently and on any thread, and must not log or change the + * logging configuration. A write callback must be set before registration. + * + * history: + * - stable: v2.0.0 + * + * @param sink The log sink to set the write callback of. + * @param callback The write callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_set_write_callback(duckdb_v2_log_sink_handle sink, + duckdb_v2_log_sink_write_callback_fn callback, + duckdb_v2_error_info_handle *err); + +/*! + * Sets the optional flush callback of the log sink, invoked when DuckDB flushes its log, so entries the sink holds on + * to can be written out. When DuckDB flushes is up to DuckDB; entries still held when the instance is destroyed are for + * the user data's destructor to write out. + * + * Like the write callback, it must be safe to run on any thread, and must not log or change the logging configuration. + * + * history: + * - stable: v2.0.0 + * + * @param sink The log sink to set the flush callback of. + * @param callback The flush callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_set_flush_callback(duckdb_v2_log_sink_handle sink, + duckdb_v2_log_sink_flush_callback_fn callback, + duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the id of the connection that wrote the entry, as `duckdb_logs` reports it. An entry written for the + * database as a whole has none: `present` then receives false and `id` is left untouched. + * + * history: + * - stable: v2.0.0 + * + * @param info The write info handle of the entry. + * @param id Receives the connection id, if present. + * @param present Receives whether the entry has a connection id. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_connection_id(duckdb_v2_log_sink_write_info_handle info, + idx_t *id, bool *present, + duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the id of the transaction the entry was written in, as `duckdb_logs` reports it. An entry written outside a + * transaction has none: `present` then receives false and `id` is left untouched. + * + * history: + * - stable: v2.0.0 + * + * @param info The write info handle of the entry. + * @param id Receives the transaction id, if present. + * @param present Receives whether the entry has a transaction id. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_transaction_id(duckdb_v2_log_sink_write_info_handle info, + idx_t *id, bool *present, + duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the id of the query the entry was written for, as `duckdb_logs` reports it. An entry written outside a + * query has none: `present` then receives false and `id` is left untouched. + * + * history: + * - stable: v2.0.0 + * + * @param info The write info handle of the entry. + * @param id Receives the query id, if present. + * @param present Receives whether the entry has a query id. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_query_id(duckdb_v2_log_sink_write_info_handle info, + idx_t *id, bool *present, + duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the id of the thread that wrote the entry, as `duckdb_logs` reports it. Only an entry written in the thread + * scope has one: otherwise `present` receives false and `id` is left untouched. The id identifies the thread that + * logged, which need not be the thread the write callback runs on. + * + * history: + * - stable: v2.0.0 + * + * @param info The write info handle of the entry. + * @param id Receives the thread id, if present. + * @param present Receives whether the entry has a thread id. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_thread_id(duckdb_v2_log_sink_write_info_handle info, + idx_t *id, bool *present, + duckdb_v2_error_info_handle *err); + +/*! + * Registers the log sink on the instance, making it available to the logging configuration under its name. + * + * Registering does not direct any entries to the sink by itself; the logging configuration does. Registration is not + * transactional, requires a name and a write callback, and fails when the name is taken: by a sink DuckDB provides + * itself, such as `memory`, `stdout` or `file`, or by a sink registered before. The log sink must have been created + * through a factory of the same instance. Its user data passes to the registered sink. The caller still owns the handle + * after registration and must destroy it with `duckdb_v2_log_sink_destroy()`, which does not affect the registered + * sink. + * + * history: + * - stable: v2.0.0 + * + * @param instance The instance to register on. + * @param sink The log sink to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_register_log_sink(duckdb_v2_instance_handle instance, + duckdb_v2_log_sink_handle sink, + duckdb_v2_error_info_handle *err); + +/*! + * Registers the log sink on the loading extension's instance, making it available to the logging configuration under + * its name. + * + * Use this from the extension's load callback. Registration follows the rules of + * `duckdb_v2_instance_register_log_sink()`. + * + * history: + * - stable: v2.0.0 + * + * @param extension The loading extension. + * @param sink The log sink to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_log_sink(duckdb_v2_extension_handle extension, + duckdb_v2_log_sink_handle sink, + duckdb_v2_error_info_handle *err); + +/*! + * Destroys the log sink, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered sink. + * + * history: + * - stable: v2.0.0 + * + * @param sink The log sink to destroy. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_log_sink_destroy(duckdb_v2_log_sink_handle *sink); + +/* --- Struct definitions for log sink --- */ + /* ============================================================================ * MODULE: logical_type * ============================================================================ */ @@ -7620,10 +8086,7 @@ typedef enum DUCKDB_V2_LOGICAL_TYPE_ID { //! ANY — used for functions that accept any type. DUCKDB_V2_LOGICAL_TYPE_ID_ANY = 3, - /*! - * A type carried as a value (type parameters). Values of this type are built via value_create_type_with_context / - * _with_connection. - */ + //! A type carried as a value (type parameters). Values of this type are built via value_create_type. DUCKDB_V2_LOGICAL_TYPE_ID_TYPE = 6, DUCKDB_V2_LOGICAL_TYPE_ID_BOOLEAN = 10, DUCKDB_V2_LOGICAL_TYPE_ID_TINYINT = 11, @@ -7708,7 +8171,7 @@ typedef enum DUCKDB_V2_LOGICAL_TYPE_ID { /*! * Creates a logical type from a type id plus value parameters. * - * The id-keyed twin of context_create_type_from_name: the type id names the kind, and the parameters bind it. With + * The id-keyed twin of factory_create_type_from_name: the type id names the kind, and the parameters bind it. With * param_count 0 it instantiates a primitive directly, without touching the catalog: BOOLEAN, TINYINT..BIGINT, * UTINYINT..UBIGINT, HUGEINT, UHUGEINT, FLOAT, DOUBLE, DATE, every TIME and TIMESTAMP variant, INTERVAL, VARCHAR, BLOB, * BIT, BIGNUM, and UUID. ANY is accepted as well. @@ -7718,19 +8181,19 @@ typedef enum DUCKDB_V2_LOGICAL_TYPE_ID { * chunk creation, scalar and aggregate return types, table function result columns, cast source and target types, and * custom type registration. * - * With parameters, the id resolves to its canonical type name and binds through the same path as - * context_create_type_from_name, so the parameterized kinds construct here too: decimal(width, scale); list(T); + * With parameters, the id resolves to its canonical built-in type and binds through the same constructors as + * factory_create_type_from_name, so the parameterized kinds construct here too: decimal(width, scale); list(T); * array(T, size); map(K, V); struct(fields); union(members); enum(entries); and varchar with a named "collation" - * parameter. Parameters are (name, value) pairs in two parallel arrays, exactly as for context_create_type_from_name. + * parameter. Parameters are (name, value) pairs in two parallel arrays, exactly as for factory_create_type_from_name. * * Returns ERROR_INPUT_INVALID when param_count is 0 and the id needs parameters (DECIMAL, LIST, STRUCT, TUPLE, MAP, * ARRAY, UNION, ENUM, VARIANT, GEOMETRY), for the bind-time-only ids (SQLNULL, UNKNOWN), for TYPE — construct that via - * context_create_type_from_text — and for INVALID. + * factory_create_type_from_text — and for INVALID. * * history: * - stable: v2.0.0 * - * @param ctx The context supplying the catalog and active transaction. + * @param factory The factory to resolve the type in. * @param type_id The type id to instantiate. * @param param_names Optional. An array of param_count parameter names; a {NULL, 0} entry is positional. Pass NULL for * all-positional parameters. @@ -7740,15 +8203,15 @@ typedef enum DUCKDB_V2_LOGICAL_TYPE_ID { * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_id( - duckdb_v2_context_handle ctx, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_id( + duckdb_v2_factory_handle factory, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); /*! * Creates a logical type from a type name plus value parameters. * - * The generic constructor: resolves the name in the context's catalog and binds it with the given parameters, exactly + * The generic constructor: resolves the name in the factory's catalog and binds it with the given parameters, exactly * as SQL binds a type expression. Built-in parameterized kinds and registered extension types construct through this * same call. * @@ -7758,16 +8221,16 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_id( * * Parameters are (name, value) pairs in two parallel arrays. param_names may be NULL to make every parameter * positional, and a {NULL, 0} entry makes that one parameter positional. Child types cross as TYPE values, built with - * value_create_type_with_context / _with_connection. The built-in shapes are: decimal(width, scale); list(T); array(T, - * size); map(K, V); struct(fields, as named or all-positional TYPE values); union(members, as named TYPE values); - * enum(entries, as VARCHAR values); and varchar with a named "collation" VARCHAR parameter. + * value_create_type. The built-in shapes are: decimal(width, scale); list(T); array(T, size); map(K, V); struct(fields, + * as named or all-positional TYPE values); union(members, as named TYPE values); enum(entries, as VARCHAR values); and + * varchar with a named "collation" VARCHAR parameter. * * A name that resolves to a type with no bind function takes no parameters, and passing any fails. Bind errors — * unknown name, wrong parameter count or types — surface from the call. * - * Runs in the caller's context scope, as create_type_from_text does: reach it from a bind-phase callback or another - * context-holding scope, not from an exec-phase worker callback. External callers holding only a connection use - * connection_create_type_from_name instead. + * Resolves in the scope of the factory: a connection's or callback's factory reaches the catalog, an instance's factory + * knows only the built-in types. A connection's factory looks up catalog types in the connection's transaction, or in + * one of its own when none is open. Through an instance's factory, only an unqualified built-in name resolves. * * The returned logical type is caller-owned and must be destroyed via logical_type_destroy. A type resolved from the * catalog shares database-owned storage, such as an ENUM dictionary, so destroy it before closing the database. This is @@ -7776,7 +8239,7 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_id( * history: * - stable: v2.0.0 * - * @param ctx The context supplying the catalog and active transaction. + * @param factory The factory to resolve the type in. * @param name The type name to resolve. Parts are matched case-insensitively; qualify it to name a type in a particular * catalog or schema. * @param param_names Optional. An array of param_count parameter names; a {NULL, 0} entry is positional. Pass NULL for @@ -7787,24 +8250,23 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_id( * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_name( - duckdb_v2_context_handle ctx, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_name( + duckdb_v2_factory_handle factory, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); /*! * Creates a logical type by parsing SQL text. * - * Parses a SQL type expression in the given context and returns the bound logical type. It accepts primitives - * ("INTEGER"), parameterized kinds ("DECIMAL(18,3)", "INTEGER[]", "STRUCT(a INTEGER, b VARCHAR)", "MAP(VARCHAR, - * INTEGER)", "INTEGER[3]", "UNION(i INTEGER, s VARCHAR)", "ENUM('a', 'b')"), and catalog-registered type names, both - * user-defined and from extensions. A catalog type name binds to its structural type, and the name is not preserved as - * an alias. Names are case-insensitive. Parse and bind errors surface from the call. + * Parses a SQL type expression and returns the bound logical type. It accepts primitives ("INTEGER"), parameterized + * kinds ("DECIMAL(18,3)", "INTEGER[]", "STRUCT(a INTEGER, b VARCHAR)", "MAP(VARCHAR, INTEGER)", "INTEGER[3]", "UNION(i + * INTEGER, s VARCHAR)", "ENUM('a', 'b')"), and catalog-registered type names, both user-defined and from extensions. A + * catalog type name binds to its structural type, and the name is not preserved as an alias. Names are + * case-insensitive. Parse and bind errors surface from the call. * - * Runs in the caller's context scope: a context handle arrives with the context lock held and a transaction active, as - * in a function bind callback or custom type registration. Catalog-touching context calls belong in bind-phase - * callbacks and other context-holding scopes, not in exec-phase worker callbacks. External callers holding only a - * connection use connection_create_type_from_text instead. + * Resolves in the scope of the factory: a connection's or callback's factory reaches the catalog, an instance's factory + * knows only the built-in types. A connection's factory looks up catalog types in the connection's transaction, or in + * one of its own when none is open. Text naming only built-in types never touches the catalog. * * The returned logical type is caller-owned and must be destroyed via logical_type_destroy. A type resolved from the * catalog shares database-owned storage, such as an ENUM dictionary, so destroy it before closing the database. This is @@ -7813,134 +8275,38 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_name( * history: * - stable: v2.0.0 * - * @param ctx The context supplying the catalog and active transaction. + * @param factory The factory to resolve the type in. * @param text View of the SQL type expression to parse. * @param out_type Receives the new logical type handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_text(duckdb_v2_context_handle ctx, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_text(duckdb_v2_factory_handle factory, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); /*! - * Creates a logical type from a type id plus value parameters. - * - * The id-keyed twin of connection_create_type_from_name: the type id names the kind, and the parameters bind it. With - * param_count 0 it instantiates a primitive directly, without touching the catalog: BOOLEAN, TINYINT..BIGINT, - * UTINYINT..UBIGINT, HUGEINT, UHUGEINT, FLOAT, DOUBLE, DATE, every TIME and TIMESTAMP variant, INTERVAL, VARCHAR, BLOB, - * BIT, BIGNUM, and UUID. ANY is accepted as well. - * - * ANY is a function-signature wildcard, constructible here so it can be passed to the function parameter and varargs - * setters, as a fixed-arity ANY parameter or an ANY varargs type. Data-creating surfaces reject it: value and data - * chunk creation, scalar and aggregate return types, table function result columns, cast source and target types, and - * custom type registration. - * - * With parameters, the id resolves to its canonical type name and binds through the same path as - * connection_create_type_from_name, so the parameterized kinds construct here too: decimal(width, scale); list(T); - * array(T, size); map(K, V); struct(fields); union(members); enum(entries); and varchar with a named "collation" - * parameter. Parameters are (name, value) pairs in two parallel arrays, exactly as for - * connection_create_type_from_name. + * Creates a copy of a logical type. * - * Returns ERROR_INPUT_INVALID when param_count is 0 and the id needs parameters (DECIMAL, LIST, STRUCT, TUPLE, MAP, - * ARRAY, UNION, ENUM, VARIANT, GEOMETRY), for the bind-time-only ids (SQLNULL, UNKNOWN), for TYPE — construct that via - * connection_create_type_from_text — and for INVALID. + * On success, writes the new caller-owned handle into *out_type; destroy it via logical_type_destroy. * * history: * - stable: v2.0.0 * - * @param conn The connection supplying the catalog and active transaction. - * @param type_id The type id to instantiate. - * @param param_names Optional. An array of param_count parameter names; a {NULL, 0} entry is positional. Pass NULL for - * all-positional parameters. - * @param param_values An array of param_count parameter values. Borrowed (copied in). Pass NULL when param_count is 0. - * @param param_count The number of parameters. 0 instantiates a parameterless primitive. + * @param type The logical type to copy. * @param out_type Receives the new logical type handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_id( - duckdb_v2_connection_handle conn, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_logical_type_copy(duckdb_v2_logical_type_handle type, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); /*! - * Creates a logical type from a type name plus value parameters, using a connection. - * - * The same as context_create_type_from_name, except that the catalog and transaction come from a connection: the bind - * runs in its own transaction on that connection's context. Use it from outside DuckDB, where a connection — but no - * context — is in hand. + * Destroys a logical type handle. * - * Parameters are (name, value) pairs in two parallel arrays, exactly as for context_create_type_from_name. - * - * The returned logical type is caller-owned and must be destroyed via logical_type_destroy. A type resolved from the - * catalog shares database-owned storage, such as an ENUM dictionary, so destroy it before closing the database. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection supplying the catalog and active transaction. - * @param name The type name to resolve. Parts are matched case-insensitively; qualify it to name a type in a particular - * catalog or schema. - * @param param_names Optional. An array of param_count parameter names; a {NULL, 0} entry is positional. Pass NULL for - * all-positional parameters. - * @param param_values An array of param_count parameter values. Borrowed (copied in). Pass NULL when param_count is 0. - * @param param_count The number of parameters. - * @param out_type Receives the new logical type handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_name( - duckdb_v2_connection_handle conn, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a logical type by parsing SQL text, using a connection. - * - * The same as context_create_type_from_text, except that the catalog and transaction come from a connection: the parse - * and bind run in their own transaction on that connection's context. Use it from outside DuckDB, where a connection — - * but no context — is in hand. - * - * The returned logical type is caller-owned and must be destroyed via logical_type_destroy. A type resolved from the - * catalog shares database-owned storage, such as an ENUM dictionary, so destroy it before closing the database. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection supplying the catalog and active transaction. - * @param text View of the SQL type expression to parse. - * @param out_type Receives the new logical type handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_text(duckdb_v2_connection_handle conn, - const duckdb_v2_str *text, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a copy of a logical type. - * - * On success, writes the new caller-owned handle into *out_type; destroy it via logical_type_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param type The logical type to copy. - * @param out_type Receives the new logical type handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_logical_type_copy(duckdb_v2_logical_type_handle type, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); - -/*! - * Destroys a logical type handle. - * - * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. On success the slot is set to nullptr. + * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. On success the slot is set to nullptr. * * history: * - stable: v2.0.0 @@ -8009,9 +8375,9 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_logical_type_get_name(duckdb_v2_logical_t * Renders a logical type as SQL text. * * An aliased type renders as its alias, and create_type_from_text resolves that spelling only when the name is - * registered in the connection's catalog. The text round-trips through create_type_from_text for every constructible - * kind, with one exception: ANY renders as "ANY", but create_type_from_text cannot parse it back, since ANY is a - * signature wildcard rather than a parseable SQL type. + * registered in the factory's catalog. The text round-trips through create_type_from_text for every constructible kind, + * with one exception: ANY renders as "ANY", but create_type_from_text cannot parse it back, since ANY is a signature + * wildcard rather than a parseable SQL type. * * Writes into a caller-supplied buffer, so nothing is allocated on the caller's behalf and nothing has to be freed. * Pass out_text = NULL to size the buffer without rendering into it: out_length then receives the length, and @@ -8089,13 +8455,13 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_logical_type_get_param(duckdb_v2_logical_ * remaining logically distinct from the base type. Intended for custom type bind callbacks, where both the base type * and the name come from the bind info. * - * Scoped like the rest of the create_type family: the alias is resolved against the catalog reachable from the context. - * An empty alias name returns ERROR_INPUT_INVALID. + * The alias is not registered anywhere, so this works through any factory. An empty alias name returns + * ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param ctx The context to resolve the alias against. + * @param factory The factory to resolve the type in. * @param base_type The logical type to alias. Typically the base type supplied in the custom type bind info. * @param alias_name The name for the resulting type. Typically the name of the custom type being constructed, also * available from the bind info. @@ -8103,5777 +8469,6099 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_logical_type_get_param(duckdb_v2_logical_ * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_context_create_type_with_alias(duckdb_v2_context_handle ctx, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_factory_create_type_with_alias(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle base_type, const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err); +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Creates a logical type that is an alias of another logical type. + * Creates the common type of a set of logical types. * - * The alias keeps the base type's internal representation, so executing against it needs no special handling, while - * remaining logically distinct from the base type. Intended for custom type bind callbacks, where both the base type - * and the name come from the bind info. + * The common type is the type every input implicitly casts to, resolved by the same rule a SQL list literal and + * value_create_list follow: INTEGER and BIGINT resolve to BIGINT, and DECIMAL(4,1) and DECIMAL(5,4) to DECIMAL(7,4). + * The types are combined pairwise from left to right, and the result is not guaranteed to be independent of their + * order. + * + * A connection's or a callback's factory also applies the casts that extensions registered, while an instance's factory + * knows only the built-in ones, so the two can resolve the same set differently. A single type resolves to a copy of + * itself. * - * Scoped like the rest of the create_type family: the alias is resolved against the catalog reachable from the - * connection. An empty alias name returns ERROR_INPUT_INVALID. + * Returns ERROR_INPUT_INVALID for an empty set, for a NULL entry and for ANY, and ERROR_QUERY_NOT_IMPLEMENTED when the + * types have no common type. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param conn The connection to resolve the alias against. - * @param base_type The logical type to alias. Typically the base type supplied in the custom type bind info. - * @param alias_name The name for the resulting type. Typically the name of the custom type being constructed, also - * available from the bind info. - * @param out_type Receives the new type: the base type's internal representation under the given alias name. + * @param factory The factory to resolve the type in. + * @param types An array of type_count logical types. Borrowed. + * @param type_count The number of types. Must be at least 1. + * @param out_type Receives the new logical type handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_create_type_with_alias(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle base_type, - const duckdb_v2_identifier_t *alias_name, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_factory_create_common_type(duckdb_v2_factory_handle factory, + const duckdb_v2_logical_type_handle *types, + idx_t type_count, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); +#endif /* --- Struct definitions for logical_type --- */ /* ============================================================================ - * MODULE: replacement scan + * MODULE: macro * ============================================================================ */ -/* --- Enums for replacement scan --- */ +/* --- Enums for macro --- */ -/* --- Struct forward declarations for replacement scan --- */ +//! The kind of a macro function, deciding how its body is read. Pass one to `duckdb_v2_macro_function_create()`. +typedef enum DUCKDB_V2_MACRO_FUNCTION_TYPE { + //! A scalar macro. The body is a single expression, and the macro is called like a scalar function. + DUCKDB_V2_MACRO_FUNCTION_TYPE_SCALAR = 0, -/* --- Types for replacement scan --- */ + //! A table macro. The body is a single SELECT query, and the macro is called like a table function. + DUCKDB_V2_MACRO_FUNCTION_TYPE_TABLE = 1, + DUCKDB_V2_MACRO_FUNCTION_TYPE_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_MACRO_FUNCTION_TYPE; -/*! - * An owned opaque handle to a replacement scan being built. Created with - * `duckdb_v2_replacement_scan_create_with_connection()`, `duckdb_v2_replacement_scan_create_with_instance()` or - * `duckdb_v2_replacement_scan_create_with_extension()`, configured with `duckdb_v2_replacement_scan_set_callback()` and - * `duckdb_v2_replacement_scan_set_user_data()`, made available with `duckdb_v2_replacement_scan_register()`, and - * destroyed with `duckdb_v2_replacement_scan_destroy()`. - */ -typedef struct _duckdb_v2_replacement_scan { - void *internal_ptr; -} * duckdb_v2_replacement_scan_handle; +/* --- Struct forward declarations for macro --- */ + +/* --- Types for macro --- */ /*! - * A borrowed opaque handle to the arguments supplied to a replacement scan when the binder consults it. The callback - * receives this handle and can use it to inspect the unresolved name and to claim it by naming what to read instead. - * Valid only for the duration of the callback; the name it hands out is owned separately and outlives it. + * An owned opaque handle to a macro function being built. Created with `duckdb_v2_macro_function_create()`, configured + * with `duckdb_v2_macro_function_set_name()`, `duckdb_v2_macro_function_set_body()` and the signature obtained via + * `duckdb_v2_macro_function_get_signature()`, made available with `duckdb_v2_extension_register_macro_function()`, and + * destroyed with `duckdb_v2_macro_function_destroy()`. */ -typedef struct _duckdb_v2_replacement_scan_info { +typedef struct _duckdb_v2_macro_function { void *internal_ptr; -} * duckdb_v2_replacement_scan_info_handle; - -/* --- Constants for replacement scan --- */ +} * duckdb_v2_macro_function_handle; -/* --- Function pointer typedefs for replacement scan --- */ +/* --- Constants for macro --- */ -typedef void (*duckdb_v2_replacement_scan_callback_fn)(duckdb_v2_replacement_scan_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); +/* --- Function pointer typedefs for macro --- */ -/* --- Functions for replacement scan --- */ +/* --- Functions for macro --- */ /*! - * Creates a new replacement scan that will be registered on the connection. + * Creates a new macro function through a factory. * - * The scan is visible only to queries on this connection and is released when the connection is destroyed. It is - * consulted before every instance-wide scan, so it can claim a name that a built-in scan would otherwise take. The scan - * starts out empty: configure it with `duckdb_v2_replacement_scan_set_callback()` and optionally - * `duckdb_v2_replacement_scan_set_user_data()`, then make it available with `duckdb_v2_replacement_scan_register()`. - * The caller owns the returned handle and must destroy it with `duckdb_v2_replacement_scan_destroy()`, also after - * registration. + * The macro function starts out empty: configure it with the setter functions and the signature obtained via + * `duckdb_v2_macro_function_get_signature()`, then make it available with + * `duckdb_v2_extension_register_macro_function()`. The caller owns the returned handle and must destroy it with + * `duckdb_v2_macro_function_destroy()`, also after registration. + * + * The macro function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param connection The connection to create the scan on. - * @param scan On success, receives the newly created replacement scan. Owned by the caller. + * @param factory The factory to create the macro function through. + * @param type Whether the macro is a scalar or a table macro. + * @param function On success, receives the newly created macro function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_replacement_scan_handle *scan, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_create(duckdb_v2_factory_handle factory, + DUCKDB_V2_MACRO_FUNCTION_TYPE type, + duckdb_v2_macro_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Creates a new replacement scan that will be registered on the instance. + * Sets the name of the macro function, as SQL calls it. * - * The scan is visible to every connection to the instance and lives until the instance is destroyed. The scan starts - * out empty: configure it with `duckdb_v2_replacement_scan_set_callback()` and optionally - * `duckdb_v2_replacement_scan_set_user_data()`, then make it available with `duckdb_v2_replacement_scan_register()`. - * The caller owns the returned handle and must destroy it with `duckdb_v2_replacement_scan_destroy()`, also after + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before * registration. * * history: * - stable: v2.0.0 * - * @param instance The instance to create the scan on. - * @param scan On success, receives the newly created replacement scan. Owned by the caller. + * @param function The macro function to set the name of. + * @param name The name to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_instance(duckdb_v2_instance_handle instance, - duckdb_v2_replacement_scan_handle *scan, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_set_name(duckdb_v2_macro_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Creates a new replacement scan that will be registered on the loading extension's instance. + * Sets the body of the macro function, as SQL text. * - * Use this from an extension load callback, where an extension handle is available. The scan is visible to every - * connection to that instance and lives until the instance is destroyed. The scan starts out empty: configure it with - * `duckdb_v2_replacement_scan_set_callback()` and optionally `duckdb_v2_replacement_scan_set_user_data()`, then make it - * available with `duckdb_v2_replacement_scan_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_replacement_scan_destroy()`, also after registration. + * For a scalar macro the body is a single expression, for a table macro a single SELECT query. It refers to the + * parameters by name. The body is parsed at registration, which fails on a syntax error; names in it are resolved only + * when the macro is called, as with SQL `CREATE MACRO`. The body is borrowed and copied. Calling this again replaces + * the previous body. A body must be set before registration. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the scan on. - * @param scan On success, receives the newly created replacement scan. Owned by the caller. + * @param function The macro function to set the body of. + * @param body The SQL text of the body. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_replacement_scan_handle *scan, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_set_body(duckdb_v2_macro_function_handle function, + const duckdb_v2_str *body, + duckdb_v2_error_info_handle *err); /*! - * Sets the callback of the replacement scan. + * Returns the macro function's signature so its parameters can be declared. * - * The callback is invoked during query planning for each table reference the catalog could not resolve. It can inspect - * the unresolved name via `duckdb_v2_replacement_scan_get_name()`, and claim the reference via - * `duckdb_v2_replacement_scan_set_function_name()`, `duckdb_v2_replacement_scan_set_collection()` or - * `duckdb_v2_replacement_scan_set_subquery()`; returning without claiming declines it. The context passed to the - * callback may be used to read settings, but not to run queries. A callback must be set before registration. + * Add parameters with `duckdb_v2_function_signature_add_parameter()`. A macro's parameters can always be passed by + * position or by name, so only `FUNCTION_PARAMETER_KIND_STANDARD` parameters are accepted; a macro has no variadic + * parameters. A parameter of type ANY is untyped, and a parameter with a default value can be omitted by a caller. A + * macro's result is decided by its body, so the signature cannot set a return type. Registration fails on a signature + * that does not follow these rules. The signature is modified in place. * * history: * - stable: v2.0.0 * - * @param scan The scan to set the callback of. - * @param callback The callback to set. + * @param function The macro function to get the signature of. + * @param sig The returned signature. Borrowed and valid for the lifetime of the macro function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_callback(duckdb_v2_replacement_scan_handle scan, - duckdb_v2_replacement_scan_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_get_signature(duckdb_v2_macro_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); /*! - * Sets arbitrary user data on the replacement scan. + * Returns the documentation of the macro function so it can be filled in. * - * Associates an opaque pointer with the scan, retrievable from the callback via - * `duckdb_v2_replacement_scan_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, - * invoked when the data is no longer needed, at the latest when the scan's scope ends. The callback may be invoked from - * several connections at once, so the data must be safe to read concurrently. + * Set a description with `duckdb_v2_function_docs_set_description()`, and add examples and categories with + * `duckdb_v2_function_docs_add_example()` and `duckdb_v2_function_docs_add_category()`. The documentation is modified + * in place and registered with the macro function. Optional. * * history: * - stable: v2.0.0 * - * @param scan The scan to set the user data of. - * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param function The macro function to get the documentation of. + * @param docs The returned documentation. Borrowed and valid for the lifetime of the macro function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_user_data(duckdb_v2_replacement_scan_handle scan, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_get_docs(duckdb_v2_macro_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_replacement_scan_set_user_data()`. + * Registers the macro function, making it available for use in SQL queries. + * + * The macro function is registered on the loading extension's database and attributed to the extension. Use this from + * the extension's load callback. Registration requires a name and a body that parses, and a signature that follows the + * rules of `duckdb_v2_macro_function_get_signature()`. It fails when a function of the same name already exists: a + * macro function has a single overload. The macro function must have been created through a factory of the same + * database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_macro_function_destroy()`, which does not affect the registered macro function. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param extension The loading extension. + * @param function The macro function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_get_user_data(duckdb_v2_replacement_scan_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_macro_function(duckdb_v2_extension_handle extension, + duckdb_v2_macro_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the name the catalog could not resolve. + * Destroys the macro function, releasing its resources. * - * The name as written in the query, as a path: an unqualified reference has a single part, and a qualified one carries - * its catalog and schema before it -- see `duckdb_v2_qname_get_part()`. For a file-backed reference the single part is - * the path with the quotes stripped. The returned name is owned by the caller and must be destroyed via - * `duckdb_v2_qname_destroy()`; being owned, it may outlive the callback. + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered macro function. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param name Receives the unresolved name. Owned by the caller; destroy via `duckdb_v2_qname_destroy()`. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param function The macro function to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_get_name(duckdb_v2_replacement_scan_info_handle info, - duckdb_v2_qname_handle *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_macro_function_destroy(duckdb_v2_macro_function_handle *function); + +/* --- Struct definitions for macro --- */ + +/* ============================================================================ + * MODULE: pragma + * ============================================================================ */ + +/* --- Enums for pragma --- */ + +/* --- Struct forward declarations for pragma --- */ + +/* --- Types for pragma --- */ /*! - * Claims the reference by naming a table function to read instead. + * An owned opaque handle to a custom pragma function being built. Created with `duckdb_v2_pragma_function_create()`, + * configured with the setter functions (e.g. `duckdb_v2_pragma_function_set_name()`, + * `duckdb_v2_pragma_function_set_query_callback()`, etc.) and the signature obtained via + * `duckdb_v2_pragma_function_get_signature()`, made available with `duckdb_v2_connection_register_pragma_function()` or + * `duckdb_v2_extension_register_pragma_function()`, and destroyed with `duckdb_v2_pragma_function_destroy()`. + */ +typedef struct _duckdb_v2_pragma_function { + void *internal_ptr; +} * duckdb_v2_pragma_function_handle; + +/*! + * A borrowed opaque handle to the result of a pragma function's "query" callback. The query callback receives this + * handle next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments and the user data, and + * uses it to set the query to run in place of the `PRAGMA` statement. + */ +typedef struct _duckdb_v2_pragma_function_query_info { + void *internal_ptr; +} * duckdb_v2_pragma_function_query_info_handle; + +/* --- Constants for pragma --- */ + +/* --- Function pointer typedefs for pragma --- */ + +typedef void (*duckdb_v2_pragma_function_query_callback_fn)(duckdb_v2_function_bind_info_handle info, + duckdb_v2_pragma_function_query_info_handle result, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_pragma_function_exec_callback_fn)(duckdb_v2_function_bind_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +/* --- Functions for pragma --- */ + +/*! + * Creates a new pragma function through a factory. * - * Arguments to the function are added with `duckdb_v2_replacement_scan_add_argument()` and - * `duckdb_v2_replacement_scan_add_named_argument()`. The name is borrowed and copied, and its parts are matched - * case-insensitively. A qualified name targets a function in a particular schema or catalog, exactly as writing it out - * in SQL would. The name is not resolved here: an unknown function fails later, when the replacement is bound. Calling - * this again replaces the previous name and keeps the arguments added so far. + * The function starts out empty: configure it with the setter functions (e.g. `duckdb_v2_pragma_function_set_name()`, + * `duckdb_v2_pragma_function_set_query_callback()`, etc.) and the signature obtained via + * `duckdb_v2_pragma_function_get_signature()`, then make it available with + * `duckdb_v2_connection_register_pragma_function()` or `duckdb_v2_extension_register_pragma_function()`. The caller + * owns the returned handle and must destroy it with `duckdb_v2_pragma_function_destroy()`, also after registration. * - * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, - * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually - * exclusive: claiming the reference through a second, different form results in an error. + * The pragma function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param name The table function to read instead. Borrowed and copied. + * @param factory The factory to create the pragma function through. + * @param function On success, receives the newly created pragma function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_function_name(duckdb_v2_replacement_scan_info_handle info, - duckdb_v2_qname_handle name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_pragma_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Appends a positional argument to the claimed table function. + * Sets the name of the pragma function, as the `PRAGMA` statement calls it. * - * Positional arguments are passed in the order they are added, before any named ones. The value is borrowed and copied, - * so the caller may destroy it after the call. Requires `duckdb_v2_replacement_scan_set_function_name()` to have been - * called first; otherwise the call results in an error. + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param value The argument value. Borrowed and copied. + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_add_argument(duckdb_v2_replacement_scan_info_handle info, - duckdb_v2_value_handle value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_name(duckdb_v2_pragma_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Appends a named argument to the claimed table function. + * Returns the documentation of the function so it can be filled in. * - * Equivalent to writing `name := value` at the call site. The name and the value are borrowed and copied, so the caller - * may destroy the value after the call. Requires `duckdb_v2_replacement_scan_set_function_name()` to have been called - * first; otherwise the call results in an error. + * Set a description with `duckdb_v2_function_docs_set_description()`, and add examples and categories with + * `duckdb_v2_function_docs_add_example()` and `duckdb_v2_function_docs_add_category()`. The documentation is modified + * in place and registered with the function. Optional. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param name The name of the parameter to bind the value to. Borrowed and copied. - * @param value The argument value. Borrowed and copied. + * @param function The function to get the documentation of. + * @param docs The returned documentation. Borrowed and valid for the lifetime of the function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_add_named_argument(duckdb_v2_replacement_scan_info_handle info, - const duckdb_v2_identifier_t *name, - duckdb_v2_value_handle value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_get_docs(duckdb_v2_pragma_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); /*! - * Claims the reference by naming a column data collection to read instead. - * - * The collection is borrowed, not copied: the caller keeps ownership and must keep it alive, and must not clear, reset - * or destroy it, for as long as any result reading it is live, since the result scans its buffers directly. - * - * A prepared statement extends that lifetime well beyond its own results. Preparing a statement over a claimed name - * captures the borrow in the plan, and since a collection claim reads no database there is nothing to invalidate that - * plan: `duckdb_v2_prepared_statement_reuses_plan()` reports true, and every later execution reuses the captured borrow - * without consulting the callback again. So dropping the name from whatever the callback resolves against does not - * release the collection, and releasing it while such a statement is live leaves the next execution reading freed - * memory. Destroy every prepared statement over a claimed name before clearing, resetting or destroying the collection - * behind it. - * - * By default the collection's columns are named col1..colN. Pass `column_names` to name them instead. When supplied, - * `column_count` must equal the collection's column count and no name may be empty; pass NULL and 0 for the default - * names. + * Returns the function's signature so it can be configured. * - * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, - * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually - * exclusive: claiming the reference through a second, different form results in an error. + * Add parameters with `duckdb_v2_function_signature_add_parameter()`. A pragma produces no value, so its signature + * cannot set a return type. A signature without parameters makes a pragma called as `PRAGMA name`. The signature is + * modified in place. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param collection The collection to read instead. Borrowed; the caller keeps ownership and must keep it alive. - * @param column_names Optional. An array of `column_count` column names, in order. Pass NULL for the default names - * col1..colN. - * @param column_count The number of names in `column_names`. Must equal the collection's column count, or 0 for the - * default names. + * @param function The function to get the signature of. + * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_collection( - duckdb_v2_replacement_scan_info_handle info, duckdb_v2_column_data_collection_handle collection, - const duckdb_v2_identifier_t *column_names, idx_t column_count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_get_signature(duckdb_v2_pragma_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); /*! - * Claims the reference by naming a query to read instead. + * Sets arbitrary user data on the pragma function. * - * The text is parsed at the call and must contain exactly one SELECT statement: a syntax error, several statements or a - * statement of another kind results in an error. The text is borrowed for the call only. Prefer - * `duckdb_v2_replacement_scan_set_function_name()` when a single table function call suffices, as it avoids the parse - * and keeps the plan flatter. - * - * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, - * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually - * exclusive: claiming the reference through a second, different form results in an error. + * Associates an opaque pointer with the function, retrievable from the callbacks via + * `duckdb_v2_function_bind_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, invoked + * when the data is no longer needed. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param sql The SELECT statement to read instead. Borrowed for the call only. + * @param function The function to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_subquery(duckdb_v2_replacement_scan_info_handle info, - const duckdb_v2_str *sql, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_user_data(duckdb_v2_pragma_function_handle function, + duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Sets the alias the claimed replacement is bound under. + * Sets the query callback of the pragma function, making it a query pragma. * - * Optional, and independent of the claim form. An alias written in the query takes precedence over this one; without - * either, the table name of the reference is used, which for a file-backed reference is the path. The alias is borrowed - * and copied. + * The query callback is invoked before the `PRAGMA` statement is bound, and must set a SQL string through + * `duckdb_v2_pragma_function_query_set_query()`. The string is parsed and its statements run in place of the `PRAGMA` + * statement. A pragma function has either a query callback or an exec callback; registration fails when both or neither + * are set. * * history: * - stable: v2.0.0 * - * @param info The info handle. - * @param alias The alias to bind the replacement under. Borrowed and copied. + * @param function The function to set the query callback of. + * @param callback The query callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_alias(duckdb_v2_replacement_scan_info_handle info, - const duckdb_v2_identifier_t *alias, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_query_callback( + duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_query_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Registers the replacement scan, making it consulted for names the catalog cannot resolve. + * Sets the exec callback of the pragma function, making it an exec pragma. * - * The scan is registered on the target given at creation: the connection, the instance or the loading extension's - * instance. Registration requires a callback. Scans are consulted in registration order within their scope, - * connection-scoped ones before instance-wide ones, and the first to claim a name wins. A scan cannot be registered - * twice, and a registered scan cannot be unregistered: it lives until its scope ends. Registering an instance-wide scan - * while queries are binding on other connections is not thread-safe; register from an extension load callback or before - * issuing queries. The caller still owns the handle after registration and must destroy it with - * `duckdb_v2_replacement_scan_destroy()`, which does not affect the registered scan. + * The exec callback is invoked when the `PRAGMA` statement executes, and runs for its side effects: the statement + * produces no rows. A pragma function has either a query callback or an exec callback; registration fails when both or + * neither are set. * * history: * - stable: v2.0.0 * - * @param scan The scan to register. + * @param function The function to set the exec callback of. + * @param callback The exec callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_register(duckdb_v2_replacement_scan_handle scan, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_exec_callback( + duckdb_v2_pragma_function_handle function, duckdb_v2_pragma_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Destroys the replacement scan, releasing its resources. + * Sets the SQL to run in place of the `PRAGMA` statement. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered scan. + * The query may hold several statements, separated by semicolons. It is borrowed and copied. Calling this again + * replaces the previous query. The query callback must set a query. * * history: * - stable: v2.0.0 * - * @param scan The scan to destroy. + * @param info The query info handle. + * @param query The SQL to run. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_destroy(duckdb_v2_replacement_scan_handle *scan); - -/* --- Struct definitions for replacement scan --- */ - -/* ============================================================================ - * MODULE: scalar - * ============================================================================ */ - -/* --- Enums for scalar --- */ - -/* --- Struct forward declarations for scalar --- */ - -/* --- Types for scalar --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_query_set_query(duckdb_v2_pragma_function_query_info_handle info, + const duckdb_v2_str *query, + duckdb_v2_error_info_handle *err); /*! - * An owned opaque handle to a custom scalar function being built. Created with - * `duckdb_v2_scalar_function_create_with_connection()` or `duckdb_v2_scalar_function_create_with_extension()`, - * configured with the setter functions (e.g. `duckdb_v2_scalar_function_set_name()`, - * `duckdb_v2_scalar_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_scalar_function_get_signature()`, made available with `duckdb_v2_scalar_function_register()`, and - * destroyed with `duckdb_v2_scalar_function_destroy()`. + * Registers the pragma function, making it available to the `PRAGMA` statement. + * + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. Registration requires a name and exactly one of a query callback or an exec callback, and fails + * when a pragma of the same name already exists. The pragma function must have been created through a factory of the + * same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_pragma_function_destroy()`, which does not affect the registered function. + * + * history: + * - stable: v2.0.0 + * + * @param conn The connection to register on. + * @param function The pragma function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_scalar_function { - void *internal_ptr; -} * duckdb_v2_scalar_function_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_pragma_function(duckdb_v2_connection_handle conn, + duckdb_v2_pragma_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * A borrowed opaque handle to the result of a scalar function's "resolve types" phase. The "resolve types" callback - * receives this handle next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments and the - * user data, and can use it to set the return type of the call site being bound. + * Registers the pragma function, making it available to the `PRAGMA` statement. + * + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name and exactly one of a query callback or an exec callback, and + * fails when a pragma of the same name already exists. The pragma function must have been created through a factory of + * the same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_pragma_function_destroy()`, which does not affect the registered function. + * + * history: + * - stable: v2.0.0 + * + * @param extension The loading extension. + * @param function The pragma function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_scalar_function_resolve_types_info { - void *internal_ptr; -} * duckdb_v2_scalar_function_resolve_types_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_pragma_function(duckdb_v2_extension_handle extension, + duckdb_v2_pragma_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * A borrowed opaque handle to the result of a scalar function's "bind" phase. The "bind" callback receives this handle - * next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments and the user data, and can use - * it to set the bind data of the call site being bound. + * Destroys the pragma function, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. + * + * history: + * - stable: v2.0.0 + * + * @param function The function to destroy. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_scalar_function_bind_info { - void *internal_ptr; -} * duckdb_v2_scalar_function_bind_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_pragma_function_destroy(duckdb_v2_pragma_function_handle *function); + +/* --- Struct definitions for pragma --- */ + +/* ============================================================================ + * MODULE: replacement scan + * ============================================================================ */ + +/* --- Enums for replacement scan --- */ + +/* --- Struct forward declarations for replacement scan --- */ + +/* --- Types for replacement scan --- */ /*! - * A borrowed opaque handle to the arguments supplied to a scalar function during the local state initialization "init" - * phase. The "init" callback receives this handle and can use it to e.g. set up local state reusable across invocations - * of the "exec" execution callback. + * An owned opaque handle to a replacement scan being built. Created with `duckdb_v2_replacement_scan_create()`, + * configured with `duckdb_v2_replacement_scan_set_callback()` and `duckdb_v2_replacement_scan_set_user_data()`, made + * available with `duckdb_v2_connection_register_replacement_scan()`, `duckdb_v2_instance_register_replacement_scan()` + * or `duckdb_v2_extension_register_replacement_scan()`, and destroyed with `duckdb_v2_replacement_scan_destroy()`. */ -typedef struct _duckdb_v2_scalar_function_init_info { +typedef struct _duckdb_v2_replacement_scan { void *internal_ptr; -} * duckdb_v2_scalar_function_init_info_handle; +} * duckdb_v2_replacement_scan_handle; /*! - * A borrowed opaque handle to the arguments supplied to a scalar function during the execution "exec" phase. The "exec" - * callback receives this handle and can use it to e.g. access the input argument vectors, retrieve the number of rows - * to process, and write results to the output vector. + * A borrowed opaque handle to the arguments supplied to a replacement scan when the binder consults it. The callback + * receives this handle and can use it to inspect the unresolved name and to claim it by naming what to read instead. + * Valid only for the duration of the callback; the name it hands out is owned separately and outlives it. */ -typedef struct _duckdb_v2_scalar_function_exec_info { +typedef struct _duckdb_v2_replacement_scan_info { void *internal_ptr; -} * duckdb_v2_scalar_function_exec_info_handle; - -/* --- Constants for scalar --- */ - -/* --- Function pointer typedefs for scalar --- */ - -typedef void (*duckdb_v2_scalar_function_resolve_types_callback_fn)( - duckdb_v2_function_bind_info_handle info, duckdb_v2_scalar_function_resolve_types_info_handle result, - duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); +} * duckdb_v2_replacement_scan_info_handle; -typedef void (*duckdb_v2_scalar_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, - duckdb_v2_scalar_function_bind_info_handle result, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); +/* --- Constants for replacement scan --- */ -typedef void (*duckdb_v2_scalar_function_init_callback_fn)(duckdb_v2_scalar_function_init_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); +/* --- Function pointer typedefs for replacement scan --- */ -typedef void (*duckdb_v2_scalar_function_exec_callback_fn)(duckdb_v2_scalar_function_exec_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); +typedef void (*duckdb_v2_replacement_scan_callback_fn)(duckdb_v2_replacement_scan_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); -/* --- Functions for scalar --- */ +/* --- Functions for replacement scan --- */ /*! - * Creates a new scalar function that will be registered on the connection's database. + * Creates a new replacement scan through a factory. * - * The function starts out empty: configure it with the setter functions (e.g. `duckdb_v2_scalar_function_set_name()`, - * `duckdb_v2_scalar_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_scalar_function_get_signature()`, then make it available with `duckdb_v2_scalar_function_register()`. The - * caller owns the returned handle and must destroy it with `duckdb_v2_scalar_function_destroy()`, also after - * registration. + * The scan starts out empty: configure it with `duckdb_v2_replacement_scan_set_callback()` and optionally + * `duckdb_v2_replacement_scan_set_user_data()`, then make it available with + * `duckdb_v2_connection_register_replacement_scan()`, `duckdb_v2_instance_register_replacement_scan()` or + * `duckdb_v2_extension_register_replacement_scan()`. The caller owns the returned handle and must destroy it with + * `duckdb_v2_replacement_scan_destroy()`, also after registration. + * + * The scan can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created scalar function. Owned by the caller. + * @param factory The factory to create the scan through. + * @param scan On success, receives the newly created replacement scan. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_create_with_connection( - duckdb_v2_connection_handle connection, duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create(duckdb_v2_factory_handle factory, + duckdb_v2_replacement_scan_handle *scan, + duckdb_v2_error_info_handle *err); /*! - * Creates a new scalar function that will be registered on the loading extension's database. + * Sets the callback of the replacement scan. * - * Use this from an extension load callback, where an extension handle is available. The function starts out empty: - * configure it with the setter functions (e.g. `duckdb_v2_scalar_function_set_name()`, - * `duckdb_v2_scalar_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_scalar_function_get_signature()`, then make it available with `duckdb_v2_scalar_function_register()`. The - * caller owns the returned handle and must destroy it with `duckdb_v2_scalar_function_destroy()`, also after - * registration. + * The callback is invoked during query planning for each table reference the catalog could not resolve. It can inspect + * the unresolved name via `duckdb_v2_replacement_scan_get_name()`, and claim the reference via + * `duckdb_v2_replacement_scan_set_function_name()`, `duckdb_v2_replacement_scan_set_collection()` or + * `duckdb_v2_replacement_scan_set_subquery()`; returning without claiming declines it. The context passed to the + * callback may be used to read settings, but not to run queries. A callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param extension The extension to create the function in. - * @param function On success, receives the newly created scalar function. Owned by the caller. + * @param scan The scan to set the callback of. + * @param callback The callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_scalar_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_callback(duckdb_v2_replacement_scan_handle scan, + duckdb_v2_replacement_scan_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Sets the name of the scalar function. + * Sets arbitrary user data on the replacement scan. * - * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before - * registration. + * Associates an opaque pointer with the scan, retrievable from the callback via + * `duckdb_v2_replacement_scan_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, + * invoked when the data is no longer needed, at the latest when the scan's scope ends. The callback may be invoked from + * several connections at once, so the data must be safe to read concurrently. * * history: * - stable: v2.0.0 * - * @param function The function to set the name of. - * @param name The name to set. Borrowed and copied. + * @param scan The scan to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_name(duckdb_v2_scalar_function_handle function, - const duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_user_data(duckdb_v2_replacement_scan_handle scan, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Returns the function's signature so it can be configured. - * - * Add parameters with `duckdb_v2_function_signature_add_parameter()` and set the return type with - * `duckdb_v2_function_signature_set_return_type()`. The signature is modified in place; the function must be given a - * signature with a return type before registration. + * Retrieves the user data set via `duckdb_v2_replacement_scan_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param function The function to get the signature of. - * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. + * @param info The info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_get_signature(duckdb_v2_scalar_function_handle function, - duckdb_v2_function_signature_handle *sig, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_get_user_data(duckdb_v2_replacement_scan_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Sets arbitrary user data on the scalar function. + * Retrieves the name the catalog could not resolve. * - * Associates an opaque pointer with the function, retrievable from the callbacks via - * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_scalar_function_init_get_user_data()` and - * `duckdb_v2_scalar_function_exec_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, - * invoked when the data is no longer needed. + * The name as written in the query, as a path: an unqualified reference has a single part, and a qualified one carries + * its catalog and schema before it -- see `duckdb_v2_qname_get_part()`. For a file-backed reference the single part is + * the path with the quotes stripped. The returned name is owned by the caller and must be destroyed via + * `duckdb_v2_qname_destroy()`; being owned, it may outlive the callback. * * history: * - stable: v2.0.0 * - * @param function The function to set the user data of. - * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param info The info handle. + * @param name Receives the unresolved name. Owned by the caller; destroy via `duckdb_v2_qname_destroy()`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_user_data(duckdb_v2_scalar_function_handle function, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_get_name(duckdb_v2_replacement_scan_info_handle info, + duckdb_v2_qname_handle *name, + duckdb_v2_error_info_handle *err); /*! - * Sets a property on the scalar function. + * Claims the reference by naming a table function to read instead. * - * Configures a function property that influences planning and execution, such as stability, NULL handling, fallibility - * or collation handling. The value must be one of the `DUCKDB_V2_FUNCTION_PROPERTY_VALUE` entries belonging to `key`; - * passing a value that does not belong to the key, or a key that is not valid for scalar functions, results in an - * error. + * Arguments to the function are added with `duckdb_v2_replacement_scan_add_argument()` and + * `duckdb_v2_replacement_scan_add_named_argument()`. The name is borrowed and copied, and its parts are matched + * case-insensitively. A qualified name targets a function in a particular schema or catalog, exactly as writing it out + * in SQL would. The name is not resolved here: an unknown function fails later, when the replacement is bound. Calling + * this again replaces the previous name and keeps the arguments added so far. + * + * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, + * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually + * exclusive: claiming the reference through a second, different form results in an error. * * history: * - stable: v2.0.0 * - * @param function The function to set the property of. - * @param key The property to set. - * @param value The value to set for the property. Must belong to `key`. + * @param info The info handle. + * @param name The table function to read instead. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_property(duckdb_v2_scalar_function_handle function, - DUCKDB_V2_FUNCTION_PROPERTY_KEY key, - DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_function_name(duckdb_v2_replacement_scan_info_handle info, + duckdb_v2_qname_handle name, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional resolve types callback of the scalar function. + * Appends a positional argument to the claimed table function. * - * The resolve types callback is invoked during query planning for each call site of the function, before the arguments - * are cast to the parameter types of the signature. Through its `duckdb_v2_function_bind_info_handle` it can inspect - * the argument types as they were passed and constant argument values. Through its - * `duckdb_v2_scalar_function_resolve_types_info_handle` it can set a concrete return type. It cannot set "bind data", - * use the bind callback for that. + * Positional arguments are passed in the order they are added, before any named ones. The value is borrowed and copied, + * so the caller may destroy it after the call. Requires `duckdb_v2_replacement_scan_set_function_name()` to have been + * called first; otherwise the call results in an error. * * history: * - stable: v2.0.0 * - * @param function The function to set the resolve types callback of. - * @param callback The resolve types callback to set. + * @param info The info handle. + * @param value The argument value. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_resolve_types_callback( - duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_resolve_types_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_add_argument(duckdb_v2_replacement_scan_info_handle info, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional bind callback of the scalar function. + * Appends a named argument to the claimed table function. * - * The bind callback is invoked during query planning for each call site of the function, after the resolve types - * callback and after the arguments have been cast to the parameter types of the signature. Through its - * `duckdb_v2_function_bind_info_handle` it can inspect the argument types and constant argument values. Through its - * `duckdb_v2_scalar_function_bind_info_handle` it can set "bind data" that is shared with the init and exec callbacks. - * It cannot change the types of the call site, use the resolve types callback for that. + * Equivalent to writing `name := value` at the call site. The name and the value are borrowed and copied, so the caller + * may destroy the value after the call. Requires `duckdb_v2_replacement_scan_set_function_name()` to have been called + * first; otherwise the call results in an error. * * history: * - stable: v2.0.0 * - * @param function The function to set the bind callback of. - * @param callback The bind callback to set. + * @param info The info handle. + * @param name The name of the parameter to bind the value to. Borrowed and copied. + * @param value The argument value. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_bind_callback( - duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_bind_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_add_named_argument(duckdb_v2_replacement_scan_info_handle info, + const duckdb_v2_identifier_t *name, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional init callback of the scalar function. + * Claims the reference by naming a column data collection to read instead. * - * The init callback is invoked at the start of execution for each thread that will execute the function. It can set - * worker-local "init data", retrievable from the exec callback, to keep mutable state across invocations of the - * function. + * The collection is borrowed, not copied: the caller keeps ownership and must keep it alive, and must not clear, reset + * or destroy it, for as long as any result reading it is live, since the result scans its buffers directly. + * + * A prepared statement extends that lifetime well beyond its own results. Preparing a statement over a claimed name + * captures the borrow in the plan, and since a collection claim reads no database there is nothing to invalidate that + * plan: `duckdb_v2_prepared_statement_reuses_plan()` reports true, and every later execution reuses the captured borrow + * without consulting the callback again. So dropping the name from whatever the callback resolves against does not + * release the collection, and releasing it while such a statement is live leaves the next execution reading freed + * memory. Destroy every prepared statement over a claimed name before clearing, resetting or destroying the collection + * behind it. + * + * By default the collection's columns are named col1..colN. Pass `column_names` to name them instead. When supplied, + * `column_count` must equal the collection's column count and no name may be empty; pass NULL and 0 for the default + * names. + * + * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, + * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually + * exclusive: claiming the reference through a second, different form results in an error. * * history: * - stable: v2.0.0 * - * @param function The function to set the init callback of. - * @param callback The init callback to set. + * @param info The info handle. + * @param collection The collection to read instead. Borrowed; the caller keeps ownership and must keep it alive. + * @param column_names Optional. An array of `column_count` column names, in order. Pass NULL for the default names + * col1..colN. + * @param column_count The number of names in `column_names`. Must equal the collection's column count, or 0 for the + * default names. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_init_callback( - duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_init_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_collection( + duckdb_v2_replacement_scan_info_handle info, duckdb_v2_column_data_collection_handle collection, + const duckdb_v2_identifier_t *column_names, idx_t column_count, duckdb_v2_error_info_handle *err); /*! - * Sets the exec callback of the scalar function. + * Claims the reference by naming a query to read instead. * - * The exec callback implements the function's logic: it is invoked during query execution with a batch of input rows - * and must fill the result vector. An exec callback must be set before registration. + * The text is parsed at the call and must contain exactly one SELECT statement: a syntax error, several statements or a + * statement of another kind results in an error. The text is borrowed for the call only. Prefer + * `duckdb_v2_replacement_scan_set_function_name()` when a single table function call suffices, as it avoids the parse + * and keeps the plan flatter. + * + * The three claim forms, `duckdb_v2_replacement_scan_set_function_name()`, + * `duckdb_v2_replacement_scan_set_collection()` and `duckdb_v2_replacement_scan_set_subquery()`, are mutually + * exclusive: claiming the reference through a second, different form results in an error. * * history: * - stable: v2.0.0 * - * @param function The function to set the exec callback of. - * @param callback The exec callback to set. + * @param info The info handle. + * @param sql The SELECT statement to read instead. Borrowed for the call only. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_exec_callback( - duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_exec_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_subquery(duckdb_v2_replacement_scan_info_handle info, + const duckdb_v2_str *sql, + duckdb_v2_error_info_handle *err); /*! - * Sets the concrete return type of the call site being bound. + * Sets the alias the claimed replacement is bound under. * - * Overrides the return type declared in the signature. Register the function with an ANY return type and set the - * concrete type here, derived from the argument types, to build a function whose result type depends on its input. The - * type is borrowed and copied. + * Optional, and independent of the claim form. An alias written in the query takes precedence over this one; without + * either, the table name of the reference is used, which for a file-backed reference is the path. The alias is borrowed + * and copied. * * history: * - stable: v2.0.0 * - * @param info The resolve types info handle. - * @param return_type The return type to set. Borrowed for the call only. + * @param info The info handle. + * @param alias The alias to bind the replacement under. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_resolve_types_set_return_type( - duckdb_v2_scalar_function_resolve_types_info_handle info, duckdb_v2_logical_type_handle return_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_alias(duckdb_v2_replacement_scan_info_handle info, + const duckdb_v2_identifier_t *alias, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's "bind data" from the bind callback. + * Registers the replacement scan, making it consulted for names the catalog cannot resolve. * - * The bind data is stored with the bound call site and retrievable from the init and exec callbacks. The opaque handle - * bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an optional - * equality callback used when comparing two bound call sites; without one, pointer equality is used. Scalar functions - * set their bind data through this function, `duckdb_v2_function_bind_set_bind_data()` is not available to them. + * The scan is visible only to queries on this connection and is released when the connection is destroyed. It is + * consulted before every instance-wide scan, so it can claim a name that a built-in scan would otherwise take. + * Registration requires a callback. Scans are consulted in registration order within their scope, connection-scoped + * ones before instance-wide ones, and the first to claim a name wins. A scan cannot be registered twice, and a + * registered scan cannot be unregistered: it lives until its scope ends. The scan must have been created through a + * factory of the same database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_replacement_scan_destroy()`, which does not affect the registered scan. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. + * @param conn The connection to register on. + * @param scan The replacement scan to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_bind_set_bind_data( - duckdb_v2_scalar_function_bind_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_replacement_scan(duckdb_v2_connection_handle conn, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_scalar_function_set_user_data()`. + * Registers the replacement scan, making it consulted for names the catalog cannot resolve. + * + * The scan is visible to every connection to the instance and lives until the instance is destroyed. Registering it + * while queries are binding on other connections is not thread-safe: register before issuing queries. Registration + * requires a callback. Scans are consulted in registration order within their scope, connection-scoped ones before + * instance-wide ones, and the first to claim a name wins. A scan cannot be registered twice, and a registered scan + * cannot be unregistered: it lives until its scope ends. The scan must have been created through a factory of the same + * database. The caller still owns the handle after registration and must destroy it with + * `duckdb_v2_replacement_scan_destroy()`, which does not affect the registered scan. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param instance The instance to register on. + * @param scan The replacement scan to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_get_user_data( - duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_instance_register_replacement_scan(duckdb_v2_instance_handle instance, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Registers the replacement scan, making it consulted for names the catalog cannot resolve. + * + * The scan is visible to every connection to the loading extension's instance and lives until the instance is + * destroyed. Use this from the extension's load callback. Registration requires a callback. Scans are consulted in + * registration order within their scope, connection-scoped ones before instance-wide ones, and the first to claim a + * name wins. A scan cannot be registered twice, and a registered scan cannot be unregistered: it lives until its scope + * ends. The scan must have been created through a factory of the same database. The caller still owns the handle after + * registration and must destroy it with `duckdb_v2_replacement_scan_destroy()`, which does not affect the registered + * scan. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Receives the bind data pointer, or null if none was set. + * @param extension The loading extension. + * @param scan The replacement scan to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_get_bind_data( - duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_replacement_scan(duckdb_v2_extension_handle extension, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's worker-local "init data" from the init callback. + * Destroys the replacement scan, releasing its resources. * - * The init data is associated with the executing thread for the duration of the query and retrievable from the exec - * callback via `duckdb_v2_scalar_function_exec_get_init_data()`. The opaque handle bundles the pointer with an optional - * destructor, invoked when the init data is no longer needed. + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered scan. * * history: * - stable: v2.0.0 * - * @param info The init info handle. - * @param data Opaque handle bundling the init data pointer plus an optional destructor. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param scan The scan to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_set_init_data( - duckdb_v2_scalar_function_init_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_replacement_scan_destroy(duckdb_v2_replacement_scan_handle *scan); + +/* --- Struct definitions for replacement scan --- */ + +/* ============================================================================ + * MODULE: scalar + * ============================================================================ */ + +/* --- Enums for scalar --- */ + +/* --- Struct forward declarations for scalar --- */ + +/* --- Types for scalar --- */ /*! - * Retrieves the user data set via `duckdb_v2_scalar_function_set_user_data()`. + * An owned opaque handle to a custom scalar function being built. Created with `duckdb_v2_scalar_function_create()`, + * configured with the setter functions (e.g. `duckdb_v2_scalar_function_set_name()`, + * `duckdb_v2_scalar_function_set_exec_callback()`, etc.) and the signature obtained via + * `duckdb_v2_scalar_function_get_signature()`, made available with `duckdb_v2_connection_register_scalar_function()` or + * `duckdb_v2_extension_register_scalar_function()`, and destroyed with `duckdb_v2_scalar_function_destroy()`. + */ +typedef struct _duckdb_v2_scalar_function { + void *internal_ptr; +} * duckdb_v2_scalar_function_handle; + +/*! + * A borrowed opaque handle to the result of a scalar function's "resolve types" phase. The "resolve types" callback + * receives this handle next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments and the + * user data, and can use it to set the return type of the call site being bound. + */ +typedef struct _duckdb_v2_scalar_function_resolve_types_info { + void *internal_ptr; +} * duckdb_v2_scalar_function_resolve_types_info_handle; + +/*! + * A borrowed opaque handle to the result of a scalar function's "bind" phase. The "bind" callback receives this handle + * next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments and the user data, and can use + * it to set the bind data of the call site being bound. + */ +typedef struct _duckdb_v2_scalar_function_bind_info { + void *internal_ptr; +} * duckdb_v2_scalar_function_bind_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a scalar function during the local state initialization "init" + * phase. The "init" callback receives this handle and can use it to e.g. set up local state reusable across invocations + * of the "exec" execution callback. + */ +typedef struct _duckdb_v2_scalar_function_init_info { + void *internal_ptr; +} * duckdb_v2_scalar_function_init_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a scalar function during the execution "exec" phase. The "exec" + * callback receives this handle and can use it to e.g. access the input argument vectors, retrieve the number of rows + * to process, and write results to the output vector. + */ +typedef struct _duckdb_v2_scalar_function_exec_info { + void *internal_ptr; +} * duckdb_v2_scalar_function_exec_info_handle; + +/* --- Constants for scalar --- */ + +/* --- Function pointer typedefs for scalar --- */ + +typedef void (*duckdb_v2_scalar_function_resolve_types_callback_fn)( + duckdb_v2_function_bind_info_handle info, duckdb_v2_scalar_function_resolve_types_info_handle result, + duckdb_v2_context_handle context, duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_scalar_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, + duckdb_v2_scalar_function_bind_info_handle result, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_scalar_function_init_callback_fn)(duckdb_v2_scalar_function_init_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_scalar_function_exec_callback_fn)(duckdb_v2_scalar_function_exec_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +/* --- Functions for scalar --- */ + +/*! + * Creates a new scalar function through a factory. + * + * The function starts out empty: configure it with the setter functions (e.g. `duckdb_v2_scalar_function_set_name()`, + * `duckdb_v2_scalar_function_set_exec_callback()`, etc.) and the signature obtained via + * `duckdb_v2_scalar_function_get_signature()`, then make it available with + * `duckdb_v2_connection_register_scalar_function()` or `duckdb_v2_extension_register_scalar_function()`. The caller + * owns the returned handle and must destroy it with `duckdb_v2_scalar_function_destroy()`, also after registration. + * + * The scalar function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the user data pointer, or null if none was set. + * @param factory The factory to create the scalar function through. + * @param function On success, receives the newly created scalar function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_user_data( - duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_scalar_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Sets the name of the scalar function. + * + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_bind_data( - duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_name(duckdb_v2_scalar_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the worker-local init data set by the function's init callback. + * Returns the documentation of the function so it can be filled in. + * + * Set a description with `duckdb_v2_function_docs_set_description()`, and add examples and categories with + * `duckdb_v2_function_docs_add_example()` and `duckdb_v2_function_docs_add_category()`. The documentation is modified + * in place, describes this overload only, and is registered with the function. Optional. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the init data pointer for the executing thread, or null if none was set. + * @param function The function to get the documentation of. + * @param docs The returned documentation. Borrowed and valid for the lifetime of the function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_init_data( - duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_get_docs(duckdb_v2_scalar_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); /*! - * Returns how many rows this execution must produce. The exec callback must write exactly this many rows to the result - * vector. Note that this may be less than a full vector: with all-constant arguments the function is invoked for a - * single row and the result is expanded by the engine. + * Returns the function's signature so it can be configured. + * + * Add parameters with `duckdb_v2_function_signature_add_parameter()` and set the return type with + * `duckdb_v2_function_signature_set_return_type()`. The signature is modified in place; the function must be given a + * signature with a return type before registration. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param count Receives the number of rows. + * @param function The function to get the signature of. + * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_row_count( - duckdb_v2_scalar_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_get_signature(duckdb_v2_scalar_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); /*! - * Returns how many argument vectors this invocation carries, split into the four parts of the argument list. + * Sets arbitrary user data on the scalar function. * - * The parts and their order are those the bind callback saw through `duckdb_v2_function_bind_get_arg_count()`, and the - * vectors are at the same indices. Valid indices for `duckdb_v2_scalar_function_exec_get_arg()` are [0, the sum of the - * four counts). Every out-parameter may be NULL, in which case nothing is written to it. + * Associates an opaque pointer with the function, retrievable from the callbacks via + * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_scalar_function_init_get_user_data()` and + * `duckdb_v2_scalar_function_exec_get_user_data()`. The opaque handle bundles the pointer with an optional destructor, + * invoked when the data is no longer needed. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param positional_fixed Optional. Receives the number of positional-only and standard parameters. - * @param positional_variadic Optional. Receives the number of arguments `*args` received, 0 when the signature has - * none. - * @param named_fixed Optional. Receives the number of named-only parameters. - * @param named_variadic Optional. Receives the number of arguments `**kwargs` received, 0 when the signature has none. + * @param function The function to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_arg_count( - duckdb_v2_scalar_function_exec_info_handle info, idx_t *positional_fixed, idx_t *positional_variadic, - idx_t *named_fixed, idx_t *named_variadic, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_user_data(duckdb_v2_scalar_function_handle function, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the argument vector at the given index. + * Sets a property on the scalar function. * - * The index is the one the bind callback used for the argument, e.g. the index - * `duckdb_v2_function_bind_get_arg_index()` found for a name. The vector holds the argument's values for the current - * batch; use `duckdb_v2_scalar_function_exec_get_row_count()` for the number of rows. Fails if the index is out of - * bounds. Borrowed; valid only for the duration of the callback. + * Configures a function property that influences planning and execution, such as stability, NULL handling, fallibility + * or collation handling. The value must be one of the `DUCKDB_V2_FUNCTION_PROPERTY_VALUE` entries belonging to `key`; + * passing a value that does not belong to the key, or a key that is not valid for scalar functions, results in an + * error. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param index The index of the argument. - * @param vector Receives the borrowed argument vector. + * @param function The function to set the property of. + * @param key The property to set. + * @param value The value to set for the property. Must belong to `key`. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_arg(duckdb_v2_scalar_function_exec_info_handle info, - idx_t index, duckdb_v2_vector_handle *vector, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_property(duckdb_v2_scalar_function_handle function, + DUCKDB_V2_FUNCTION_PROPERTY_KEY key, + DUCKDB_V2_FUNCTION_PROPERTY_VALUE value, duckdb_v2_error_info_handle *err); /*! - * Retrieves the result vector the exec callback must write into. + * Sets the optional resolve types callback of the scalar function. * - * The callback must write one entry per input row; use `duckdb_v2_scalar_function_exec_get_row_count()` for the number - * of rows. Borrowed; valid only for the duration of the callback. + * The resolve types callback is invoked during query planning for each call site of the function, before the arguments + * are cast to the parameter types of the signature. Through its `duckdb_v2_function_bind_info_handle` it can inspect + * the argument types as they were passed and constant argument values. Through its + * `duckdb_v2_scalar_function_resolve_types_info_handle` it can set a concrete return type. It cannot set "bind data", + * use the bind callback for that. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param vector Receives the borrowed result vector to write into. + * @param function The function to set the resolve types callback of. + * @param callback The resolve types callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_result(duckdb_v2_scalar_function_exec_info_handle info, - duckdb_v2_vector_handle *vector, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_resolve_types_callback( + duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_resolve_types_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Registers the scalar function, making it available for use in SQL queries. + * Sets the optional bind callback of the scalar function. * - * The function is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a name, an exec callback, and a signature with a complete return type; an ANY return type is - * accepted only together with a resolve types callback that sets the concrete type per call site. The caller still owns - * the handle after registration and must destroy it with `duckdb_v2_scalar_function_destroy()`, which does not affect - * the registered function. + * The bind callback is invoked during query planning for each call site of the function, after the resolve types + * callback and after the arguments have been cast to the parameter types of the signature. Through its + * `duckdb_v2_function_bind_info_handle` it can inspect the argument types and constant argument values. Through its + * `duckdb_v2_scalar_function_bind_info_handle` it can set "bind data" that is shared with the init and exec callbacks. + * It cannot change the types of the call site, use the resolve types callback for that. * * history: * - stable: v2.0.0 * - * @param function The function to register. + * @param function The function to set the bind callback of. + * @param callback The bind callback to set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_register(duckdb_v2_scalar_function_handle function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_bind_callback( + duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_bind_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Destroys the scalar function, releasing its resources. + * Sets the optional init callback of the scalar function. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered function. + * The init callback is invoked at the start of execution for each thread that will execute the function. It can set + * worker-local "init data", retrievable from the exec callback, to keep mutable state across invocations of the + * function. * * history: * - stable: v2.0.0 * - * @param function The function to destroy. + * @param function The function to set the init callback of. + * @param callback The init callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_destroy(duckdb_v2_scalar_function_handle *function); - -/* --- Struct definitions for scalar --- */ - -/* ============================================================================ - * MODULE: sql_statement - * ============================================================================ */ - -/* --- Enums for sql_statement --- */ - -//! SQL statement type of a parsed statement or an executed query. -typedef enum DUCKDB_V2_STATEMENT_TYPE { - DUCKDB_V2_STATEMENT_TYPE_INVALID = 0, - DUCKDB_V2_STATEMENT_TYPE_SELECT = 1, - DUCKDB_V2_STATEMENT_TYPE_INSERT = 2, - DUCKDB_V2_STATEMENT_TYPE_UPDATE = 3, - DUCKDB_V2_STATEMENT_TYPE_CREATE = 4, - DUCKDB_V2_STATEMENT_TYPE_DELETE = 5, - DUCKDB_V2_STATEMENT_TYPE_PREPARE = 6, - DUCKDB_V2_STATEMENT_TYPE_EXECUTE = 7, - DUCKDB_V2_STATEMENT_TYPE_ALTER = 8, - DUCKDB_V2_STATEMENT_TYPE_TRANSACTION = 9, - DUCKDB_V2_STATEMENT_TYPE_COPY = 10, - DUCKDB_V2_STATEMENT_TYPE_ANALYZE = 11, - DUCKDB_V2_STATEMENT_TYPE_VARIABLE_SET = 12, - DUCKDB_V2_STATEMENT_TYPE_CREATE_FUNC = 13, - DUCKDB_V2_STATEMENT_TYPE_EXPLAIN = 14, - DUCKDB_V2_STATEMENT_TYPE_DROP = 15, - DUCKDB_V2_STATEMENT_TYPE_EXPORT = 16, - DUCKDB_V2_STATEMENT_TYPE_PRAGMA = 17, - DUCKDB_V2_STATEMENT_TYPE_VACUUM = 18, - DUCKDB_V2_STATEMENT_TYPE_CALL = 19, - DUCKDB_V2_STATEMENT_TYPE_SET = 20, - DUCKDB_V2_STATEMENT_TYPE_LOAD = 21, - DUCKDB_V2_STATEMENT_TYPE_RELATION = 22, - DUCKDB_V2_STATEMENT_TYPE_EXTENSION = 23, - DUCKDB_V2_STATEMENT_TYPE_LOGICAL_PLAN = 24, - DUCKDB_V2_STATEMENT_TYPE_ATTACH = 25, - DUCKDB_V2_STATEMENT_TYPE_DETACH = 26, - DUCKDB_V2_STATEMENT_TYPE_MULTI = 27, - DUCKDB_V2_STATEMENT_TYPE_COPY_DATABASE = 28, - DUCKDB_V2_STATEMENT_TYPE_UPDATE_EXTENSIONS = 29, - DUCKDB_V2_STATEMENT_TYPE_MERGE_INTO = 30, - DUCKDB_V2_STATEMENT_TYPE_CONNECT = 31, - DUCKDB_V2_STATEMENT_TYPE_DISCONNECT = 32, - DUCKDB_V2_STATEMENT_TYPE_EXTERNAL_RESOURCE = 33, - DUCKDB_V2_STATEMENT_TYPE_PASSTHROUGH = 34, - DUCKDB_V2_STATEMENT_TYPE_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_STATEMENT_TYPE; - -/* --- Struct forward declarations for sql_statement --- */ - -/* --- Types for sql_statement --- */ - -/*! - * An opaque, owned handle to a single parsed SQL statement, produced by statement_iterator_next. statement_execute runs - * it without consuming it; the caller always destroys it via sql_statement_destroy. - */ -typedef struct _duckdb_v2_sql_statement { - void *internal_ptr; -} * duckdb_v2_sql_statement_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_init_callback( + duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_init_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * An opaque, owned handle to an iterator over the statements of a SQL string, produced by parse_sql. Destroy it via - * statement_iterator_destroy; statements it already yielded are independently owned and unaffected. + * Sets the exec callback of the scalar function. + * + * The exec callback implements the function's logic: it is invoked during query execution with a batch of input rows + * and must fill the result vector. An exec callback must be set before registration. + * + * history: + * - stable: v2.0.0 + * + * @param function The function to set the exec callback of. + * @param callback The exec callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_statement_iterator { - void *internal_ptr; -} * duckdb_v2_statement_iterator_handle; - -/* --- Constants for sql_statement --- */ - -/* --- Function pointer typedefs for sql_statement --- */ - -/* --- Functions for sql_statement --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_exec_callback( + duckdb_v2_scalar_function_handle function, duckdb_v2_scalar_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Parses a SQL string into an iterator over its statements. + * Sets the concrete return type of the call site being bound. * - * Parses and nothing more: no binding, no catalog access, no transaction. The connection supplies the parser options - * and parser extensions, and is not otherwise touched. Statements are raw parser output; statement-level rewrites - * happen inside statement_execute. The SQL string is copied, so the caller may free it once this call returns. An input - * with no statements — empty, whitespace, or separators only — yields an iterator that is immediately exhausted. + * Overrides the return type declared in the signature. Register the function with an ANY return type and set the + * concrete type here, derived from the argument types, to build a function whose result type depends on its input. The + * type is borrowed and copied. * * history: * - stable: v2.0.0 * - * @param conn The connection supplying the parser configuration. - * @param sql Null-terminated SQL string; may contain any number of statements. - * @param out_iterator Receives the new iterator handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The resolve types info handle. + * @param return_type The return type to set. Borrowed for the call only. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_parse_sql(duckdb_v2_connection_handle conn, const char *sql, - duckdb_v2_statement_iterator_handle *out_iterator, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_resolve_types_set_return_type( + duckdb_v2_scalar_function_resolve_types_info_handle info, duckdb_v2_logical_type_handle return_type, + duckdb_v2_error_info_handle *err); /*! - * Yields the next statement, or NULL when exhausted. + * Sets the function's "bind data" from the bind callback. * - * On success *out_statement receives the next owned statement, or NULL once the iterator is exhausted — repeatedly, so - * calling again is harmless. A parse error within the input surfaces no later than the call that reaches the failing - * statement: an implementation that parses eagerly reports it from parse_sql instead and yields no statements at all, - * while an incremental one yields the statements ahead of the failure first. On failure *out_statement is set to NULL. + * The bind data is stored with the bound call site and retrievable from the init and exec callbacks. The opaque handle + * bundles the pointer with an optional destructor, invoked when the bind data is no longer needed, and an optional + * equality callback used when comparing two bound call sites; without one, pointer equality is used. Scalar functions + * set their bind data through this function, `duckdb_v2_function_bind_set_bind_data()` is not available to them. * * history: * - stable: v2.0.0 * - * @param iterator The iterator to advance. - * @param out_statement Receives an owned statement, or NULL when the iterator is exhausted. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The bind info handle. + * @param data Opaque handle bundling the bind data pointer plus optional destructor and equality callbacks. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_iterator_next(duckdb_v2_statement_iterator_handle iterator, - duckdb_v2_sql_statement_handle *out_statement, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_bind_set_bind_data( + duckdb_v2_scalar_function_bind_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Binds a parsed statement without executing, yielding its schema signature. - * - * Preprocesses and binds the statement exactly as execution would, but runs nothing: the result is the statement's - * signature as two schemas, not a query result. The statement is borrowed, not consumed, so it can be bound as often as - * you like and executed later. out_schema receives the output schema (result columns) and is never empty, since a - * non-SELECT reports a single status column: a BIGINT changed-rows count, or a BOOLEAN success. out_parameters, when - * non-NULL, receives the input schema (parameter types, ordered by binding index). Both are owned; destroy them via - * schema_destroy. - * - * Binding is read-only and does not disturb a live result: it begins no query, claims no cursor, reuses an active - * transaction read-only, and runs alongside a paused stream. It is single-consumer like the stepping functions, so bind - * concurrently only on a second connection. Prepare-time errors — binder, catalog, preprocessing — surface here. A - * statement that preprocessing expands into a group (a dynamic PIVOT, or statement-expanding DDL such as ALTER ADD - * COLUMN with a non-constant DEFAULT) cannot be bound and is rejected with ERROR_INPUT_INVALID; execute it instead. - * - * *out_schema and *out_parameters are set to NULL on failure. + * Retrieves the user data set via `duckdb_v2_scalar_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection supplying the catalog, transaction, and parser configuration. - * @param statement The statement to bind. Borrowed; not consumed. - * @param out_schema Receives the owned output schema (result columns). Destroy via schema_destroy. - * @param out_parameters Optional. When non-NULL, receives the owned input schema (parameter types, ordered by binding - * index). Destroy via schema_destroy. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The init info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_bind(duckdb_v2_connection_handle conn, - duckdb_v2_sql_statement_handle statement, - duckdb_v2_schema_handle *out_schema, - duckdb_v2_schema_handle *out_parameters, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_get_user_data( + duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Returns the statement's type as classified by the parser. - * - * This gives the type before the statement-level rewrites that `duckdb_v2_statement_execute()` applies, if any. So a - * PRAGMA reports PRAGMA even where execution rewrites it into a SELECT or a CALL. - * `duckdb_v2_result_get_statement_type()` on the executed result reports the rewritten type. A statement the parser - * expands into a group reports MULTI. Its parts are not visible here, and statement_bind rejects it. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param statement The statement. - * @param out_type Receives the statement type. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The init info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_type(duckdb_v2_sql_statement_handle statement, - DUCKDB_V2_STATEMENT_TYPE *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_get_bind_data( + duckdb_v2_scalar_function_init_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Borrows the statement's own SQL text. + * Sets the function's worker-local "init data" from the init callback. * - * The slice of the parsed string that belongs to this statement. A trailing terminator and the whitespace after it are - * included, whitespace and comments before the first token are not. The statement holds its own copy, so the view - * outlives the SQL string passed to parse_sql and the iterator, and stays valid until the statement is destroyed. A - * statement produced by a parser extension that overrides parsing carries whatever text the extension recorded. + * The init data is associated with the executing thread for the duration of the query and retrievable from the exec + * callback via `duckdb_v2_scalar_function_exec_get_init_data()`. The opaque handle bundles the pointer with an optional + * destructor, invoked when the init data is no longer needed. * * history: * - stable: v2.0.0 * - * @param statement The statement. - * @param out_text Receives a borrowed view of the statement text, valid until the statement is destroyed. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The init info handle. + * @param data Opaque handle bundling the init data pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_text(duckdb_v2_sql_statement_handle statement, - duckdb_v2_str *out_text, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_init_set_init_data( + duckdb_v2_scalar_function_init_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Returns the number of distinct parameters the statement declares. - * - * This counts the parameters the parser found ($1, ?, $name, ...), so it needs no catalog and no binding; only the - * parameter types wait for `duckdb_v2_statement_bind()`. Repeated uses of one parameter count once. + * Retrieves the user data set via `duckdb_v2_scalar_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param statement The statement. - * @param out_count Receives the number of distinct parameters. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_parameter_count(duckdb_v2_sql_statement_handle statement, - idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_user_data( + duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Borrows the name of one parameter, in binding order. - * - * Parse-time metadata. Positions follow the parameters' binding indices, the order of `duckdb_v2_statement_bind()`'s - * parameter schema, so position i here names field i there. The name is the binding key that - * `duckdb_v2_statement_execute()` accepts: "1", "2", ... for a positional parameter ($1 or ?), the identifier for a - * named one ($name). Positional indices may be gapped ($1 and $3 without $2), in which case the names are "1" and "3" - * at positions 0 and 1. The view is valid until the statement is destroyed. An index outside [0, count) is rejected - * with ERROR_INPUT_OUT_OF_RANGE. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param statement The statement. - * @param index Zero-based position in binding order. - * @param out_name Receives a borrowed view of the parameter name, valid until the statement is destroyed. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_parameter_name(duckdb_v2_sql_statement_handle statement, - idx_t index, duckdb_v2_identifier_t *out_name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_bind_data( + duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Destroys a statement handle. - * - * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. statement_execute does not consume a - * statement, so every statement is destroyed here once it is no longer needed. On success the slot is set to nullptr. + * Retrieves the worker-local init data set by the function's init callback. * * history: * - stable: v2.0.0 * - * @param statement The statement to destroy. + * @param info The exec info handle. + * @param data Receives the init data pointer for the executing thread, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_destroy(duckdb_v2_sql_statement_handle *statement); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_init_data( + duckdb_v2_scalar_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Destroys a statement iterator handle. - * - * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. Statements already yielded are independently - * owned and stay valid. On success the slot is set to nullptr. + * Returns how many rows this execution must produce. The exec callback must write exactly this many rows to the result + * vector. Note that this may be less than a full vector: with all-constant arguments the function is invoked for a + * single row and the result is expanded by the engine. * * history: * - stable: v2.0.0 * - * @param iterator The iterator to destroy. + * @param info The exec info handle. + * @param count Receives the number of rows. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_iterator_destroy(duckdb_v2_statement_iterator_handle *iterator); - -/* --- Struct definitions for sql_statement --- */ - -/* ============================================================================ - * MODULE: value - * ============================================================================ */ - -/* --- Enums for value --- */ - -/* --- Struct forward declarations for value --- */ - -/* --- Types for value --- */ - -/* --- Constants for value --- */ - -/* --- Function pointer typedefs for value --- */ - -/* --- Functions for value --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_row_count( + duckdb_v2_scalar_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Destroys a value handle. + * Returns how many argument vectors this invocation carries, split into the four parts of the argument list. * - * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. On success the slot is set to nullptr. + * The parts and their order are those the bind callback saw through `duckdb_v2_function_bind_get_arg_count()`, and the + * vectors are at the same indices. Valid indices for `duckdb_v2_scalar_function_exec_get_arg()` are [0, the sum of the + * four counts). Every out-parameter may be NULL, in which case nothing is written to it. * * history: * - stable: v2.0.0 * - * @param value The value to destroy. + * @param info The exec info handle. + * @param positional_fixed Optional. Receives the number of positional-only and standard parameters. + * @param positional_variadic Optional. Receives the number of arguments `*args` received, 0 when the signature has + * none. + * @param named_fixed Optional. Receives the number of named-only parameters. + * @param named_variadic Optional. Receives the number of arguments `**kwargs` received, 0 when the signature has none. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_destroy(duckdb_v2_value_handle *value); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_arg_count( + duckdb_v2_scalar_function_exec_info_handle info, idx_t *positional_fixed, idx_t *positional_variadic, + idx_t *named_fixed, idx_t *named_variadic, duckdb_v2_error_info_handle *err); /*! - * Creates a NULL value of the given logical type. + * Retrieves the argument vector at the given index. * - * The logical type is borrowed and copied into the value, so the caller can destroy it independently. + * The index is the one the bind callback used for the argument, e.g. the index + * `duckdb_v2_function_bind_get_arg_index()` found for a name. The vector holds the argument's values for the current + * batch; use `duckdb_v2_scalar_function_exec_get_row_count()` for the number of rows. Fails if the index is out of + * bounds. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param type The borrowed logical type to attach to the NULL value. - * @param out_value Receives the new NULL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param index The index of the argument. + * @param vector Receives the borrowed argument vector. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_null(duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_arg(duckdb_v2_scalar_function_exec_info_handle info, + idx_t index, duckdb_v2_vector_handle *vector, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a BOOLEAN. + * Retrieves the result vector the exec callback must write into. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The callback must write one entry per input row; use `duckdb_v2_scalar_function_exec_get_row_count()` for the number + * of rows. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a bool. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param vector Receives the borrowed result vector to write into. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_bool(duckdb_v2_value_handle value, bool *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_result(duckdb_v2_scalar_function_exec_info_handle info, + duckdb_v2_vector_handle *vector, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a UTINYINT. + * Registers the scalar function, making it available for use in SQL queries. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. Registration requires a name, an exec callback, and a signature with a complete return type; an + * ANY return type is accepted only together with a resolve types callback that sets the concrete type per call site. + * The scalar function must have been created through a factory of the same database. The caller still owns the handle + * after registration and must destroy it with `duckdb_v2_scalar_function_destroy()`, which does not affect the + * registered function. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a uint8_t. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param conn The connection to register on. + * @param function The scalar function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_utinyint(duckdb_v2_value_handle value, uint8_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_scalar_function(duckdb_v2_connection_handle conn, + duckdb_v2_scalar_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a USMALLINT. + * Registers the scalar function, making it available for use in SQL queries. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name, an exec callback, and a signature with a complete return + * type; an ANY return type is accepted only together with a resolve types callback that sets the concrete type per call + * site. The scalar function must have been created through a factory of the same database. The caller still owns the + * handle after registration and must destroy it with `duckdb_v2_scalar_function_destroy()`, which does not affect the + * registered function. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a uint16_t. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param extension The loading extension. + * @param function The scalar function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_usmallint(duckdb_v2_value_handle value, uint16_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_scalar_function(duckdb_v2_extension_handle extension, + duckdb_v2_scalar_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a UINTEGER. + * Destroys the scalar function, releasing its resources. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a uint32_t. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uint(duckdb_v2_value_handle value, uint32_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_scalar_function_destroy(duckdb_v2_scalar_function_handle *function); -/*! - * Returns the value as a UBIGINT. +/* --- Struct definitions for scalar --- */ + +/* ============================================================================ + * MODULE: sql_statement + * ============================================================================ */ + +/* --- Enums for sql_statement --- */ + +//! SQL statement type of a parsed statement or an executed query. +typedef enum DUCKDB_V2_STATEMENT_TYPE { + DUCKDB_V2_STATEMENT_TYPE_INVALID = 0, + DUCKDB_V2_STATEMENT_TYPE_SELECT = 1, + DUCKDB_V2_STATEMENT_TYPE_INSERT = 2, + DUCKDB_V2_STATEMENT_TYPE_UPDATE = 3, + DUCKDB_V2_STATEMENT_TYPE_CREATE = 4, + DUCKDB_V2_STATEMENT_TYPE_DELETE = 5, + DUCKDB_V2_STATEMENT_TYPE_PREPARE = 6, + DUCKDB_V2_STATEMENT_TYPE_EXECUTE = 7, + DUCKDB_V2_STATEMENT_TYPE_ALTER = 8, + DUCKDB_V2_STATEMENT_TYPE_TRANSACTION = 9, + DUCKDB_V2_STATEMENT_TYPE_COPY = 10, + DUCKDB_V2_STATEMENT_TYPE_ANALYZE = 11, + DUCKDB_V2_STATEMENT_TYPE_VARIABLE_SET = 12, + DUCKDB_V2_STATEMENT_TYPE_CREATE_FUNC = 13, + DUCKDB_V2_STATEMENT_TYPE_EXPLAIN = 14, + DUCKDB_V2_STATEMENT_TYPE_DROP = 15, + DUCKDB_V2_STATEMENT_TYPE_EXPORT = 16, + DUCKDB_V2_STATEMENT_TYPE_PRAGMA = 17, + DUCKDB_V2_STATEMENT_TYPE_VACUUM = 18, + DUCKDB_V2_STATEMENT_TYPE_CALL = 19, + DUCKDB_V2_STATEMENT_TYPE_SET = 20, + DUCKDB_V2_STATEMENT_TYPE_LOAD = 21, + DUCKDB_V2_STATEMENT_TYPE_RELATION = 22, + DUCKDB_V2_STATEMENT_TYPE_EXTENSION = 23, + DUCKDB_V2_STATEMENT_TYPE_LOGICAL_PLAN = 24, + DUCKDB_V2_STATEMENT_TYPE_ATTACH = 25, + DUCKDB_V2_STATEMENT_TYPE_DETACH = 26, + DUCKDB_V2_STATEMENT_TYPE_MULTI = 27, + DUCKDB_V2_STATEMENT_TYPE_COPY_DATABASE = 28, + DUCKDB_V2_STATEMENT_TYPE_UPDATE_EXTENSIONS = 29, + DUCKDB_V2_STATEMENT_TYPE_MERGE_INTO = 30, + DUCKDB_V2_STATEMENT_TYPE_CONNECT = 31, + DUCKDB_V2_STATEMENT_TYPE_DISCONNECT = 32, + DUCKDB_V2_STATEMENT_TYPE_EXTERNAL_RESOURCE = 33, + DUCKDB_V2_STATEMENT_TYPE_PASSTHROUGH = 34, + DUCKDB_V2_STATEMENT_TYPE_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_STATEMENT_TYPE; + +/* --- Struct forward declarations for sql_statement --- */ + +/* --- Types for sql_statement --- */ + +/*! + * An opaque, owned handle to a single parsed SQL statement, produced by statement_iterator_next. statement_execute runs + * it without consuming it; the caller always destroys it via sql_statement_destroy. + */ +typedef struct _duckdb_v2_sql_statement { + void *internal_ptr; +} * duckdb_v2_sql_statement_handle; + +/*! + * An opaque, owned handle to an iterator over the statements of a SQL string, produced by parse_sql. Destroy it via + * statement_iterator_destroy; statements it already yielded are independently owned and unaffected. + */ +typedef struct _duckdb_v2_statement_iterator { + void *internal_ptr; +} * duckdb_v2_statement_iterator_handle; + +/* --- Constants for sql_statement --- */ + +/* --- Function pointer typedefs for sql_statement --- */ + +/* --- Functions for sql_statement --- */ + +/*! + * Parses a SQL string into an iterator over its statements. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Parses and nothing more: no binding, no catalog access, no transaction. The connection supplies the parser options + * and parser extensions, and is not otherwise touched. Statements are raw parser output; statement-level rewrites + * happen inside statement_execute. The SQL string is copied, so the caller may free it once this call returns. An input + * with no statements — empty, whitespace, or separators only — yields an iterator that is immediately exhausted. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a uint64_t. + * @param conn The connection supplying the parser configuration. + * @param sql Null-terminated SQL string; may contain any number of statements. + * @param out_iterator Receives the new iterator handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_ubigint(duckdb_v2_value_handle value, uint64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_parse_sql(duckdb_v2_connection_handle conn, const char *sql, + duckdb_v2_statement_iterator_handle *out_iterator, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a UHUGEINT. + * Yields the next statement, or NULL when exhausted. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * On success *out_statement receives the next owned statement, or NULL once the iterator is exhausted — repeatedly, so + * calling again is harmless. A parse error within the input surfaces no later than the call that reaches the failing + * statement: an implementation that parses eagerly reports it from parse_sql instead and yields no statements at all, + * while an incremental one yields the statements ahead of the failure first. On failure *out_statement is set to NULL. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a uint128_t. + * @param iterator The iterator to advance. + * @param out_statement Receives an owned statement, or NULL when the iterator is exhausted. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uhugeint(duckdb_v2_value_handle value, duckdb_v2_uhugeint_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_iterator_next(duckdb_v2_statement_iterator_handle iterator, + duckdb_v2_sql_statement_handle *out_statement, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TINYINT. + * Binds a parsed statement without executing, yielding its schema signature. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Preprocesses and binds the statement exactly as execution would, but runs nothing: the result is the statement's + * signature as two schemas, not a query result. The statement is borrowed, not consumed, so it can be bound as often as + * you like and executed later. out_schema receives the output schema (result columns) and is never empty, since a + * non-SELECT reports a single status column: a BIGINT changed-rows count, or a BOOLEAN success. out_parameters, when + * non-NULL, receives the input schema (parameter types, ordered by binding index). Both are owned; destroy them via + * schema_destroy. + * + * Binding is read-only and does not disturb a live result: it begins no query, claims no cursor, reuses an active + * transaction read-only, and runs alongside a paused stream. It is single-consumer like the stepping functions, so bind + * concurrently only on a second connection. Prepare-time errors — binder, catalog, preprocessing — surface here. A + * statement that preprocessing expands into a group (a dynamic PIVOT, or statement-expanding DDL such as ALTER ADD + * COLUMN with a non-constant DEFAULT) cannot be bound and is rejected with ERROR_INPUT_INVALID; execute it instead. + * + * *out_schema and *out_parameters are set to NULL on failure. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as an int8_t. + * @param conn The connection supplying the catalog, transaction, and parser configuration. + * @param statement The statement to bind. Borrowed; not consumed. + * @param out_schema Receives the owned output schema (result columns). Destroy via schema_destroy. + * @param out_parameters Optional. When non-NULL, receives the owned input schema (parameter types, ordered by binding + * index). Destroy via schema_destroy. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_tinyint(duckdb_v2_value_handle value, int8_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_bind(duckdb_v2_connection_handle conn, + duckdb_v2_sql_statement_handle statement, + duckdb_v2_schema_handle *out_schema, + duckdb_v2_schema_handle *out_parameters, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a SMALLINT. + * Binds a column data collection to the statement under a name. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Makes the collection visible to the statement as a table named `name`, by adding a common table expression to the + * parsed statement. The statement can then be bound, prepared or executed as usual; each execution reads the + * collection's contents at that moment, so a prepared statement can be executed again after the collection was cleared + * and refilled. Supported statement types are SELECT, INSERT, UPDATE, DELETE and MERGE INTO; any other type is rejected + * with `ERROR_INPUT_INVALID`. + * + * A name the statement already defines as a common table expression, in its SQL text or by a prior call, is rejected + * with `ERROR_INPUT_INVALID`. The binding shadows any catalog table named `name`, as a common table expression does. + * + * By default the collection's columns are named col1..colN. Pass `column_names` to name them instead; when supplied, + * `column_count` must equal the collection's column count. Pass NULL and 0 for the default names. + * + * The collection is borrowed: the caller keeps ownership and must keep it alive, and must not clear, reset or destroy + * it, for as long as the statement, any statement copied or prepared from it, or any result executing it is live. An + * empty collection is a valid binding. Several collections can be bound under distinct names. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as an int16_t. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param statement The statement to bind the collection to. Modified in place. + * @param name The non-empty name the statement refers to the collection by. Borrowed and copied. + * @param collection The collection to bind. Borrowed; the caller keeps ownership and must keep it alive. + * @param column_names Optional. An array of `column_count` names for the collection's columns, in order. Pass NULL for + * col1..colN. + * @param column_count The number of names in `column_names`. 0 for the default names; otherwise the collection's column + * count. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_smallint(duckdb_v2_value_handle value, int16_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_add_collection(duckdb_v2_sql_statement_handle statement, + const duckdb_v2_identifier_t *name, + duckdb_v2_column_data_collection_handle collection, + const duckdb_v2_identifier_t *column_names, + idx_t column_count, duckdb_v2_error_info_handle *err); /*! - * Returns the value as an INTEGER. + * Returns the statement's type as classified by the parser. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * This gives the type before the statement-level rewrites that `duckdb_v2_statement_execute()` applies, if any. So a + * PRAGMA reports PRAGMA even where execution rewrites it into a SELECT or a CALL. + * `duckdb_v2_result_get_statement_type()` on the executed result reports the rewritten type. A statement the parser + * expands into a group reports MULTI. Its parts are not visible here, and statement_bind rejects it. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as an int32_t. + * @param statement The statement. + * @param out_type Receives the statement type. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_int(duckdb_v2_value_handle value, int32_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_type(duckdb_v2_sql_statement_handle statement, + DUCKDB_V2_STATEMENT_TYPE *out_type, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a BIGINT. + * Borrows the statement's own SQL text. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The slice of the parsed string that belongs to this statement. A trailing terminator and the whitespace after it are + * included, whitespace and comments before the first token are not. The statement holds its own copy, so the view + * outlives the SQL string passed to parse_sql and the iterator, and stays valid until the statement is destroyed. A + * statement produced by a parser extension that overrides parsing carries whatever text the extension recorded. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as an int64_t. + * @param statement The statement. + * @param out_text Receives a borrowed view of the statement text, valid until the statement is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_bigint(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_text(duckdb_v2_sql_statement_handle statement, + duckdb_v2_str *out_text, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a HUGEINT. + * Returns the number of distinct parameters the statement declares. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * This counts the parameters the parser found ($1, ?, $name, ...), so it needs no catalog and no binding; only the + * parameter types wait for `duckdb_v2_statement_bind()`. Repeated uses of one parameter count once. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as an int128_t. + * @param statement The statement. + * @param out_count Receives the number of distinct parameters. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_hugeint(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_parameter_count(duckdb_v2_sql_statement_handle statement, + idx_t *out_count, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a VARCHAR. + * Borrows the name of one parameter, in binding order. * - * The string is borrowed from the value and stays valid until the value is destroyed. That borrow is why this getter - * does not convert: a converted copy would not outlive the call. A value of any other type id, and a NULL value, return - * ERROR_INPUT_INVALID. + * Parse-time metadata. Positions follow the parameters' binding indices, the order of `duckdb_v2_statement_bind()`'s + * parameter schema, so position i here names field i there. The name is the binding key that + * `duckdb_v2_statement_execute()` accepts: "1", "2", ... for a positional parameter ($1 or ?), the identifier for a + * named one ($name). Positional indices may be gapped ($1 and $3 without $2), in which case the names are "1" and "3" + * at positions 0 and 1. The view is valid until the statement is destroyed. An index outside [0, count) is rejected + * with ERROR_INPUT_OUT_OF_RANGE. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a UTF-8 string. The returned string is borrowed and valid until the value handle - * is destroyed. + * @param statement The statement. + * @param index Zero-based position in binding order. + * @param out_name Receives a borrowed view of the parameter name, valid until the statement is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_varchar(duckdb_v2_value_handle value, duckdb_v2_str *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_parameter_name(duckdb_v2_sql_statement_handle statement, + idx_t index, duckdb_v2_identifier_t *out_name, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a byte string: a BLOB, or the storage bytes of a BIT or BIGNUM. + * Destroys a statement handle. * - * The string is borrowed from the value and stays valid until the value is destroyed. That borrow is why this getter - * does not convert: a converted copy would not outlive the call. A value of any other type id, and a NULL value, return - * ERROR_INPUT_INVALID. + * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. statement_execute does not consume a + * statement, so every statement is destroyed here once it is no longer needed. On success the slot is set to nullptr. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a byte string. The returned string is borrowed and valid until the value handle is - * destroyed. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param statement The statement to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_blob(duckdb_v2_value_handle value, duckdb_v2_str *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_sql_statement_destroy(duckdb_v2_sql_statement_handle *statement); /*! - * Returns the value as a FLOAT. + * Destroys a statement iterator handle. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. Statements already yielded are independently + * owned and stay valid. On success the slot is set to nullptr. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a float. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param iterator The iterator to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_float(duckdb_v2_value_handle value, float *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_iterator_destroy(duckdb_v2_statement_iterator_handle *iterator); + +/* --- Struct definitions for sql_statement --- */ + +/* ============================================================================ + * MODULE: vector + * ============================================================================ */ + +/* --- Enums for vector --- */ /*! - * Returns the value as a DOUBLE. + * Internal representation of a vector, with FSST / SEQUENCE / SHREDDED collapsed into OTHER: the view getter rejects + * those and requires an explicit vector_flatten first. OTHER is the 0-value, so a zero-initialized out-param reads as + * "unspecified, needs flatten" rather than silently looking like FLAT. + */ +typedef enum DUCKDB_V2_VECTOR_TYPE { + //! Default for zero-init. Covers FSST / SEQUENCE / SHREDDED — call vector_flatten first. + DUCKDB_V2_VECTOR_TYPE_OTHER = 0, + + //! Standard per-row storage. + DUCKDB_V2_VECTOR_TYPE_FLAT = 1, + + //! A single value applies to every row in the vector. + DUCKDB_V2_VECTOR_TYPE_CONSTANT = 2, + + //! Data + selection vector indirection into another vector. + DUCKDB_V2_VECTOR_TYPE_DICTIONARY = 3, + DUCKDB_V2_VECTOR_TYPE_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_VECTOR_TYPE; + +/* --- Struct forward declarations for vector --- */ + +typedef struct duckdb_v2_vector_view duckdb_v2_vector_view; + +typedef struct duckdb_v2_list_entry duckdb_v2_list_entry; + +typedef struct duckdb_v2_hugeint_t duckdb_v2_hugeint_t; + +typedef struct duckdb_v2_uhugeint_t duckdb_v2_uhugeint_t; + +typedef struct duckdb_v2_interval_t duckdb_v2_interval_t; + +/* --- Types for vector --- */ + +/* --- Constants for vector --- */ + +/* --- Function pointer typedefs for vector --- */ + +/* --- Functions for vector --- */ + +/*! + * Returns the internal representation kind: flat, constant, or dictionary. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Reports VECTOR_TYPE_OTHER for FSST / SEQUENCE / SHREDDED vectors, which must be passed through vector_flatten before + * vector_get_view will read them. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload as a double. + * @param vector The vector. + * @param out_type Receives the representation kind. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_double(duckdb_v2_value_handle value, double *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_vector_type(duckdb_v2_vector_handle vector, + DUCKDB_V2_VECTOR_TYPE *out_type, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a DATE. - * - * The payload is days since 1970-01-01, the same unit a vector of this type exposes. + * Returns the vector's logical type. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The returned logical type is the same type that was passed to vector_reference, or the type that was set on creation. + * It is caller-owned; destroy it via logical_type_destroy. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector. + * @param out_type Receives the caller-owned logical type handle. Destroy via logical_type_destroy. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_date(duckdb_v2_value_handle value, int32_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_logical_type(duckdb_v2_vector_handle vector, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIME. + * Reads a vector as a unified view of data, validity, selection, and count. * - * The payload is microseconds since midnight, the same unit a vector of this type exposes. + * Populates out_view with borrowed pointers, valid until the owning chunk is destroyed. view.data addresses the + * vector's elements, to be cast to the layout its logical type implies: a typed leaf array for the primitives, + * hugeint_t / uhugeint_t / interval_t for those kinds, bytes for VARCHAR / BLOB / BIT / BIGNUM, list_entry for LIST and + * MAP, and nothing at all for ARRAY / STRUCT / UNION, whose data lives in their children. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * DECIMAL and ENUM are typed leaf arrays too, over a storage tier that follows from the type; both mappings are + * contract. A DECIMAL of width <= 4 stores int16, <= 9 int32, <= 18 int64, and <= 38 int128, boundaries inclusive, with + * the width and scale available from logical_type_get_param. An ENUM stores its dictionary indices as uint8 for a + * dictionary of at most 255 entries, uint16 for at most 65535, and uint32 beyond that, boundaries inclusive and an + * empty dictionary uint8; logical_type_get_param resolves an index to its entry. + * + * Rejects VECTOR_TYPE_OTHER; call vector_flatten first. + * + * Reading a DICTIONARY vector has one side effect: the dictionary's underlying child is flattened in place if it is not + * already FLAT. The parent vector stays DICTIONARY, and only the child's storage shape changes, but any pointer + * previously borrowed into that child — from an earlier vector_get_view on a vector aliasing the same buffer, say — is + * invalidated. This is the only mutation vector_get_view performs; compare vector_flatten, which materializes the whole + * vector to FLAT. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to read. + * @param out_view Receives the view. Its pointers are borrowed and valid until the owning chunk is destroyed. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_view(duckdb_v2_vector_handle vector, duckdb_v2_vector_view *out_view, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIME_NS. - * - * The payload is nanoseconds since midnight, the same unit a vector of this type exposes. + * Returns the number of elements in the vector. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The size is the number of logical elements the vector represents, which for a nested kind can differ from the size of + * its children. An ARRAY vector of size 10 whose array size is 3, for instance, has 10 logical elements while its + * single child vector has 30. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector. + * @param out_size Receives the number of elements. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time_ns(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_size(duckdb_v2_vector_handle vector, idx_t *out_size, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIME_TZ. - * - * The payload packs the time of day and the UTC offset into one integer, in the committed layout: micros in the high 40 - * bits, a biased offset in the low 24. The same form a vector of this type exposes. + * Sets the number of elements in the vector. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * The counterpart of vector_get_size: it declares how many logical elements the vector now holds. It reserves enough + * space for size logical elements. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector. + * @param size The new number of logical elements. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time_tz(duckdb_v2_value_handle value, uint64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_size(duckdb_v2_vector_handle vector, idx_t size, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP. + * Reads one cell of a vector as an owned value. * - * The payload is microseconds since 1970-01-01, the same unit a vector of this type exposes. + * The total fallback reader. It reads any logical row of any vector representation without flattening — FLAT, CONSTANT, + * DICTIONARY, and the compressed kinds the view getter rejects — and covers every type kind, including those with no + * committed view layout (today VARIANT and GEOMETRY), where it is the only way to reach a cell. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Each call allocates one owned value, which makes this a single-cell bridge rather than a per-row loop primitive; use + * vector_get_view on hot paths. The row index is logical and bounds-checked against vector_get_size. The returned value + * is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to read from. + * @param row The logical row index, in [0, vector_get_size). + * @param out_value Receives the owned cell value. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_value(duckdb_v2_vector_handle vector, idx_t row, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP_SEC. + * Writes one cell of a FLAT vector from a value. * - * The payload is seconds since 1970-01-01, the same unit a vector of this type exposes. + * The total fallback writer. It covers every type kind, including those with no committed view layout (today VARIANT + * and GEOMETRY), where it is the only way to write a cell. The value is borrowed and copied in, then cast to the + * vector's type through the non-strict default cast; a cast failure surfaces from the call. A NULL value clears the + * row's validity. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Requires a FLAT vector: constant, dictionary, and compressed representations are not row-addressable, so call + * vector_flatten first. Each call performs one value write, which makes this a single-cell bridge rather than a per-row + * loop primitive; use the typed mutable-data paths on hot paths. The row index is logical and bounds-checked against + * vector_get_size. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to write to. + * @param row The logical row index, in [0, vector_get_size). + * @param value The borrowed value to write. Copied in; cast to the vector's type. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_sec(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_value(duckdb_v2_vector_handle vector, idx_t row, + duckdb_v2_value_handle value, duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP_MS. + * Returns a mutable pointer to the data of a FLAT or CONSTANT vector. * - * The payload is milliseconds since 1970-01-01, the same unit a vector of this type exposes. + * Any other representation returns ERROR_INPUT_INVALID. The pointer is valid until the owning chunk is destroyed, and + * the vector's storage shape must not change — through a flatten, say — while it is in use. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * When writing VARCHAR values, the caller must ensure that the bytes contain valid UTF-8. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector. + * @param out_data Receives a mutable pointer to the data. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_ms(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_data_mutable(duckdb_v2_vector_handle vector, void **out_data, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP_NS. - * - * The payload is nanoseconds since 1970-01-01, the same unit a vector of this type exposes. + * Forces a vector into FLAT representation, in place. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * A no-op for a vector that is already FLAT. For a CONSTANT, DICTIONARY, FSST, SEQUENCE, or SHREDDED vector it + * materializes the per-row data and switches the representation to FLAT. Call it before vector_get_view if you would + * rather not handle CONSTANT and DICTIONARY semantics in the view; it is required for any vector reporting + * VECTOR_TYPE_OTHER. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to flatten. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_ns(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_flatten(duckdb_v2_vector_handle vector, duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP_TZ. + * Casts every row of a vector into another vector, using a factory. * - * The payload is microseconds since 1970-01-01, in UTC, the same unit a vector of this type exposes. + * The conversion is the one SQL `CAST` / `TRY_CAST` performs, registered custom casts included, from the type of + * `source` to the type of `target`. It covers as many rows as `source` holds (`duckdb_v2_vector_get_size()`), of any + * representation. `target` is replaced by the result, as `duckdb_v2_vector_reference()` would, taking its size: data + * and views taken from it beforehand do not survive the call, and nothing it held before carries over. In + * CAST_MODE_NORMAL a row that does not convert fails the call with the engine's conversion error, and the contents of + * `target` are unspecified. In CAST_MODE_TRY such rows are NULL in `target` instead and the call succeeds; compare the + * validity of `source` and `target` to find them. `cast_err` then receives the conversion error of one of them, e.g. to + * report why the cast did not convert every row. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * A connection's or callback's factory casts with that client's cast functions and settings (e.g. TimeZone). An + * instance's factory supports only the built-in casts, with default settings. `source` and `target` must be different + * vectors; `target` is typically a vector of a data chunk created by the caller, or the result vector of a callback. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param factory The factory supplying the cast functions. + * @param source The vector to cast. Borrowed for the call only. + * @param target The vector to write the cast rows into. Its logical type is the target type. + * @param mode Whether a row that does not convert fails the call, or becomes NULL. + * @param out_converted Optional. Receives whether every row converted. Always true when a CAST_MODE_NORMAL cast + * succeeds. + * @param cast_err Optional. When a CAST_MODE_TRY cast succeeds without converting every row, receives an opaque info + * handle with the conversion error of one row that did not convert, which the caller must destroy via + * error_info_destroy. Otherwise receives NULL. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_tz(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_cast(duckdb_v2_factory_handle factory, duckdb_v2_vector_handle source, + duckdb_v2_vector_handle target, DUCKDB_V2_CAST_MODE mode, + bool *out_converted, duckdb_v2_error_info_handle *cast_err, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a TIMESTAMP_TZ_NS. - * - * The payload is nanoseconds since 1970-01-01, in UTC, the same unit a vector of this type exposes. + * Repoints a vector at another vector's data, without copying. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * `vector` takes on the storage of `source`: no data moves, and the two alias the same buffers until one of them is + * reset or re-referenced. Works for any type, including nested types. The source's logical type must equal the + * vector's; a mismatch returns ERROR_INPUT_INVALID. The source's data must outlive every read of `vector`. Use it to + * hand an already-materialized vector straight to an output vector without a per-row copy. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to repoint at the source's data. + * @param source The vector whose data to reference. Its logical type must equal the vector's. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_tz_ns(duckdb_v2_value_handle value, int64_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_reference(duckdb_v2_vector_handle vector, duckdb_v2_vector_handle source, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as an INTERVAL. - * - * The payload is the (months, days, micros) triple, the same unit a vector of this type exposes. + * Turns the vector into a CONSTANT vector holding the given value. * - * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. - * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. + * Afterwards the vector holds a single element that applies to every logical row; write it through + * vector_get_data_mutable, which returns a pointer to that one element. For a STRUCT vector the change propagates to + * every child. The value's logical type must equal the vector's; a mismatch returns ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the payload. + * @param vector The vector to make constant. + * @param value The value every row takes. Its logical type must equal the vector's. + * @param count The number of elements in the vector. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_interval(duckdb_v2_value_handle value, duckdb_v2_interval_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_make_constant(duckdb_v2_vector_handle vector, + duckdb_v2_value_handle value, idx_t count, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a UUID, in its internal 128-bit form. + * Sets a vector to a SEQUENCE representation. * - * The dual of value_create_uuid: the storage form a vector element holds, with the high bit flipped so the integer - * sorts, rather than the canonical byte order. A value of any other type id, and a NULL value, return - * ERROR_INPUT_INVALID. + * Afterwards the vector represents the arithmetic sequence start, start+increment, start+2*increment, ... over count + * elements. No data pointer is involved; the three parameters describe the sequence completely. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out Receives the internal 128-bit storage form. + * @param vector The vector to turn into a sequence. + * @param start The first value of the sequence. + * @param increment The step between consecutive values. + * @param count The number of elements in the sequence. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uuid(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_make_sequence(duckdb_v2_vector_handle vector, int64_t start, + int64_t increment, idx_t count, + duckdb_v2_error_info_handle *err); /*! - * Returns the value as a DECIMAL: its backing integer plus the width and scale of its type. - * - * The integer is the value scaled by 10^scale — the dual of value_create_decimal — widened to 128 bits from whatever - * storage tier the width selects. The integer getters convert, so a DECIMAL read through value_get_bigint is its - * numeric value with the fraction dropped; this one always reports storage instead, together with the scale needed to - * interpret it. A value of any other type id, and a NULL value, return ERROR_INPUT_INVALID. - * - * history: - * - stable: v2.0.0 + * Sets one row of a FLAT vector to NULL, nested children included. * - * @param value The value to read. - * @param out Receives the backing integer, scaled by 10^scale. - * @param out_width Receives the total digit count of the value's type. - * @param out_scale Receives the digits after the decimal point. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_decimal(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, - uint8_t *out_width, uint8_t *out_scale, - duckdb_v2_error_info_handle *err); - -/*! - * Unwraps the logical type carried by a TYPE value. + * Clears the row's validity and broadcasts the NULL recursively to every descendant element under that row — STRUCT + * fields, and ARRAY elements strided by the array size — maintaining DuckDB's nested NULL invariant: when a STRUCT or + * ARRAY row is NULL, every descendant element under it must be NULL too. LIST children are left untouched, since their + * consumers gate on the list's own validity. Which kinds propagate is normative DuckDB behavior that evolves with the + * type system, which makes this the total NULL write path: equivalent to vector_set_value with a NULL value, without + * the per-call value allocation. * - * Returns ERROR_INPUT_INVALID unless the value is a non-NULL TYPE value. The returned logical type is caller-owned and - * must be destroyed via logical_type_destroy. + * Requires a FLAT vector: constant, dictionary, and compressed representations are not row-addressable, so call + * vector_flatten first. The row index is logical and bounds-checked against vector_get_size. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out_type Receives the owned logical type. + * @param vector The vector to write to. + * @param row The logical row index, in [0, vector_get_size). * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_type(duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_set_null(duckdb_v2_vector_handle vector, idx_t row, + duckdb_v2_error_info_handle *err); /*! - * Creates a NULL value of the given logical type. - * - * The logical type is borrowed and copied into the value, so the caller can destroy it independently. - * - * history: - * - stable: v2.0.0 + * Returns a mutable pointer to the validity mask of a FLAT vector. * - * @param ctx The context to use for the construction. - * @param type The borrowed logical type to attach to the NULL value. - * @param out_value Receives the new NULL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_null_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a BOOLEAN value from a bool. + * Allocates the mask on first use, so the returned pointer is always non-null. Each uint64_t word covers 64 rows: bit N + * of word W is row W*64+N. A set bit means valid; a cleared bit means NULL. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * A raw mask write marks only this vector's rows. Clearing a STRUCT or ARRAY parent bit leaves the descendant elements + * untouched, which violates DuckDB's nested NULL invariant — a NULL STRUCT or ARRAY row requires NULL descendants — so + * set nested rows NULL through vector_set_null instead. When writing nested masks raw anyway, invalidate the child + * masks up front and mark elements valid as the values are written. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The bool to wrap. - * @param out_value Receives the new BOOLEAN value. + * @param vector The vector. + * @param out_validity Receives a mutable pointer to the validity mask. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bool_with_context(duckdb_v2_context_handle ctx, bool in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_flat_get_validity_mutable(duckdb_v2_vector_handle vector, + uint64_t **out_validity, + duckdb_v2_error_info_handle *err); /*! - * Creates a UTINYINT value from a uint8_t. + * Sets the validity of a CONSTANT vector. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * A constant vector holds a single element, so one flag decides whether every row reads as the value or as NULL. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The uint8_t to wrap. - * @param out_value Receives the new UTINYINT value. + * @param vector The constant vector. + * @param validity True to mark the vector's single element valid, false to mark it NULL. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint_with_context(duckdb_v2_context_handle ctx, - uint8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_constant_set_valid(duckdb_v2_vector_handle vector, bool validity, + duckdb_v2_error_info_handle *err); /*! - * Creates a USMALLINT value from a uint16_t. + * Borrows a string-backed vector's arena for writing. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Allocates the arena on first use. Valid for VARCHAR, BLOB, BIT, and BIGNUM vectors, and returns ERROR_INPUT_INVALID + * for anything else. This call is the single string-ness check, which is why arena_allocate itself performs none. The + * arena is borrowed — never destroy it — and stays valid until the vector is flattened, reallocated, or destroyed. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The uint16_t to wrap. - * @param out_value Receives the new USMALLINT value. + * @param vector The string-backed vector whose arena to borrow. + * @param out_arena Receives the borrowed arena handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint_with_context(duckdb_v2_context_handle ctx, - uint16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_arena(duckdb_v2_vector_handle vector, + duckdb_v2_arena_handle *out_arena, + duckdb_v2_error_info_handle *err); /*! - * Creates a UINTEGER value from a uint32_t. + * Returns the number of child vectors a nested vector exposes. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * 1 for LIST, MAP and ARRAY; the field count for STRUCT; member_count + 1 for UNION, the tag plus the members; and 0 + * for any non-nested kind. vector_get_child documents what each index addresses. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The uint32_t to wrap. - * @param out_value Receives the new UINTEGER value. + * @param vector The vector. + * @param out_count Receives the number of child vectors. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uint_with_context(duckdb_v2_context_handle ctx, uint32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_child_count(duckdb_v2_vector_handle vector, idx_t *out_count, + duckdb_v2_error_info_handle *err); /*! - * Creates a UBIGINT value from a uint64_t. + * Borrows a child vector by index. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * What each index addresses, by kind: LIST and ARRAY expose [0] = the elements; MAP [0] = the entries, a STRUCT(key, + * value) whose children are the keys and the values; STRUCT and TUPLE [i] = field i; and UNION [0] = the tag with + * [1..N] = the members. Note that value_get_child diverges on UNION, exposing only the active member. + * + * The returned child is borrowed and lives as long as the owning chunk. Returns ERROR_INPUT_INVALID if the vector has + * no children, or if the index is out of range. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The uint64_t to wrap. - * @param out_value Receives the new UBIGINT value. + * @param vector The nested vector to descend into. + * @param index The child index, in [0, vector_get_child_count). + * @param out_child Receives the borrowed child vector. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint_with_context(duckdb_v2_context_handle ctx, - uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_vector_get_child(duckdb_v2_vector_handle vector, idx_t index, + duckdb_v2_vector_handle *out_child, + duckdb_v2_error_info_handle *err); /*! - * Creates a UHUGEINT value from a uint128_t. + * Decodes BIGNUM storage bytes into a caller-supplied magnitude buffer + sign flag. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * in_data and in_length are the value's raw storage bytes, exactly as they sit in a vector's bignum payload or as + * value_get_blob reports them for a BIGNUM value. Those bytes are opaque: the storage encoding is not a committed part + * of this API, and this function is the only thing that interprets them. * - * history: - * - stable: v2.0.0 + * Writes the big-endian magnitude bytes into out_data and the sign flag into out_is_negative; reconstruct the integer + * as (-1)**out_is_negative * unsigned_big_endian(out_data[0..out_length]). Materializing the bytes is unavoidable, + * since a negative bignum stores its magnitude bit-inverted, but the buffer belongs to the caller: this function never + * allocates and hands back nothing to free. * - * @param ctx The context to use for the construction. - * @param in_value The uint128_t to wrap. - * @param out_value Receives the new UHUGEINT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_uhugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a TINYINT value from an int8_t. + * Pass out_data = NULL to size the buffer without decoding. out_length then receives the exact number of magnitude + * bytes and out_is_negative the sign, and out_capacity is ignored. The size comes straight out of the storage header, + * so the query is O(1) and exact — a size call followed by a decode call never needs a retry loop. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * With out_data != NULL, out_capacity must be at least out_length bytes, or the call returns ERROR_INPUT_OBJECT_SIZE + * with out_length set to the required size and out_data left untouched. On success exactly out_length bytes are + * written, and the rest of the buffer is not modified. + * + * Returns ERROR_INPUT_INVALID if in_data is NULL, or if in_length is too short to hold a bignum header. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The int8_t to wrap. - * @param out_value Receives the new TINYINT value. + * @param in_data The value's raw BIGNUM storage bytes. + * @param in_length Size of in_data in bytes. + * @param out_data Caller-owned buffer receiving the magnitude bytes, or NULL to only report the required size in + * out_length. + * @param out_capacity Bytes available in out_data. Ignored when out_data is NULL. + * @param out_length Receives the magnitude size in bytes, written on success and on ERROR_INPUT_OBJECT_SIZE. + * @param out_is_negative Receives true if the value is negative, false otherwise. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint_with_context(duckdb_v2_context_handle ctx, int8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_bignum_decode(const uint8_t *in_data, idx_t in_length, uint8_t *out_data, + idx_t out_capacity, idx_t *out_length, bool *out_is_negative, + duckdb_v2_error_info_handle *err); /*! - * Creates a SMALLINT value from an int16_t. + * Encodes a magnitude + sign flag into BIGNUM storage bytes. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * The inverse of bignum_decode. in_data and in_length are the big-endian magnitude bytes and is_negative the sign; + * out_data receives the raw storage bytes for that integer, in the form a BIGNUM vector payload expects. Those bytes + * are opaque: the storage encoding is not a committed part of this API. * - * history: - * - stable: v2.0.0 + * Pass out_data = NULL to size the buffer without encoding. out_length then receives the exact number of storage bytes, + * and out_capacity is ignored. The size is a fixed function of in_length, so the query is O(1) and exact. * - * @param ctx The context to use for the construction. - * @param in_value The int16_t to wrap. - * @param out_value Receives the new SMALLINT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_smallint_with_context(duckdb_v2_context_handle ctx, - int16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - -/*! - * Creates an INTEGER value from an int32_t. + * With out_data != NULL, out_capacity must be at least out_length bytes, or the call returns ERROR_INPUT_OBJECT_SIZE + * with out_length set to the required size and out_data left untouched. On success exactly out_length bytes are + * written, and the rest of the buffer is not modified. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * The magnitude must be canonical, matching what bignum_decode produces: in_length >= 1 with no leading zero bytes, so + * the value zero is the single byte 0x00 with is_negative = false. A NULL in_data, an in_length of 0, a leading zero + * byte, or a negative zero returns ERROR_INPUT_INVALID; a magnitude beyond the maximum bignum width returns + * ERROR_INPUT_OUT_OF_RANGE. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The int32_t to wrap. - * @param out_value Receives the new INTEGER value. + * @param in_data Big-endian magnitude bytes, with no leading zero bytes. + * @param in_length Size of in_data in bytes. Must be >= 1. + * @param is_negative True to encode a negative value. + * @param out_data Caller-owned buffer receiving the storage bytes, or NULL to only report the required size in + * out_length. + * @param out_capacity Bytes available in out_data. Ignored when out_data is NULL. + * @param out_length Receives the storage size in bytes — written on success and on ERROR_INPUT_OBJECT_SIZE. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_int_with_context(duckdb_v2_context_handle ctx, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_bignum_encode(const uint8_t *in_data, idx_t in_length, bool is_negative, + uint8_t *out_data, idx_t out_capacity, idx_t *out_length, + duckdb_v2_error_info_handle *err); /*! - * Creates a BIGINT value from an int64_t. + * Validates all text->len bytes as UTF-8, including bytes after embedded NUL characters. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Returns ERROR_INPUT_INVALID if any of: + * - text is NULL. + * - text->ptr is NULL and text->len is nonzero. + * - The input contains malformed UTF-8. + * + * A view with a NULL ptr and zero length is valid. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The int64_t to wrap. - * @param out_value Receives the new BIGINT value. + * @param text The bytes to validate. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bigint_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_validate_utf8(const duckdb_v2_str *text, duckdb_v2_error_info_handle *err); -/*! - * Creates a HUGEINT value from an int128_t. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param ctx The context to use for the construction. - * @param in_value The int128_t to wrap. - * @param out_value Receives the new HUGEINT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR +/* --- Struct definitions for vector --- */ + +struct duckdb_v2_vector_view { + const void *data; + const uint64_t *validity; + const duckdb_v2_sel_t *sel; + idx_t count; +}; + +struct duckdb_v2_list_entry { + idx_t offset; + idx_t length; +}; + +struct duckdb_v2_hugeint_t { + uint64_t lower; + int64_t upper; +}; + +struct duckdb_v2_uhugeint_t { + uint64_t lower; + uint64_t upper; +}; + +struct duckdb_v2_interval_t { + int32_t months; + int32_t days; + int64_t micros; +}; + +/* ============================================================================ + * MODULE: prepared_statement + * ============================================================================ */ + +/* --- Enums for prepared_statement --- */ + +/* --- Struct forward declarations for prepared_statement --- */ + +/* --- Types for prepared_statement --- */ + +/*! + * An owned handle to a statement bound and planned once, executable repeatedly via + * `duckdb_v2_prepared_statement_execute()`. Construct with `duckdb_v2_prepared_statement_create()` and destroy with + * `duckdb_v2_prepared_statement_destroy()`. It keeps its connection's session alive, so it stays usable across + * executions and even after the connection is disconnected. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_prepared_statement { + void *internal_ptr; +} * duckdb_v2_prepared_statement_handle; + +/* --- Constants for prepared_statement --- */ + +/* --- Function pointer typedefs for prepared_statement --- */ + +/* --- Functions for prepared_statement --- */ /*! - * Creates a VARCHAR value from a UTF-8 string. + * Prepares a parsed statement into a reusable handle. Non-consuming. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Copies the statement's AST, then binds and plans it once. Binder and catalog errors surface here, exactly as they + * would from `duckdb_v2_statement_execute()`. The statement is borrowed rather than consumed, since a copy is what gets + * prepared, so it can be prepared again or executed directly; the caller destroys it with + * `duckdb_v2_sql_statement_destroy()`. + * + * By default this succeeds for any preparable statement, whether or not its plan will be reused; ask + * `duckdb_v2_prepared_statement_reuses_plan()` which one you got. Setting `require_cacheable` instead fails with + * `ERROR_INPUT_INVALID` when the plan would not be reused, so a caller who wants the handle only for the speedup finds + * out here rather than after silently taking the slow path. + * + * Refuses with `ERROR_RESOURCE_IN_USE` while a statement on the connection is still running. Drain, destroy, or + * interrupt that result first, or prepare on another connection. `*out_prepared` is set to NULL on failure. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The UTF-8 string to wrap. May be null only when len is 0 (empty string). - * @param out_value Receives the new VARCHAR value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param conn The connection supplying the catalog, transaction, and parser state. The prepared statement belongs to + * it. + * @param statement The statement to prepare. Borrowed and copied, not consumed; destroy it with + * `duckdb_v2_sql_statement_destroy()`. + * @param require_cacheable When true, fail with `ERROR_INPUT_INVALID` unless the prepared plan will be reused across + * executions, as `duckdb_v2_prepared_statement_reuses_plan()` would report it. + * @param out_prepared On success, receives the new prepared statement. Owned by the caller; destroy via + * `duckdb_v2_prepared_statement_destroy()`. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_varchar_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_create(duckdb_v2_connection_handle conn, + duckdb_v2_sql_statement_handle statement, + bool require_cacheable, + duckdb_v2_prepared_statement_handle *out_prepared, + duckdb_v2_error_info_handle *err); /*! - * Creates a BLOB value from a byte string. + * Executes a prepared statement, streaming its result. Non-consuming. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Returns a result without executing anything: execution happens incrementally as the result is stepped or drained, + * exactly as with `duckdb_v2_statement_execute()`. The handle returned is an ordinary result, with identical behaviour + * throughout -- streaming, draining, the changed-row count of a DML statement, the output schema, the statement type, + * and the result type. * - * history: - * - stable: v2.0.0 + * `parameter_values` binds the statement's parameters as constants for this execution. Binding is positional by default + * ($1 = element 0); supply `parameter_names` to bind by name instead, where a non-empty entry binds its value to that + * named parameter ($name, matched case-insensitively) and a {NULL, 0} entry stays positional. Pass NULL for both + * arrays, or a count of 0, for a statement without parameters. Both arrays are borrowed and copied in, so the caller + * still owns and destroys them. A key set that does not match the statement's parameters is rejected with + * `ERROR_INPUT_INVALID`, with one exception: a named parameter left without a value reads the session variable of the + * same name (`SET VARIABLE`) when one exists. * - * @param ctx The context to use for the construction. - * @param in_value The byte string to wrap. May be null only when len is 0 (empty string). - * @param out_value Receives the new BLOB value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_blob_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); - -/*! - * Creates a FLOAT value from a float. + * Not consumed: execute the same handle again, with the same values or different ones, as often as you like. Values are + * bound per execution and nothing carries over between them. A catalog change since the statement was prepared, or a + * parameter type that differs from the one the cached plan assumed, triggers a re-bind that is invisible apart from its + * cost. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Refuses with `ERROR_RESOURCE_IN_USE` while a statement on the connection is still running; drain, destroy, or + * interrupt that result first, or execute on another connection. A failed execution, at any stage, leaves the prepared + * statement usable. `*out_result` is set to NULL on failure. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The float to wrap. - * @param out_value Receives the new FLOAT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param prepared The prepared statement to execute. Borrowed; not consumed. + * @param parameter_names Optional. An array of `parameter_count` parameter names; a non-empty entry binds its value to + * the named parameter ($name, case-insensitive), a {NULL, 0} entry keeps it positional ($1 = element 0). Pass NULL to + * bind everything positionally. + * @param parameter_values Optional. An array of `parameter_count` values. Each binds by name when `parameter_names` + * supplies one, and positionally ($1 = element 0) otherwise. Borrowed and copied in. Pass NULL for a statement without + * parameters. + * @param parameter_count The number of entries in `parameter_names` and `parameter_values`. Pass 0 for a statement + * without parameters. + * @param out_result On success, receives the new result. Owned by the caller; destroy via `duckdb_v2_result_destroy()`. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_float_with_context(duckdb_v2_context_handle ctx, float in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_execute(duckdb_v2_prepared_statement_handle prepared, + const duckdb_v2_identifier_t *parameter_names, + const duckdb_v2_value_handle *parameter_values, + idx_t parameter_count, + duckdb_v2_result_handle *out_result, + duckdb_v2_error_info_handle *err); /*! - * Creates a DOUBLE value from a double. + * Reports whether the prepared statement reuses its compiled plan across executions. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * True when executions reuse the plan built at prepare time, provided the supplied values match the planned parameter + * types and nothing the plan depends on has changed; false when the statement re-binds on every execution, making it no + * faster than `duckdb_v2_statement_execute()`. A plan is reused only when all parameter types were resolved at prepare + * time and the plan is cacheable: `SELECT 42` reuses, `SELECT $1::INTEGER + 1` reuses, a statement reading a base table + * does not (it re-binds so a catalog change is picked up), and `SELECT $1 + $2` does not (the types are unknown until + * values arrive). + * + * A static property of the built plan, fixed when the statement was prepared and independent of the values later passed + * to `duckdb_v2_prepared_statement_execute()`. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The double to wrap. - * @param out_value Receives the new DOUBLE value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param prepared The prepared statement to inspect. + * @param out_reuses Receives true when the compiled plan is reused across executions, false when the statement re-binds + * each time. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_double_with_context(duckdb_v2_context_handle ctx, double in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_reuses_plan(duckdb_v2_prepared_statement_handle prepared, + bool *out_reuses, + duckdb_v2_error_info_handle *err); /*! - * Creates a TYPE value from a logical type. + * Destroys a prepared statement. * - * The logical type is borrowed and copied into the value, so the caller can destroy it independently. + * Null-safe: passing NULL, or a slot already set to NULL, is a no-op. A result produced by + * `duckdb_v2_prepared_statement_execute()` is independently owned and keeps the session alive itself, so the prepared + * statement may be destroyed while results made from it are still live. On success the slot is set to NULL. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_type The borrowed logical type to wrap. - * @param out_value Receives the new TYPE value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param prepared The prepared statement to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_type_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle in_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_destroy(duckdb_v2_prepared_statement_handle *prepared); + +/* --- Struct definitions for prepared_statement --- */ + +/* ============================================================================ + * MODULE: query_result + * ============================================================================ */ + +/* --- Enums for query_result --- */ /*! - * Creates a NULL value of the given logical type. + * Shape of a query result. QUERY_RESULT carries rows and columns; CHANGED_ROWS carries an affected row count, as an + * INSERT, UPDATE, or DELETE without RETURNING produces; NOTHING covers DDL and other statements with no row output. + */ +typedef enum DUCKDB_V2_RESULT_TYPE { + DUCKDB_V2_RESULT_TYPE_QUERY_RESULT = 0, + DUCKDB_V2_RESULT_TYPE_CHANGED_ROWS = 1, + DUCKDB_V2_RESULT_TYPE_NOTHING = 2, + DUCKDB_V2_RESULT_TYPE_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_RESULT_TYPE; + +/*! + * Outcome of a result_step call. WAITING is the 0-value, so a zero-initialized out-param reads as "no work product yet" + * rather than CHUNK, the same convention VECTOR_TYPE_OTHER follows. The four states are the ones a consumer acts on; + * they are deliberately not a projection of any internal enum. + */ +typedef enum DUCKDB_V2_RESULT_STEP_STATUS { + //! No chunk yet; step again, or block in result_wait. + DUCKDB_V2_RESULT_STEP_STATUS_WAITING = 0, + + //! A caller-owned chunk was written to *out_chunk, or an array to *out_array. + DUCKDB_V2_RESULT_STEP_STATUS_CHUNK = 1, + + //! Stream exhausted. Sticky. + DUCKDB_V2_RESULT_STEP_STATUS_FINISHED = 2, + + //! Query was interrupted. Sticky. + DUCKDB_V2_RESULT_STEP_STATUS_CANCELLED = 3, + DUCKDB_V2_RESULT_STEP_STATUS_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_RESULT_STEP_STATUS; + +/* --- Struct forward declarations for query_result --- */ + +/* --- Types for query_result --- */ + +/* --- Constants for query_result --- */ + +/* --- Function pointer typedefs for query_result --- */ + +/* --- Functions for query_result --- */ + +/*! + * Executes a parsed statement on the connection, streaming its result. Non-consuming. * - * The logical type is borrowed and copied into the value, so the caller can destroy it independently. + * Takes a statement produced by the sql_statement module (parse_sql / statement_iterator_next), preprocesses and + * prepares it, and returns a result handle without executing anything: execution happens incrementally as the result is + * stepped (result_step) or drained (result_fetch_chunk). This call reports only the errors detectable at prepare time — + * binder, catalog, pragma preprocessing — while errors raised during execution surface from the stepping functions. + * + * The statement is borrowed, not consumed, since a copy is what executes. It can be executed again, for example with a + * different set of values, and the caller destroys it with sql_statement_destroy. + * + * parameter_values binds the statement's parameters as constants for this execution. Binding is positional by default: + * the i-th value binds $(i+1), matching SQL's own convention, so dense $1..$N and ? placeholders work directly. Supply + * parameter_names to bind by name instead — a non-empty name binds its value to that named parameter ($name, matched + * case-insensitively), while a {NULL, 0} entry leaves that value positional. The parameter schema from statement_bind + * lists the names to use. Both arrays are borrowed and copied in; the caller still owns and destroys them. Pass NULL + * for both, or a count of 0, for an unparameterized statement. A key set that does not match the statement's parameters + * — names for a positional statement, or the reverse — is rejected with ERROR_INPUT_INVALID, and named and positional + * parameters cannot be mixed within one statement. Parameters and statement expansion are mutually exclusive: passing + * values for a statement that preprocesses into a group is rejected with ERROR_INPUT_INVALID. + * + * Preprocessing can expand one statement into a group — a dynamic PIVOT, or statement-expanding DDL such as ALTER ... + * ADD COLUMN with a non-constant DEFAULT. The group executes as one result through the same steps, and the stream + * surfaces the first row-producing statement of the group, or the last statement when none produces rows. An expansion + * with more than one row-producing statement cannot be streamed as a single result and reports + * ERROR_QUERY_NOT_IMPLEMENTED; no known expansion produces one. + * + * One running statement per connection: this refuses with ERROR_RESOURCE_IN_USE while a statement on the connection is + * still running. Drain, destroy, or interrupt that result first, or open another connection. + * + * Schema metadata — result type, statement type, column count, names, logical types — is available on the returned + * handle immediately, before the first step. + * + * *out_result is set to nullptr on failure. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param type The borrowed logical type to attach to the NULL value. - * @param out_value Receives the new NULL value. + * @param conn The connection on which to execute the statement. + * @param statement The statement to execute. Borrowed and copied; not consumed. Destroy it with sql_statement_destroy. + * @param parameter_names Optional. An array of parameter_count parameter names; a non-empty entry binds its value to + * the named parameter ($name, case-insensitive), a {NULL, 0} entry keeps it positional ($1 = element 0). Pass NULL for + * all-positional binding. Named and positional parameters cannot be mixed within one statement. + * @param parameter_values Optional. An array of parameter_count value handles. Each binds by name when parameter_names + * supplies one, otherwise positionally ($1 = element 0). Borrowed (copied in). Pass NULL for an unparameterized + * statement. + * @param parameter_count The number of parameters in parameter_names and parameter_values. Pass 0 for an + * unparameterized statement. + * @param out_result Receives the new result handle. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_null_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_execute(duckdb_v2_connection_handle conn, + duckdb_v2_sql_statement_handle statement, + const duckdb_v2_identifier_t *parameter_names, + const duckdb_v2_value_handle *parameter_values, + idx_t parameter_count, duckdb_v2_result_handle *out_result, + duckdb_v2_error_info_handle *err); /*! - * Creates a BOOLEAN value from a bool. + * Destroys a result handle. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. Frees the memory the result owns and releases + * the connection for its next query. Safe at any point in the stream's life. Destroying a result before its statement + * ended (drained, or stepped to FINISHED) aborts the statement. On autocommit, its writes are rolled back. Inside a + * transaction, a statement that may write invalidates the transaction, and a read-only statement just stops. Chunks + * already fetched are caller-owned and stay valid. On success the slot is set to nullptr. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The bool to wrap. - * @param out_value Receives the new BOOLEAN value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param result The result to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bool_with_connection(duckdb_v2_connection_handle conn, - bool in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_destroy(duckdb_v2_result_handle *result); /*! - * Creates a UTINYINT value from a uint8_t. + * Runs one bounded unit of query execution and returns without blocking. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * The streaming primitive: a step does a bounded amount of execution work, mostly without blocking, and returns control + * to the caller, so an event loop stays responsive and can interleave other work between steps. The conveniences — + * result_wait, result_fetch_chunk, result_drain — block, and are for synchronous callers. out_status reports the + * outcome: + * + * - CHUNK: *out_chunk receives a caller-owned chunk (destroy via data_chunk_destroy). Written only for this status; + * nullptr otherwise. + * - WAITING: no chunk yet, but work was done. Transient: keep stepping and it resolves to CHUNK, FINISHED, CANCELLED, + * or an error. Block in result_wait rather than busy-stepping. + * - FINISHED: stream exhausted. Sticky. + * - CANCELLED: query interrupted via connection_interrupt. Sticky. Cancellation is a status here, not an error; + * result_fetch_chunk, which has no status out-param, reports it as ERROR_RUNTIME_INTERRUPT. + * + * Execution errors come back as the return code plus err, never as a status; out_status is then unspecified and + * *out_chunk is nullptr. Errors are sticky, so later steps report the same code. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The uint8_t to wrap. - * @param out_value Receives the new UTINYINT value. + * @param result The result to step. + * @param out_chunk Receives an owned chunk if *out_status is CHUNK; set to nullptr otherwise. + * @param out_status Receives the step status. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint_with_connection(duckdb_v2_connection_handle conn, - uint8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_step(duckdb_v2_result_handle result, + duckdb_v2_data_chunk_handle *out_chunk, + DUCKDB_V2_RESULT_STEP_STATUS *out_status, + duckdb_v2_error_info_handle *err); /*! - * Creates a USMALLINT value from a uint16_t. + * Blocks until the next chunk is available and returns it. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * A convenience over result_step: blocks until a chunk is produced or the stream ends. On success *out_chunk receives a + * caller-owned chunk (destroy via data_chunk_destroy), or nullptr at end-of-stream. End-of-stream is sticky, so later + * calls keep succeeding with *out_chunk set to nullptr. + * + * An interrupted query returns ERROR_RUNTIME_INTERRUPT — the same event result_step reports as status CANCELLED, + * carried on the error channel because this function has no status out-param. + * + * On failure *out_chunk is set to nullptr. Errors are sticky, so later calls report the same code. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The uint16_t to wrap. - * @param out_value Receives the new USMALLINT value. + * @param result The result to fetch from. + * @param out_chunk Receives an owned chunk, or nullptr at end-of-stream. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint_with_connection(duckdb_v2_connection_handle conn, - uint16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_fetch_chunk(duckdb_v2_result_handle result, + duckdb_v2_data_chunk_handle *out_chunk, + duckdb_v2_error_info_handle *err); /*! - * Creates a UINTEGER value from a uint32_t. + * Blocks until result_step can make progress. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * A convenience over result_step: blocks until a step is worth issuing again, that is, until a unit of execution work + * can run on the calling thread. Never produces or consumes chunks. Waiting on a terminal result — FINISHED, CANCELLED, + * or a sticky error — returns immediately; it is a no-op, never an error. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The uint32_t to wrap. - * @param out_value Receives the new UINTEGER value. + * @param result The result to wait on. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uint_with_connection(duckdb_v2_connection_handle conn, - uint32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_wait(duckdb_v2_result_handle result, duckdb_v2_error_info_handle *err); /*! - * Creates a UBIGINT value from a uint64_t. + * Renders the result as a box table, consuming it. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Drains the result into a column data collection and renders it with the same renderer the DuckDB CLI uses, so every + * client displays results identically without reimplementing table formatting. The result is consumed by transfer: the + * slot is set to NULL on success and on failure alike. A partially consumed result is accepted, and the remainder is + * what gets rendered. + * + * The whole remaining result materializes in memory before rendering. max_rows bounds what is DISPLAYED, not what is + * read, so with limit 0 the footer's row count is exact. A caller who cannot afford full materialization should bound + * the query itself (e.g. LIMIT n) and pass n as limit; the footer then renders "? rows" whenever the result fills that + * bound, since the true total is unknown at that point. + * + * Zero selects the renderer default for each sizing knob: max_rows 20, max_width the probed terminal width or 80 when + * that is unavailable, max_col_width 20. An empty null_value renders NULL cells as the default "NULL" text. + * + * The rendered text goes to sink rather than being returned: a box can be large, and this way nothing allocates a + * buffer the caller has to free. The sink is called exactly once with the whole box, so its view carries the full + * length — write it straight to a stream, or copy it into your own string. sink must not be NULL. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The uint64_t to wrap. - * @param out_value Receives the new UBIGINT value. + * @param result The result to render; consumed and set to NULL. Left intact only when the call rejects null arguments. + * @param max_rows Maximum rows displayed; 0 selects the renderer default (20). + * @param max_width Maximum total width in characters; 0 selects the renderer default. + * @param max_col_width Maximum width of one column; 0 selects the renderer default (20). + * @param null_value Text rendered for NULL cells; empty selects "NULL". + * @param render_mode 0 renders rows (records down the page); 1 renders columns; other values are rejected with + * INVALID_INPUT. + * @param limit The row limit the caller applied to the query before rendering; 0 means none. When the materialized + * result holds exactly this many rows the true total is unknown, so the footer renders "? rows" instead of an exact + * count. + * @param sink Receives the whole rendered box in a single call. Borrowed for the duration of that call; see text_sink + * for the full contract. + * @param user_data Opaque pointer passed through to sink untouched. May be NULL. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint_with_connection(duckdb_v2_connection_handle conn, - uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_render_box(duckdb_v2_result_handle *result, idx_t max_rows, + idx_t max_width, idx_t max_col_width, + const duckdb_v2_str *null_value, idx_t render_mode, + idx_t limit, duckdb_v2_text_sink_fn sink, void *user_data, + duckdb_v2_error_info_handle *err); /*! - * Creates a UHUGEINT value from a uint128_t. + * Runs the result to completion and reports the changed-row count. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * A convenience over result_step: blocks until the stream is fully consumed, so every side effect is applied. Rows of a + * row-producing result are discarded. For a CHANGED_ROWS result *out_rows_changed receives the affected row count; for + * every other result type, and for a stream whose Count chunk was already consumed, it receives 0. + * + * The result type (result_get_result_type) is prepare-time metadata, so a caller can choose between consuming rows and + * draining without inspecting the SQL. Draining an already FINISHED result succeeds. Cancellation surfaces as + * ERROR_RUNTIME_INTERRUPT; errors are sticky, and on failure *out_rows_changed is unspecified. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The uint128_t to wrap. - * @param out_value Receives the new UHUGEINT value. + * @param result The result to drain. + * @param out_rows_changed Receives the affected row count for CHANGED_ROWS results, 0 otherwise. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_uhugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_drain(duckdb_v2_result_handle result, idx_t *out_rows_changed, + duckdb_v2_error_info_handle *err); /*! - * Creates a TINYINT value from an int8_t. + * Returns the shape of the result: query, changed rows, or nothing. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * QUERY_RESULT for statements that produce rows (SELECT, RETURNING, EXPLAIN), CHANGED_ROWS for an INSERT, UPDATE, or + * DELETE without RETURNING, NOTHING for DDL and other statements with no row output. + * + * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a + * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The int8_t to wrap. - * @param out_value Receives the new TINYINT value. + * @param result The result. + * @param out_type Receives the result shape. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint_with_connection(duckdb_v2_connection_handle conn, - int8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_result_type(duckdb_v2_result_handle result, + DUCKDB_V2_RESULT_TYPE *out_type, + duckdb_v2_error_info_handle *err); /*! - * Creates a SMALLINT value from an int16_t. + * Returns the SQL statement type that produced the result. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a + * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The int16_t to wrap. - * @param out_value Receives the new SMALLINT value. + * @param result The result. + * @param out_type Receives the statement type. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_smallint_with_connection(duckdb_v2_connection_handle conn, - int16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_statement_type(duckdb_v2_result_handle result, + DUCKDB_V2_STATEMENT_TYPE *out_type, + duckdb_v2_error_info_handle *err); /*! - * Creates an INTEGER value from an int32_t. + * Returns the result's output schema as a single schema handle. * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. + * Builds an owned schema of the result's column names and types into *out_schema; destroy it via schema_destroy. + * Prepare-time metadata, available before the first step, or for an expanding statement once stepping has prepared the + * row-producing fragment. Never empty: a non-SELECT reports a single status column, either a BIGINT changed-rows count + * or a BOOLEAN success. + * + * This schema is the authoritative source for the column types of the chunks the result produces; vectors do not carry + * their own type. + * + * *out_schema is set to NULL on failure. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The int32_t to wrap. - * @param out_value Receives the new INTEGER value. + * @param result The result. + * @param out_schema Receives the owned output schema. Destroy via schema_destroy. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_int_with_connection(duckdb_v2_connection_handle conn, - int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_schema(duckdb_v2_result_handle result, + duckdb_v2_schema_handle *out_schema, + duckdb_v2_error_info_handle *err); + +/* --- Struct definitions for query_result --- */ + +/* ============================================================================ + * MODULE: table + * ============================================================================ */ + +/* --- Enums for table --- */ + +/*! + * Whether a table function's scan is partitioned by a given set of columns, and how. Reported by + * `duckdb_v2_table_function_set_partitioning_callback()` via + * `duckdb_v2_table_function_partitioning_set_partition_info()`. + */ +typedef enum DUCKDB_V2_TABLE_PARTITION_INFO { + //! The scan is not known to be partitioned by the requested columns. + DUCKDB_V2_TABLE_PARTITION_INFO_NOT_PARTITIONED = 0, + + //! Each partition the scan produces carries exactly one distinct value for the requested columns. + DUCKDB_V2_TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS = 1, + + //! The partitions the scan produces overlap only at their boundaries. + DUCKDB_V2_TABLE_PARTITION_INFO_OVERLAPPING_PARTITIONS = 2, + + //! The partitions the scan produces are disjoint ranges. + DUCKDB_V2_TABLE_PARTITION_INFO_DISJOINT_PARTITIONS = 3, + DUCKDB_V2_TABLE_PARTITION_INFO_MAX_ENUM = 0x7FFFFFFF, +} DUCKDB_V2_TABLE_PARTITION_INFO; + +/* --- Struct forward declarations for table --- */ + +/* --- Types for table --- */ + +/*! + * An owned opaque handle to a custom table function being built. Created with `duckdb_v2_table_function_create()`, + * configured with the setter functions (e.g. `duckdb_v2_table_function_set_name()`, + * `duckdb_v2_table_function_set_exec_callback()`, etc.) and the signature obtained via + * `duckdb_v2_table_function_get_signature()`, made available with `duckdb_v2_connection_register_table_function()` or + * `duckdb_v2_extension_register_table_function()`, and destroyed with `duckdb_v2_table_function_destroy()`. + */ +typedef struct _duckdb_v2_table_function { + void *internal_ptr; +} * duckdb_v2_table_function_handle; + +/*! + * A borrowed opaque handle to the result of a table function's "bind" phase. The "bind" callback receives this handle + * next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments, the user data and the bind + * data. It must use this handle to declare the columns the function returns, and can use it to hint at the number of + * rows the scan will produce. + */ +typedef struct _duckdb_v2_table_function_bind_info { + void *internal_ptr; +} * duckdb_v2_table_function_bind_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a table function during the global state initialization "init + * global" phase. The "init global" callback receives this handle and can use it to e.g. set up the state shared by + * every thread scanning the function, and to declare how many threads may scan it. + */ +typedef struct _duckdb_v2_table_function_init_global_info { + void *internal_ptr; +} * duckdb_v2_table_function_init_global_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a table function during the local state initialization "init + * local" phase. The "init local" callback receives this handle and can use it to e.g. set up worker-local state, + * typically by claiming work from the shared global state. + */ +typedef struct _duckdb_v2_table_function_init_local_info { + void *internal_ptr; +} * duckdb_v2_table_function_init_local_info_handle; + +/*! + * A borrowed opaque handle to the arguments supplied to a table function during the execution "exec" phase. The "exec" + * callback receives this handle and can use it to e.g. access the global and local state and write the next batch of + * rows to the output chunk. + */ +typedef struct _duckdb_v2_table_function_exec_info { + void *internal_ptr; +} * duckdb_v2_table_function_exec_info_handle; /*! - * Creates a BIGINT value from an int64_t. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The int64_t to wrap. - * @param out_value Receives the new BIGINT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function during the "progress" phase. The "progress" + * callback receives this handle and can use it to report how far the scan has advanced. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bigint_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_progress_info { + void *internal_ptr; +} * duckdb_v2_table_function_progress_info_handle; /*! - * Creates a HUGEINT value from an int128_t. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The int128_t to wrap. - * @param out_value Receives the new HUGEINT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function during the "filter pushdown" phase of query + * optimization. The "filter pushdown" callback receives this handle and can use it to inspect the filter predicates the + * query applies to the function's rows, and to accept the ones it will apply itself. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_filter_pushdown_info { + void *internal_ptr; +} * duckdb_v2_table_function_filter_pushdown_info_handle; /*! - * Creates a VARCHAR value from a UTF-8 string. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The UTF-8 string to wrap. May be null only when len is 0 (empty string). - * @param out_value Receives the new VARCHAR value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function during the "partition data" phase. The engine + * invokes the callback on the worker thread that just produced a batch of rows, once per batch, only when a downstream + * operator needs to know the batch's ordering position, the values of a set of partitioning columns, or both; the + * handle reports which of those were actually requested and receives the answer. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_varchar_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_partition_data_info { + void *internal_ptr; +} * duckdb_v2_table_function_partition_data_info_handle; /*! - * Creates a BLOB value from a byte string. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The byte string to wrap. May be null only when len is 0 (empty string). - * @param out_value Receives the new BLOB value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function during the "partitioning" phase of query + * optimization. The engine invokes the callback once per candidate `GROUP BY` column set, before execution starts, to + * decide whether the scan can feed a partitioned aggregate directly instead of hashing. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_blob_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_partitioning_info { + void *internal_ptr; +} * duckdb_v2_table_function_partitioning_info_handle; /*! - * Creates a FLOAT value from a float. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The float to wrap. - * @param out_value Receives the new FLOAT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function when a scanning thread claims its next batch + * of work. The "claim batch" callback receives this handle and can use it to claim the next part of the scan from the + * shared global state for the thread's local state, and to report whether it claimed one. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_float_with_connection(duckdb_v2_connection_handle conn, - float in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_claim_batch_info { + void *internal_ptr; +} * duckdb_v2_table_function_claim_batch_info_handle; /*! - * Creates a DOUBLE value from a double. - * - * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param in_value The double to wrap. - * @param out_value Receives the new DOUBLE value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * A borrowed opaque handle to the arguments supplied to a table function when the engine asks it to describe a bound + * call. The "get bind info" callback receives this handle, and can use it to describe the call in more detail than its + * columns: with identifiers of its columns (e.g. field ids), and with options - key-value facts about what the call + * reads. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_double_with_connection(duckdb_v2_connection_handle conn, - double in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_table_function_get_bind_info_info { + void *internal_ptr; +} * duckdb_v2_table_function_get_bind_info_info_handle; /*! - * Creates a TYPE value wrapping the given logical type. - * - * The input logical type is borrowed; the value internally copies the type so the caller can destroy the logical type - * independently. - * - * history: - * - stable: v2.0.0 - * - * @param conn The connection to use for the construction. - * @param type The borrowed logical type to wrap. - * @param out_value Receives the new value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR + * An owned opaque handle to a multi-file table function being built. A multi-file function wraps an already registered + * table function that reads a single file, and adds everything that is needed to read many files at once on top of it: + * globbing, lists of files, hive partitioning, the `filename` column, `union_by_name` and the like. Created with + * `duckdb_v2_multi_file_function_create()`, configured with the setter functions (e.g. + * `duckdb_v2_multi_file_function_set_name()`, `duckdb_v2_multi_file_function_set_single_file_function()`), made + * available with `duckdb_v2_connection_register_multi_file_function()` or + * `duckdb_v2_extension_register_multi_file_function()`, and destroyed with `duckdb_v2_multi_file_function_destroy()`. */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_type_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +typedef struct _duckdb_v2_multi_file_function { + void *internal_ptr; +} * duckdb_v2_multi_file_function_handle; + +/* --- Constants for table --- */ + +/* --- Function pointer typedefs for table --- */ + +typedef void (*duckdb_v2_table_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, + duckdb_v2_table_function_bind_info_handle result, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_init_global_callback_fn)(duckdb_v2_table_function_init_global_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_init_local_callback_fn)(duckdb_v2_table_function_init_local_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_exec_callback_fn)(duckdb_v2_table_function_exec_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_progress_callback_fn)(duckdb_v2_table_function_progress_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_filter_pushdown_callback_fn)( + duckdb_v2_table_function_filter_pushdown_info_handle info, duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_partition_data_callback_fn)( + duckdb_v2_table_function_partition_data_info_handle info, duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_partitioning_callback_fn)( + duckdb_v2_table_function_partitioning_info_handle info, duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_claim_batch_callback_fn)(duckdb_v2_table_function_claim_batch_info_handle info, + duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +typedef void (*duckdb_v2_table_function_get_bind_info_callback_fn)( + duckdb_v2_table_function_get_bind_info_info_handle info, duckdb_v2_context_handle context, + duckdb_v2_error_info_handle *err); + +/* --- Functions for table --- */ /*! - * Creates a DATE value. + * Creates a new table function through a factory. * - * The payload is days since 1970-01-01, the same unit value_get_date reports. It is not range-checked here; value_cast - * is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The function starts out empty: configure it with the setter functions (e.g. `duckdb_v2_table_function_set_name()`, + * `duckdb_v2_table_function_set_exec_callback()`, etc.) and the signature obtained via + * `duckdb_v2_table_function_get_signature()`, then make it available with + * `duckdb_v2_connection_register_table_function()` or `duckdb_v2_extension_register_table_function()`. The caller owns + * the returned handle and must destroy it with `duckdb_v2_table_function_destroy()`, also after registration. + * + * The table function can only be registered on the database the factory belongs to. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new DATE value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param factory The factory to create the table function through. + * @param function On success, receives the newly created table function. Owned by the caller. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_date_with_context(duckdb_v2_context_handle ctx, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_table_function_handle *function, + duckdb_v2_error_info_handle *err); /*! - * Creates a DATE value. + * Sets the name of the table function. * - * The payload is days since 1970-01-01, the same unit value_get_date reports. It is not range-checked here; value_cast - * is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new DATE value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_date_with_connection(duckdb_v2_connection_handle conn, - int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_name(duckdb_v2_table_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME value. + * Returns the documentation of the function so it can be filled in. * - * The payload is microseconds since midnight, the same unit value_get_time reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * Set a description with `duckdb_v2_function_docs_set_description()`, and add examples and categories with + * `duckdb_v2_function_docs_add_example()` and `duckdb_v2_function_docs_add_category()`. The documentation is modified + * in place, describes this overload only, and is registered with the function. Optional. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to get the documentation of. + * @param docs The returned documentation. Borrowed and valid for the lifetime of the function handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_docs(duckdb_v2_table_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME value. + * Returns the function's signature so it can be configured. * - * The payload is microseconds since midnight, the same unit value_get_time reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * Add parameters with `duckdb_v2_function_signature_add_parameter()`. The signature is modified in place. A table + * function declares the columns it returns from its bind callback instead of through a return type, so registration + * rejects a signature whose return type was set with `duckdb_v2_function_signature_set_return_type()`. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to get the signature of. + * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_signature(duckdb_v2_table_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME_NS value. + * Sets arbitrary user data on the table function. * - * The payload is nanoseconds since midnight, the same unit value_get_time_ns reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * Associates an opaque pointer with the function, retrievable from the callbacks via + * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_table_function_init_global_get_user_data()`, + * `duckdb_v2_table_function_exec_get_user_data()` and their counterparts on the other phases. The opaque handle bundles + * the pointer with an optional destructor, invoked when the data is no longer needed. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the user data of. + * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_user_data(duckdb_v2_table_function_handle function, + duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME_NS value. + * Sets the bind callback of the table function. * - * The payload is nanoseconds since midnight, the same unit value_get_time_ns reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The bind callback is invoked during query planning for each call site of the function. It must declare the columns + * the function returns via `duckdb_v2_table_function_bind_add_result_column()`. Through its + * `duckdb_v2_function_bind_info_handle` it can also inspect the constant argument values and set "bind data" that is + * shared with all later callbacks. A bind callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the bind callback of. + * @param callback The bind callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_bind_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_bind_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME_TZ value. + * Sets the optional global init callback of the table function. * - * The payload packs the time of day and the UTC offset into one integer, in the committed layout: micros in the high 40 - * bits, a biased offset in the low 24. The same form value_get_time_tz reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The global init callback is invoked once per scan, at the start of execution. It can set "global state" shared by + * every thread scanning the function, and declare how many threads may scan it in parallel via + * `duckdb_v2_table_function_init_global_set_max_threads()`. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME_TZ value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the global init callback of. + * @param callback The global init callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz_with_context(duckdb_v2_context_handle ctx, - uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_init_global_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_init_global_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIME_TZ value. + * Sets the optional local init callback of the table function. * - * The payload packs the time of day and the UTC offset into one integer, in the committed layout: micros in the high 40 - * bits, a biased offset in the low 24. The same form value_get_time_tz reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The local init callback is invoked once per thread that will scan the function. It can set worker-local "local + * state", retrievable from the exec callback, typically derived from the shared global state. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIME_TZ value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the local init callback of. + * @param callback The local init callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz_with_connection(duckdb_v2_connection_handle conn, - uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_init_local_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_init_local_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP value. + * Sets the exec callback of the table function. * - * The payload is microseconds since 1970-01-01, the same unit value_get_timestamp reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The exec callback implements the function's logic: it is invoked repeatedly during query execution and writes the + * next batch of rows to the output chunk, until it produces an empty batch to signal the end of the scan. An exec + * callback must be set before registration. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the exec callback of. + * @param callback The exec callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_exec_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP value. + * Sets the optional progress callback of the table function. * - * The payload is microseconds since 1970-01-01, the same unit value_get_timestamp reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The progress callback is invoked on demand during execution to report how far the scan has advanced, which the engine + * surfaces as the query's progress. It is invoked concurrently with the exec callback, so it must read the global state + * in a thread-safe way. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the progress callback of. + * @param callback The progress callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_progress_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_progress_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_SEC value. + * Sets whether the table function supports projection pushdown. Defaults to false. * - * The payload is seconds since 1970-01-01, the same unit value_get_timestamp_sec reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * With projection pushdown, the engine asks the function for only the columns a query actually uses: the exec + * callback's output chunk holds one vector per requested column rather than one per column declared in bind, and the + * global init, local init and exec callbacks can look up which declared column each vector stands for via e.g. + * `duckdb_v2_table_function_exec_get_column_index()`. Without it the output chunk always holds every declared column, + * and the engine drops the unused ones itself. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_SEC value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to configure. + * @param enable Whether the function supports projection pushdown. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_projection_pushdown(duckdb_v2_table_function_handle function, + bool enable, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_SEC value. + * Sets the optional filter pushdown callback of the table function. * - * The payload is seconds since 1970-01-01, the same unit value_get_timestamp_sec reports. It is not range-checked here; - * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The filter pushdown callback is invoked while the query is optimized, after the bind callback and before any init + * callback, with the filter predicates the query applies to the function's rows. Each predicate is a bound expression + * the callback can inspect with the `expression` functions; the callback accepts the predicates it will apply itself + * via `duckdb_v2_table_function_filter_pushdown_accept()`, typically after recording what they select in the bind data. + * The engine then stops applying the accepted predicates and keeps applying the rest above the scan, so accepting a + * predicate is a promise to filter the rows exactly as it would have. The optimizer may invoke the callback more than + * once for the same query, each time with the predicates not yet accepted; it is not invoked when there are none. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_SEC value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the filter pushdown callback of. + * @param callback The filter pushdown callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_filter_pushdown_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_filter_pushdown_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_MS value. + * Sets the optional "partition data" callback of the table function. * - * The payload is milliseconds since 1970-01-01, the same unit value_get_timestamp_ms reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The callback reports, for the batch the exec callback just produced, an ordering batch index, the values of a set of + * partitioning columns, or both, depending on what a downstream operator requires; it runs on the worker thread that + * produced the batch. Set `duckdb_v2_table_function_set_partitioning_callback()` too when the function can answer + * partitioning-column requests, since the engine only asks a scan for those when that callback reports + * `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS`. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_MS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the partition data callback of. + * @param callback The partition data callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_partition_data_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_partition_data_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_MS value. + * Sets the optional "partitioning" callback of the table function. * - * The payload is milliseconds since 1970-01-01, the same unit value_get_timestamp_ms reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The callback tells the query optimizer, for a candidate `GROUP BY` column set, whether every partition the scan + * produces carries exactly one distinct value for those columns; only `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS` + * unlocks the partitioned aggregate optimization; any other result, or leaving this callback unset, keeps the regular + * hash aggregate. It runs on the planning thread, before execution starts, and must be deterministic for a given column + * set since it may be called on a plan the optimizer later discards. Registration fails unless + * `duckdb_v2_table_function_set_partition_data_callback()` is set too, since the engine asks that callback for the + * partitioning column values once this one claims single-value partitions. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_MS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param function The function to set the partitioning callback of. + * @param callback The partitioning callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_partitioning_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_partitioning_callback_fn callback, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_NS value. + * Declares one of the columns the function returns. * - * The payload is nanoseconds since 1970-01-01, the same unit value_get_timestamp_ns reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * Call this once per column, in order: the columns declared here are the columns of the table the function produces, + * and the vectors of the output chunk the exec callback fills follow the same order. At least one column must be + * declared. The type must be a fully defined concrete type; ANY is rejected, as a result column carries data. The name + * and type are borrowed and copied. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The bind info handle. + * @param name The name of the column. Borrowed and copied. + * @param type The type of the column. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_add_result_column( + duckdb_v2_table_function_bind_info_handle info, const duckdb_v2_identifier_t *name, + duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_NS value. + * Sets the estimated number of rows the scan will produce. * - * The payload is nanoseconds since 1970-01-01, the same unit value_get_timestamp_ns reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * The estimate is a hint for the optimizer, not a limit: producing a different number of rows is not an error. Without + * it, the optimizer falls back on its own defaults. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The bind info handle. + * @param cardinality The estimated number of rows. + * @param is_exact Whether the estimate is exact, which also makes it an upper bound on the row count. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_set_cardinality( + duckdb_v2_table_function_bind_info_handle info, idx_t cardinality, bool is_exact, duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_TZ value. + * Sets the order guarantee of the rows this call of the function produces. Defaults to + * `ORDER_PRESERVATION_INSERTION_ORDER`. * - * The payload is microseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz reports. It is not - * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via - * value_destroy. + * With `ORDER_PRESERVATION_NO_ORDER`, a scan that reports more than one thread via + * `duckdb_v2_table_function_init_global_set_max_threads()` runs in parallel into query results, INSERT and COPY without + * a `duckdb_v2_table_function_set_partition_data_callback()`, and the rows arrive in no particular order. With + * insertion order kept, those consumers need the partition data callback to run in parallel. Fails with + * `ERROR_INPUT_INVALID` when order is not one of the enum's declared values. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_TZ value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The bind info handle. + * @param order The order guarantee of the produced rows. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_set_order_preservation( + duckdb_v2_table_function_bind_info_handle info, DUCKDB_V2_ORDER_PRESERVATION order, + duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_TZ value. - * - * The payload is microseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz reports. It is not - * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via - * value_destroy. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_TZ value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The global init info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_user_data( + duckdb_v2_table_function_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_TZ_NS value. - * - * The payload is nanoseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz_ns reports. It is not - * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via - * value_destroy. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 - * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_TZ_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * + * @param info The global init info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns_with_context(duckdb_v2_context_handle ctx, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_bind_data( + duckdb_v2_table_function_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a TIMESTAMP_TZ_NS value. + * Sets the function's "global state" from the global init callback. * - * The payload is nanoseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz_ns reports. It is not - * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via - * value_destroy. + * The global state lives for the duration of the scan and is retrievable from the local init, exec and progress + * callbacks. Every thread scanning the function shares it, so the function must synchronize its own access to it. The + * opaque handle bundles the pointer with an optional destructor, invoked when the scan is done with the state. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new TIMESTAMP_TZ_NS value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The global init info handle. + * @param data Opaque handle bundling the global state pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_set_global_state( + duckdb_v2_table_function_init_global_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Creates a INTERVAL value. + * Sets how many threads may scan the function in parallel. * - * The payload is the (months, days, micros) triple, the same unit value_get_interval reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * Defaults to 1, a single-threaded scan. The engine creates at most this many local states, and therefore runs at most + * this many exec callbacks concurrently. It is an upper bound, not a request: the engine may use fewer threads. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new INTERVAL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The global init info handle. + * @param max_threads The maximum number of threads. Must be at least 1. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_interval_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_interval_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_set_max_threads( + duckdb_v2_table_function_init_global_info_handle info, idx_t max_threads, duckdb_v2_error_info_handle *err); /*! - * Creates a INTERVAL value. + * Returns the number of columns the scan produces. * - * The payload is the (months, days, micros) triple, the same unit value_get_interval reports. It is not range-checked - * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. + * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the + * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of + * columns declared in bind. Valid indices for `duckdb_v2_table_function_init_global_get_column_index()` are `0` up to + * (but excluding) this count. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The payload to wrap. - * @param out_value Receives the new INTERVAL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The global init info handle. + * @param count Receives the number of columns the scan produces. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_interval_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_interval_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_column_count( + duckdb_v2_table_function_init_global_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Creates a DECIMAL value from its backing integer. + * Returns which declared column the scan's column at the given index stands for. * - * The value is scaled by 10^scale, so (18500, 18, 3) is 18.500. width is the total digit count and must be 1..38; scale - * is the number of digits after the point and must not exceed width. Violating either returns ERROR_INPUT_INVALID, as - * does a value too wide for the storage tier the width selects. The value itself is not range-checked against the - * width; value_cast is the validating path. + * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration + * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection + * pushdown the mapping is the identity. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The backing integer, scaled by 10^scale. - * @param width Total digit count, 1..38. - * @param scale Digits after the decimal point; must not exceed width. - * @param out_value Receives the new DECIMAL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The global init info handle. + * @param index The index of the column in the scan's output. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_decimal_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, - uint8_t width, uint8_t scale, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_column_index( + duckdb_v2_table_function_init_global_info_handle info, idx_t index, idx_t *column_index, + duckdb_v2_error_info_handle *err); /*! - * Creates a DECIMAL value from its backing integer. - * - * The value is scaled by 10^scale, so (18500, 18, 3) is 18.500. width is the total digit count and must be 1..38; scale - * is the number of digits after the point and must not exceed width. Violating either returns ERROR_INPUT_INVALID, as - * does a value too wide for the storage tier the width selects. The value itself is not range-checked against the - * width; value_cast is the validating path. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The backing integer, scaled by 10^scale. - * @param width Total digit count, 1..38. - * @param scale Digits after the decimal point; must not exceed width. - * @param out_value Receives the new DECIMAL value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_decimal_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, - uint8_t width, uint8_t scale, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_user_data( + duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a UUID value from its internal 128-bit form. - * - * The payload is the storage form a vector element holds, not the canonical byte order: the high bit is flipped so the - * integer sorts. To build one from canonical text, cast a VARCHAR with value_cast instead, which is also how a UUID - * renders back. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The internal 128-bit storage form. - * @param out_value Receives the new UUID value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uuid_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_bind_data( + duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a UUID value from its internal 128-bit form. + * Retrieves the global state set by the function's global init callback. * - * The payload is the storage form a vector element holds, not the canonical byte order: the high bit is flipped so the - * integer sorts. To build one from canonical text, cast a VARCHAR with value_cast instead, which is also how a UUID - * renders back. + * Shared with every other thread scanning the function; access to it must be synchronized by the function. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The internal 128-bit storage form. - * @param out_value Receives the new UUID value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param data Receives the global state pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uuid_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_global_state( + duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a BIT value from its wire bytes. + * Sets the function's worker-local "local state" from the local init callback. * - * The wire form is a mandatory padding-header byte — the count of leading bits in the first data byte that are not part - * of the bit string — followed by the data bytes, so the input must be at least 1 byte. These are the same bytes - * value_get_blob reports back. + * The local state is associated with the executing thread for the duration of the scan and retrievable from the exec + * callback via `duckdb_v2_table_function_exec_get_local_state()`. No other thread observes it, so it needs no + * synchronization. The opaque handle bundles the pointer with an optional destructor, invoked when the local state is + * no longer needed. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The wire bytes, a padding header byte followed by data. Must be at least 1 byte. - * @param out_value Receives the new BIT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param data Opaque handle bundling the local state pointer plus an optional destructor. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bit_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_set_local_state( + duckdb_v2_table_function_init_local_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); /*! - * Creates a BIT value from its wire bytes. + * Returns the number of columns the scan produces. * - * The wire form is a mandatory padding-header byte — the count of leading bits in the first data byte that are not part - * of the bit string — followed by the data bytes, so the input must be at least 1 byte. These are the same bytes - * value_get_blob reports back. + * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the + * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of + * columns declared in bind. Valid indices for `duckdb_v2_table_function_init_local_get_column_index()` are `0` up to + * (but excluding) this count. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The wire bytes, a padding header byte followed by data. Must be at least 1 byte. - * @param out_value Receives the new BIT value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param count Receives the number of columns the scan produces. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bit_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_column_count( + duckdb_v2_table_function_init_local_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Creates a BIGNUM value from its storage bytes. + * Returns which declared column the scan's column at the given index stands for. * - * The bytes are opaque storage, as produced by bignum_encode from a magnitude and a sign flag; a negative is stored - * bit-inverted behind a header, so these are not the magnitude bytes. The header plus at least one magnitude byte means - * the input must exceed 3 bytes. These are the same bytes value_get_blob reports back, for bignum_decode to translate. + * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration + * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection + * pushdown the mapping is the identity. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param in_value The opaque storage bytes from bignum_encode. Must exceed 3 bytes. - * @param out_value Receives the new BIGNUM value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The local init info handle. + * @param index The index of the column in the scan's output. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bignum_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR +duckdb_v2_table_function_init_local_get_column_index(duckdb_v2_table_function_init_local_info_handle info, idx_t index, + idx_t *column_index, duckdb_v2_error_info_handle *err); /*! - * Creates a BIGNUM value from its storage bytes. - * - * The bytes are opaque storage, as produced by bignum_encode from a magnitude and a sign flag; a negative is stored - * bit-inverted behind a header, so these are not the magnitude bytes. The header plus at least one magnitude byte means - * the input must exceed 3 bytes. These are the same bytes value_get_blob reports back, for bignum_decode to translate. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param in_value The opaque storage bytes from bignum_encode. Must exceed 3 bytes. - * @param out_value Receives the new BIGNUM value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bignum_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_user_data(duckdb_v2_table_function_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a LIST value from its elements. - * - * The child type is the common type of the elements, resolved by the same rule a SQL list literal follows, and every - * element is cast to it; a set with no common type surfaces the cast error. Pass child_type to name the type instead, - * which is also how an empty list is built: with child_type NULL, an empty element array has no type to resolve and - * returns ERROR_INPUT_INVALID. - * - * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. The type is rebuilt from its child, - * so an alias on the outer LIST type is not preserved; value_cast is the alias-preserving path. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. - * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. - * @param child_count The number of elements. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_list_with_context( - duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_bind_data(duckdb_v2_table_function_exec_info_handle info, + void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a LIST value from its elements. - * - * The child type is the common type of the elements, resolved by the same rule a SQL list literal follows, and every - * element is cast to it; a set with no common type surfaces the cast error. Pass child_type to name the type instead, - * which is also how an empty list is built: with child_type NULL, an empty element array has no type to resolve and - * returns ERROR_INPUT_INVALID. + * Retrieves the global state set by the function's global init callback. * - * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. The type is rebuilt from its child, - * so an alias on the outer LIST type is not preserved; value_cast is the alias-preserving path. + * Shared with every other thread scanning the function; access to it must be synchronized by the function. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. - * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. - * @param child_count The number of elements. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the global state pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_list_with_connection( - duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); - -/*! - * Creates an ARRAY value from its elements, sized by their count. - * - * The child type is resolved exactly as for value_create_list, and child_type names it explicitly the same way. The - * minimum array size is 1, so an empty element array returns ERROR_INPUT_INVALID whether or not child_type is given. - * - * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_global_state( + duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); + +/*! + * Retrieves the worker-local local state set by the function's local init callback. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. - * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. - * @param child_count The number of elements. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param data Receives the local state pointer for the executing thread, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_array_with_context( - duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_local_state( + duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates an ARRAY value from its elements, sized by their count. - * - * The child type is resolved exactly as for value_create_list, and child_type names it explicitly the same way. The - * minimum array size is 1, so an empty element array returns ERROR_INPUT_INVALID whether or not child_type is given. + * Retrieves the output chunk the exec callback must write the next batch of rows into. * - * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. + * The chunk holds one vector per column declared with `duckdb_v2_table_function_bind_add_result_column()`, in the same + * order; reach them with `duckdb_v2_data_chunk_get_vector()`. The chunk starts out empty on every invocation: write the + * rows, then declare how many there are with `duckdb_v2_vector_set_size()` on the first vector, which the engine takes + * as the batch's row count and propagates to the other vectors. Producing an empty batch signals the end of the scan, + * after which the callback is not invoked again on that thread. Borrowed; valid only for the duration of the callback. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. - * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. - * @param child_count The number of elements. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param chunk Receives the borrowed output chunk to write into. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_array_with_connection( - duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, - idx_t child_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR +duckdb_v2_table_function_exec_get_output_chunk(duckdb_v2_table_function_exec_info_handle info, + duckdb_v2_data_chunk_handle *chunk, duckdb_v2_error_info_handle *err); /*! - * Creates a STRUCT value from named fields, in order. - * - * Each field keeps its own child's type, so there is nothing to resolve across them; names and children are parallel - * arrays of field_count entries. An empty field list builds the empty struct, which is a real type. + * Returns the number of columns the scan produces. * - * The names and children are borrowed and copied in, and a NULL child becomes a typed NULL. Duplicate field names are - * rejected. + * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the + * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of + * columns declared in bind. Valid indices for `duckdb_v2_table_function_exec_get_column_index()` are `0` up to (but + * excluding) this count. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param names An array of field_count field names. Pass NULL when field_count is 0. - * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. - * @param field_count The number of fields. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param count Receives the number of columns the scan produces. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_struct_with_context( - duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, - idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_column_count( + duckdb_v2_table_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Creates a STRUCT value from named fields, in order. - * - * Each field keeps its own child's type, so there is nothing to resolve across them; names and children are parallel - * arrays of field_count entries. An empty field list builds the empty struct, which is a real type. + * Returns which declared column the scan's column at the given index stands for. * - * The names and children are borrowed and copied in, and a NULL child becomes a typed NULL. Duplicate field names are - * rejected. + * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration + * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection + * pushdown the mapping is the identity. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param names An array of field_count field names. Pass NULL when field_count is 0. - * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. - * @param field_count The number of fields. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The exec info handle. + * @param index The index of the column in the scan's output. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_struct_with_connection( - duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *names, const duckdb_v2_value_handle *children, - idx_t field_count, duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_column_index( + duckdb_v2_table_function_exec_info_handle info, idx_t index, idx_t *column_index, duckdb_v2_error_info_handle *err); /*! - * Creates a TUPLE value from positional fields. - * - * TUPLE is the unnamed struct: the same positional children as value_create_struct, under a distinct type id and with - * no names. Each field keeps its own child's type. An empty field list builds the empty tuple, which is a real type. - * - * The children are borrowed and copied in, and a NULL child becomes a typed NULL. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. - * @param field_count The number of fields. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The progress info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tuple_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_value_handle *children, - idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_user_data( + duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a TUPLE value from positional fields. - * - * TUPLE is the unnamed struct: the same positional children as value_create_struct, under a distinct type id and with - * no names. Each field keeps its own child's type. An empty field list builds the empty tuple, which is a real type. - * - * The children are borrowed and copied in, and a NULL child becomes a typed NULL. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. - * @param field_count The number of fields. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The progress info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tuple_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_value_handle *children, - idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_bind_data( + duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a MAP value from parallel key and value arrays. - * - * The key and value types are the common types of the keys and of the values, resolved as for value_create_list, and - * every entry is cast to them. Pass key_type and value_type to name them instead, which is also how an empty map is - * built: with both NULL, empty key and value arrays have no types to resolve and return ERROR_INPUT_INVALID. + * Retrieves the global state set by the function's global init callback. * - * Keys and values are borrowed and copied in, and the two arrays must be the same length. Keys must be non-NULL and - * unique; both are enforced. + * The progress callback runs concurrently with the exec callbacks scanning the function, so it must read the global + * state in a thread-safe way. * * history: * - stable: v2.0.0 * - * @param ctx The context to use for the construction. - * @param key_type Optional. The key type. Pass NULL to resolve it from the keys. - * @param value_type Optional. The value type. Pass NULL to resolve it from the values. - * @param keys An array of entry_count keys. Borrowed (copied in). Pass NULL when entry_count is 0. - * @param values An array of entry_count values, parallel to keys. Borrowed (copied in). Pass NULL when entry_count is - * 0. - * @param entry_count The number of entries. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The progress info handle. + * @param data Receives the global state pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_map_with_context( - duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, - const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_global_state( + duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Creates a MAP value from parallel key and value arrays. - * - * The key and value types are the common types of the keys and of the values, resolved as for value_create_list, and - * every entry is cast to them. Pass key_type and value_type to name them instead, which is also how an empty map is - * built: with both NULL, empty key and value arrays have no types to resolve and return ERROR_INPUT_INVALID. + * Reports how far the scan has advanced. * - * Keys and values are borrowed and copied in, and the two arrays must be the same length. Keys must be non-NULL and - * unique; both are enforced. + * A fraction between 0.0 (nothing scanned yet) and 1.0 (done); values outside that range are clamped. A progress + * callback that returns without calling this reports no progress. * * history: * - stable: v2.0.0 * - * @param conn The connection to use for the construction. - * @param key_type Optional. The key type. Pass NULL to resolve it from the keys. - * @param value_type Optional. The value type. Pass NULL to resolve it from the values. - * @param keys An array of entry_count keys. Borrowed (copied in). Pass NULL when entry_count is 0. - * @param values An array of entry_count values, parallel to keys. Borrowed (copied in). Pass NULL when entry_count is - * 0. - * @param entry_count The number of entries. - * @param out_value Receives the new composite value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The progress info handle. + * @param progress The fraction of the scan that is complete, in [0.0, 1.0]. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_map_with_connection( - duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, - const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_set_progress( + duckdb_v2_table_function_progress_info_handle info, double progress, duckdb_v2_error_info_handle *err); /*! - * Returns the number of child values of a value. - * - * Per type kind: the element count for LIST and ARRAY; the field count for STRUCT and TUPLE; twice the entry count for - * MAP, whose children alternate key, value; and 2 for UNION, the tag plus the active member. Everything else reports 0, - * NULL values of any nested type included. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`, or null if none was set. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out_count Receives the number of children. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param data Receives the user data pointer. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_child_count(duckdb_v2_value_handle value, idx_t *out_count, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_user_data( + duckdb_v2_table_function_filter_pushdown_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Returns one child of a composite value as an owned copy. - * - * LIST and ARRAY children are the elements. STRUCT and TUPLE children are the fields in declared order, and a STRUCT's - * field names come from its type, via logical_type_get_param. MAP children alternate key, value, symmetric with - * value_create_map. UNION children are [0] the tag, as a UTINYINT value, and [1] the active member, because a union - * value holds only that one member. - * - * Note the divergence from vector_get_child, which descends structurally and so exposes [0] = tag and [1..N] = ALL - * members. Code written to descend generically over both must account for that difference. + * Retrieves the bind data set via `duckdb_v2_function_bind_set_bind_data()`, or null if none was set. * - * An out-of-range index returns ERROR_INPUT_INVALID, as does any index on a non-composite or NULL value. The returned - * value is caller-owned; destroy it via value_destroy. + * This is the same object the init and exec callbacks later receive, so a predicate the callback accepts can be + * recorded in it for the scan to apply. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param index The child index, in [0, child_count). - * @param out_child Receives the owned child value. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param data Receives the bind data pointer. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_child(duckdb_v2_value_handle value, idx_t index, - duckdb_v2_value_handle *out_child, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_bind_data( + duckdb_v2_table_function_filter_pushdown_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Casts a value to a target type, using a connection. - * - * The same as value_cast_with_context, except that the cast function set comes from a connection: the cast runs in its - * own transaction on that connection's context. Use it from outside DuckDB, where a connection — but no context — is in - * hand. - * - * The conversion is the SQL-faithful, non-strict one, registered custom casts included, and a cast failure surfaces - * from the call. Together with a VARCHAR built through value_create_varchar_with_context / _with_connection, this - * constructs any value from text, extension values included; casting a member value to a union type, or a VARCHAR to an - * enum type, is the sanctioned way to build UNION and ENUM values. + * Returns the number of filter predicates offered to the function. * - * The input value and target type are borrowed. The returned value is caller-owned; destroy it via value_destroy. + * The predicates are combined with `AND`: every row the scan produces must satisfy all of them. Valid indices for + * `duckdb_v2_table_function_filter_pushdown_get_filter()` and `duckdb_v2_table_function_filter_pushdown_accept()` are + * `0` up to (but excluding) this count. * * history: * - stable: v2.0.0 * - * @param conn The connection whose context supplies the cast function set. - * @param value The borrowed value to cast. - * @param target_type The borrowed target logical type. - * @param out_value Receives the owned cast result. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param count Receives the number of predicates. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_cast_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_filter_count( + duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Casts a value to a target type, using a context. - * - * The conversion is the SQL-faithful, non-strict one, registered custom casts included, and a cast failure surfaces - * from the call. Together with a VARCHAR built through value_create_varchar_with_context / _with_connection, this - * constructs any value from text, extension values included; casting a member value to a union type, or a VARCHAR to an - * enum type, is the sanctioned way to build UNION and ENUM values. - * - * Runs in the caller's context scope, as create_type_from_text does: reach it from a bind-phase callback or another - * context-holding scope, not from an exec-phase worker callback. From outside DuckDB use value_cast_with_connection. + * Retrieves the filter predicate at the given index. * - * The input value and target type are borrowed. The returned value is caller-owned; destroy it via value_destroy. + * Fails if the index is out of bounds. The predicate is a bound expression evaluating to `BOOLEAN`; inspect it with + * `duckdb_v2_expression_get_type()` and the other `expression` functions, and resolve the column references it contains + * via `duckdb_v2_table_function_filter_pushdown_get_column_index()`. Borrowed; valid only for the duration of the + * callback. * * history: * - stable: v2.0.0 * - * @param ctx The context supplying the cast function set. - * @param value The borrowed value to cast. - * @param target_type The borrowed target logical type. - * @param out_value Receives the owned cast result. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. - * @return DUCKDB_V2_ERROR - */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_cast_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err); + * @param info The filter pushdown info handle. + * @param index The index of the predicate to retrieve. + * @param filter Receives the borrowed predicate. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_filter( + duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, duckdb_v2_expression_handle *filter, + duckdb_v2_error_info_handle *err); /*! - * Returns whether the value is NULL. + * Accepts the filter predicate at the given index: the function will apply it itself. + * + * The engine stops applying an accepted predicate, so the scan must produce only rows that satisfy it, or the query + * returns rows it should not. Predicates left unaccepted are applied by the engine as usual, so a callback that + * recognizes nothing can simply return. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out_is_null Receives true if the value is NULL, false otherwise. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param index The index of the predicate to accept. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_is_null(duckdb_v2_value_handle value, bool *out_is_null, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_accept( + duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, duckdb_v2_error_info_handle *err); /*! - * Returns the logical type of the value. + * Returns the number of columns the predicates can refer to. * - * The returned logical type is caller-owned; destroy it via logical_type_destroy. + * A column reference inside a predicate (`duckdb_v2_expression_column_ref_get_index()`) indexes the columns the query + * reads from the function, not the columns declared in bind. Valid indices for + * `duckdb_v2_table_function_filter_pushdown_get_column_index()` are `0` up to (but excluding) this count. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out_type Receives the owned logical type. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param count Receives the number of referable columns. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_logical_type(duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_column_count( + duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Renders the value as a human-readable string. Diagnostic only. - * - * Writes into a caller-supplied buffer, so nothing is allocated on the caller's behalf and nothing has to be freed. - * - * Pass out_string = NULL to size the buffer without rendering into it: out_length then receives the length, and - * out_capacity is ignored. With out_string != NULL, out_capacity must be at least out_length + 1, or the call returns - * ERROR_INPUT_OBJECT_SIZE with out_length set to the required length and out_string left untouched. + * Resolves a column reference found in a predicate to the declared column it refers to. * - * out_length never counts the terminator, but a successful write always appends one, so the buffer is usable as a C - * string. + * Takes the index a `EXPRESSION_TYPE_BOUND_COLUMN_REF` node reports and returns the index of the column among the ones + * declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration order. Fails if the index is out of + * bounds. * * history: * - stable: v2.0.0 * - * @param value The value to read. - * @param out_string Caller-owned buffer receiving the text plus a null terminator, or NULL to only report the required - * length in out_length. - * @param out_capacity Bytes available in out_string, terminator included. Ignored when out_string is NULL. - * @param out_length Receives the text length excluding the null terminator — written on success and on - * ERROR_INPUT_OBJECT_SIZE. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The filter pushdown info handle. + * @param index The column index reported by a column reference node. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_to_string(duckdb_v2_value_handle value, char *out_string, - idx_t out_capacity, idx_t *out_length, - duckdb_v2_error_info_handle *err); - -/* --- Struct definitions for value --- */ - -/* ============================================================================ - * MODULE: prepared_statement - * ============================================================================ */ - -/* --- Enums for prepared_statement --- */ - -/* --- Struct forward declarations for prepared_statement --- */ - -/* --- Types for prepared_statement --- */ - -/*! - * An owned handle to a statement bound and planned once, executable repeatedly via - * `duckdb_v2_prepared_statement_execute()`. Construct with `duckdb_v2_prepared_statement_create()` and destroy with - * `duckdb_v2_prepared_statement_destroy()`. It keeps its connection's session alive, so it stays usable across - * executions and even after the connection is disconnected. - */ -typedef struct _duckdb_v2_prepared_statement { - void *internal_ptr; -} * duckdb_v2_prepared_statement_handle; - -/* --- Constants for prepared_statement --- */ - -/* --- Function pointer typedefs for prepared_statement --- */ - -/* --- Functions for prepared_statement --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_column_index( + duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, idx_t *column_index, + duckdb_v2_error_info_handle *err); /*! - * Prepares a parsed statement into a reusable handle. Non-consuming. - * - * Copies the statement's AST, then binds and plans it once. Binder and catalog errors surface here, exactly as they - * would from `duckdb_v2_statement_execute()`. The statement is borrowed rather than consumed, since a copy is what gets - * prepared, so it can be prepared again or executed directly; the caller destroys it with - * `duckdb_v2_sql_statement_destroy()`. - * - * By default this succeeds for any preparable statement, whether or not its plan will be reused; ask - * `duckdb_v2_prepared_statement_reuses_plan()` which one you got. Setting `require_cacheable` instead fails with - * `ERROR_INPUT_INVALID` when the plan would not be reused, so a caller who wants the handle only for the speedup finds - * out here rather than after silently taking the slow path. - * - * Refuses with `ERROR_RESOURCE_IN_USE` while the connection has a live result. Drain, destroy, or interrupt that result - * first, or prepare on another connection. `*out_prepared` is set to NULL on failure. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param conn The connection supplying the catalog, transaction, and parser state. The prepared statement belongs to - * it. - * @param statement The statement to prepare. Borrowed and copied, not consumed; destroy it with - * `duckdb_v2_sql_statement_destroy()`. - * @param require_cacheable When true, fail with `ERROR_INPUT_INVALID` unless the prepared plan will be reused across - * executions, as `duckdb_v2_prepared_statement_reuses_plan()` would report it. - * @param out_prepared On success, receives the new prepared statement. Owned by the caller; destroy via - * `duckdb_v2_prepared_statement_destroy()`. + * @param info The partition data info handle. + * @param data Receives the user data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_create(duckdb_v2_connection_handle conn, - duckdb_v2_sql_statement_handle statement, - bool require_cacheable, - duckdb_v2_prepared_statement_handle *out_prepared, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_user_data( + duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Executes a prepared statement, streaming its result. Non-consuming. - * - * Returns a result without executing anything: execution happens incrementally as the result is stepped or drained, - * exactly as with `duckdb_v2_statement_execute()`. The handle returned is an ordinary result, with identical behaviour - * throughout -- streaming, draining, the changed-row count of a DML statement, the output schema, the statement type, - * and the result type. - * - * `parameter_values` binds the statement's parameters as constants for this execution. Binding is positional by default - * ($1 = element 0); supply `parameter_names` to bind by name instead, where a non-empty entry binds its value to that - * named parameter ($name, matched case-insensitively) and a {NULL, 0} entry stays positional. Pass NULL for both - * arrays, or a count of 0, for a statement without parameters. Both arrays are borrowed and copied in, so the caller - * still owns and destroys them. A key set that does not match the statement's parameters is rejected with - * `ERROR_INPUT_INVALID`, with one exception: a named parameter left without a value reads the session variable of the - * same name (`SET VARIABLE`) when one exists. - * - * Not consumed: execute the same handle again, with the same values or different ones, as often as you like. Values are - * bound per execution and nothing carries over between them. A catalog change since the statement was prepared, or a - * parameter type that differs from the one the cached plan assumed, triggers a re-bind that is invisible apart from its - * cost. - * - * Refuses with `ERROR_RESOURCE_IN_USE` while the connection has a live result; drain, destroy, or interrupt it first, - * or execute on another connection. A failed execution, at any stage, leaves the prepared statement usable. - * `*out_result` is set to NULL on failure. + * Retrieves the bind data set by the function's bind callback. * * history: * - stable: v2.0.0 * - * @param prepared The prepared statement to execute. Borrowed; not consumed. - * @param parameter_names Optional. An array of `parameter_count` parameter names; a non-empty entry binds its value to - * the named parameter ($name, case-insensitive), a {NULL, 0} entry keeps it positional ($1 = element 0). Pass NULL to - * bind everything positionally. - * @param parameter_values Optional. An array of `parameter_count` values. Each binds by name when `parameter_names` - * supplies one, and positionally ($1 = element 0) otherwise. Borrowed and copied in. Pass NULL for a statement without - * parameters. - * @param parameter_count The number of entries in `parameter_names` and `parameter_values`. Pass 0 for a statement - * without parameters. - * @param out_result On success, receives the new result. Owned by the caller; destroy via `duckdb_v2_result_destroy()`. + * @param info The partition data info handle. + * @param data Receives the bind data pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_execute(duckdb_v2_prepared_statement_handle prepared, - const duckdb_v2_identifier_t *parameter_names, - const duckdb_v2_value_handle *parameter_values, - idx_t parameter_count, - duckdb_v2_result_handle *out_result, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_bind_data( + duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Reports whether the prepared statement reuses its compiled plan across executions. - * - * True when executions reuse the plan built at prepare time, provided the supplied values match the planned parameter - * types and nothing the plan depends on has changed; false when the statement re-binds on every execution, making it no - * faster than `duckdb_v2_statement_execute()`. A plan is reused only when all parameter types were resolved at prepare - * time and the plan is cacheable: `SELECT 42` reuses, `SELECT $1::INTEGER + 1` reuses, a statement reading a base table - * does not (it re-binds so a catalog change is picked up), and `SELECT $1 + $2` does not (the types are unknown until - * values arrive). + * Retrieves the global state set by the function's global init callback. * - * A static property of the built plan, fixed when the statement was prepared and independent of the values later passed - * to `duckdb_v2_prepared_statement_execute()`. + * Shared with every other thread scanning the function; access to it must be synchronized by the function. * * history: * - stable: v2.0.0 * - * @param prepared The prepared statement to inspect. - * @param out_reuses Receives true when the compiled plan is reused across executions, false when the statement re-binds - * each time. + * @param info The partition data info handle. + * @param data Receives the global state pointer, or null if none was set. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_reuses_plan(duckdb_v2_prepared_statement_handle prepared, - bool *out_reuses, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_global_state( + duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Destroys a prepared statement. - * - * Null-safe: passing NULL, or a slot already set to NULL, is a no-op. A result produced by - * `duckdb_v2_prepared_statement_execute()` is independently owned and keeps the session alive itself, so the prepared - * statement may be destroyed while results made from it are still live. On success the slot is set to NULL. + * Retrieves the worker-local local state set by the function's local init callback, for the thread that produced the + * batch this call reports on. No other thread observes it, so it needs no synchronization. * * history: * - stable: v2.0.0 * - * @param prepared The prepared statement to destroy. + * @param info The partition data info handle. + * @param data Receives the local state pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_prepared_statement_destroy(duckdb_v2_prepared_statement_handle *prepared); - -/* --- Struct definitions for prepared_statement --- */ - -/* ============================================================================ - * MODULE: query_result - * ============================================================================ */ - -/* --- Enums for query_result --- */ - -/*! - * Shape of a query result. QUERY_RESULT carries rows and columns; CHANGED_ROWS carries an affected row count, as an - * INSERT, UPDATE, or DELETE without RETURNING produces; NOTHING covers DDL and other statements with no row output. - */ -typedef enum DUCKDB_V2_RESULT_TYPE { - DUCKDB_V2_RESULT_TYPE_QUERY_RESULT = 0, - DUCKDB_V2_RESULT_TYPE_CHANGED_ROWS = 1, - DUCKDB_V2_RESULT_TYPE_NOTHING = 2, - DUCKDB_V2_RESULT_TYPE_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_RESULT_TYPE; - -/*! - * Outcome of a result_step call. WAITING is the 0-value, so a zero-initialized out-param reads as "no work product yet" - * rather than CHUNK, the same convention VECTOR_TYPE_OTHER follows. The four states are the ones a consumer acts on; - * they are deliberately not a projection of any internal enum. - */ -typedef enum DUCKDB_V2_RESULT_STEP_STATUS { - //! No chunk yet; step again, or block in result_wait. - DUCKDB_V2_RESULT_STEP_STATUS_WAITING = 0, - - //! A caller-owned chunk was written to *out_chunk, or an array to *out_array. - DUCKDB_V2_RESULT_STEP_STATUS_CHUNK = 1, - - //! Stream exhausted. Sticky. - DUCKDB_V2_RESULT_STEP_STATUS_FINISHED = 2, - - //! Query was interrupted. Sticky. - DUCKDB_V2_RESULT_STEP_STATUS_CANCELLED = 3, - DUCKDB_V2_RESULT_STEP_STATUS_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_RESULT_STEP_STATUS; - -/* --- Struct forward declarations for query_result --- */ - -/* --- Types for query_result --- */ - -/* --- Constants for query_result --- */ - -/* --- Function pointer typedefs for query_result --- */ - -/* --- Functions for query_result --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_local_state( + duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Executes a parsed statement on the connection, streaming its result. Non-consuming. + * Returns whether a downstream operator needs this batch's ordering position. * - * Takes a statement produced by the sql_statement module (parse_sql / statement_iterator_next), preprocesses and - * prepares it, and returns a result handle without executing anything: execution happens incrementally as the result is - * stepped (result_step) or drained (result_fetch_chunk). This call reports only the errors detectable at prepare time — - * binder, catalog, pragma preprocessing — while errors raised during execution surface from the stepping functions. + * `duckdb_v2_table_function_partition_data_set_batch_index()` must still be called even when this is false: the engine + * validates the reported value regardless of whether it is actually used for ordering, so `0` is always a safe answer + * when this reports false. * - * The statement is borrowed, not consumed, since a copy is what executes. It can be executed again, for example with a - * different set of values, and the caller destroys it with sql_statement_destroy. + * history: + * - stable: v2.0.0 * - * parameter_values binds the statement's parameters as constants for this execution. Binding is positional by default: - * the i-th value binds $(i+1), matching SQL's own convention, so dense $1..$N and ? placeholders work directly. Supply - * parameter_names to bind by name instead — a non-empty name binds its value to that named parameter ($name, matched - * case-insensitively), while a {NULL, 0} entry leaves that value positional. The parameter schema from statement_bind - * lists the names to use. Both arrays are borrowed and copied in; the caller still owns and destroys them. Pass NULL - * for both, or a count of 0, for an unparameterized statement. A key set that does not match the statement's parameters - * — names for a positional statement, or the reverse — is rejected with ERROR_INPUT_INVALID, and named and positional - * parameters cannot be mixed within one statement. Parameters and statement expansion are mutually exclusive: passing - * values for a statement that preprocesses into a group is rejected with ERROR_INPUT_INVALID. + * @param info The partition data info handle. + * @param required Receives whether a batch index is required. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_requires_batch_index( + duckdb_v2_table_function_partition_data_info_handle info, bool *required, duckdb_v2_error_info_handle *err); + +/*! + * Returns whether a downstream operator needs this batch's values for a set of partitioning columns. * - * Preprocessing can expand one statement into a group — a dynamic PIVOT, or statement-expanding DDL such as ALTER ... - * ADD COLUMN with a non-constant DEFAULT. The group executes as one result through the same steps, and the stream - * surfaces the first row-producing statement of the group, or the last statement when none produces rows. An expansion - * with more than one row-producing statement cannot be streamed as a single result and reports - * ERROR_QUERY_NOT_IMPLEMENTED; no known expansion produces one. + * When true, call `duckdb_v2_table_function_partition_data_set_partition_value()` once for every index in `[0, count)`, + * where `count` comes from `duckdb_v2_table_function_partition_data_get_partition_column_count()`. * - * One live result per connection: this refuses with ERROR_RESOURCE_IN_USE while the connection already has a live - * result. Drain, destroy, or interrupt that one first, or open another connection. + * history: + * - stable: v2.0.0 * - * Schema metadata — result type, statement type, column count, names, logical types — is available on the returned - * handle immediately, before the first step. + * @param info The partition data info handle. + * @param required Receives whether partitioning column values are required. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_requires_partition_columns( + duckdb_v2_table_function_partition_data_info_handle info, bool *required, duckdb_v2_error_info_handle *err); + +/*! + * Returns the number of partitioning columns a downstream operator is requesting values for. * - * *out_result is set to nullptr on failure. + * `0` when `duckdb_v2_table_function_partition_data_requires_partition_columns()` is false. Valid indices for + * `duckdb_v2_table_function_partition_data_get_partition_column_index()` and + * `duckdb_v2_table_function_partition_data_set_partition_value()` are `0` up to (but excluding) this count. * * history: * - stable: v2.0.0 * - * @param conn The connection on which to execute the statement. - * @param statement The statement to execute. Borrowed and copied; not consumed. Destroy it with sql_statement_destroy. - * @param parameter_names Optional. An array of parameter_count parameter names; a non-empty entry binds its value to - * the named parameter ($name, case-insensitive), a {NULL, 0} entry keeps it positional ($1 = element 0). Pass NULL for - * all-positional binding. Named and positional parameters cannot be mixed within one statement. - * @param parameter_values Optional. An array of parameter_count value handles. Each binds by name when parameter_names - * supplies one, otherwise positionally ($1 = element 0). Borrowed (copied in). Pass NULL for an unparameterized - * statement. - * @param parameter_count The number of parameters in parameter_names and parameter_values. Pass 0 for an - * unparameterized statement. - * @param out_result Receives the new result handle. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partition data info handle. + * @param count Receives the number of requested partitioning columns. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_execute(duckdb_v2_connection_handle conn, - duckdb_v2_sql_statement_handle statement, - const duckdb_v2_identifier_t *parameter_names, - const duckdb_v2_value_handle *parameter_values, - idx_t parameter_count, duckdb_v2_result_handle *out_result, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_partition_column_count( + duckdb_v2_table_function_partition_data_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Destroys a result handle. + * Returns which declared column the requested partitioning column at the given index stands for. * - * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. Frees the memory the result owns and releases - * the connection for its next query. Safe at any point in the stream's life. Destroying a result before its statement - * ended (drained, or stepped to FINISHED) aborts the statement. On autocommit, its writes are rolled back. Inside a - * transaction, a statement that may write invalidates the transaction, and a read-only statement just stops. Chunks - * already fetched are caller-owned and stay valid. On success the slot is set to nullptr. + * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration + * order. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param result The result to destroy. + * @param info The partition data info handle. + * @param index The index of the requested partitioning column. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_destroy(duckdb_v2_result_handle *result); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_partition_column_index( + duckdb_v2_table_function_partition_data_info_handle info, idx_t index, idx_t *column_index, + duckdb_v2_error_info_handle *err); /*! - * Runs one bounded unit of query execution and returns without blocking. - * - * The streaming primitive: a step does a bounded amount of execution work, mostly without blocking, and returns control - * to the caller, so an event loop stays responsive and can interleave other work between steps. The conveniences — - * result_wait, result_fetch_chunk, result_drain — block, and are for synchronous callers. out_status reports the - * outcome: - * - * - CHUNK: *out_chunk receives a caller-owned chunk (destroy via data_chunk_destroy). Written only for this status; - * nullptr otherwise. - * - WAITING: no chunk yet, but work was done. Transient: keep stepping and it resolves to CHUNK, FINISHED, CANCELLED, - * or an error. Block in result_wait rather than busy-stepping. - * - FINISHED: stream exhausted. Sticky. - * - CANCELLED: query interrupted via connection_interrupt. Sticky. Cancellation is a status here, not an error; - * result_fetch_chunk, which has no status out-param, reports it as ERROR_RUNTIME_INTERRUPT. + * Reports the ordering position of the batch the exec callback just produced. * - * Execution errors come back as the return code plus err, never as a status; out_status is then unspecified and - * *out_chunk is nullptr. Errors are sticky, so later steps report the same code. + * Required on every call, whether or not `duckdb_v2_table_function_partition_data_requires_batch_index()` is true: the + * engine validates the reported value regardless. Not calling this before the callback returns fails the query; `0` is + * always a safe answer when the batch index is not actually required. Must not decrease across successive calls on the + * same thread, must be unique across threads for the ordering to be meaningful, must be less than roughly `10^13`, and, + * when partitioning column values are also being reported, must change whenever those values change: the engine only + * re-reads them when the batch index changes, so reporting a new value under an unchanged batch index is treated as a + * caller error rather than applied. Fails with `ERROR_INPUT_INVALID` for an out-of-range value; a decreasing value or + * an unchanged value paired with changed partitioning columns fails the query once the callback returns. * * history: * - stable: v2.0.0 * - * @param result The result to step. - * @param out_chunk Receives an owned chunk if *out_status is CHUNK; set to nullptr otherwise. - * @param out_status Receives the step status. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partition data info handle. + * @param batch_index The batch's ordering position. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_step(duckdb_v2_result_handle result, - duckdb_v2_data_chunk_handle *out_chunk, - DUCKDB_V2_RESULT_STEP_STATUS *out_status, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_set_batch_index( + duckdb_v2_table_function_partition_data_info_handle info, idx_t batch_index, duckdb_v2_error_info_handle *err); /*! - * Blocks until the next chunk is available and returns it. - * - * A convenience over result_step: blocks until a chunk is produced or the stream ends. On success *out_chunk receives a - * caller-owned chunk (destroy via data_chunk_destroy), or nullptr at end-of-stream. End-of-stream is sticky, so later - * calls keep succeeding with *out_chunk set to nullptr. - * - * An interrupted query returns ERROR_RUNTIME_INTERRUPT — the same event result_step reports as status CANCELLED, - * carried on the error channel because this function has no status out-param. + * Reports the single value of the partitioning column at the given index, for the batch the exec callback just + * produced. * - * On failure *out_chunk is set to nullptr. Errors are sticky, so later calls report the same code. + * Every partition reported this way carries exactly one distinct value for the column, so the value serves as both the + * minimum and maximum of the partition's range. The value is borrowed and copied, and must be of the declared type of + * the corresponding result column. Required once for every index in `[0, + * `duckdb_v2_table_function_partition_data_get_partition_column_count()`)` before the callback returns; calling it + * again for the same index overwrites the previous value. The reported value only takes effect together with a changed + * `duckdb_v2_table_function_partition_data_set_batch_index()`: reporting a different value under an unchanged batch + * index fails the query once the callback returns. Fails with `ERROR_INPUT_INVALID` when the index is out of bounds or + * the value's type does not match. * * history: * - stable: v2.0.0 * - * @param result The result to fetch from. - * @param out_chunk Receives an owned chunk, or nullptr at end-of-stream. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partition data info handle. + * @param index The index of the partitioning column being reported. + * @param value The single value of the partitioning column. Borrowed and copied. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_fetch_chunk(duckdb_v2_result_handle result, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_set_partition_value( + duckdb_v2_table_function_partition_data_info_handle info, idx_t index, duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); /*! - * Blocks until result_step can make progress. - * - * A convenience over result_step: blocks until a step is worth issuing again, that is, until a unit of execution work - * can run on the calling thread. Never produces or consumes chunks. Waiting on a terminal result — FINISHED, CANCELLED, - * or a sticky error — returns immediately; it is a no-op, never an error. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. * * history: * - stable: v2.0.0 * - * @param result The result to wait on. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partitioning info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_wait(duckdb_v2_result_handle result, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_user_data( + duckdb_v2_table_function_partitioning_info_handle info, void **data, duckdb_v2_error_info_handle *err); /*! - * Renders the result as a box table, consuming it. - * - * Drains the result into a column data collection and renders it with the same renderer the DuckDB CLI uses, so every - * client displays results identically without reimplementing table formatting. The result is consumed by transfer: the - * slot is set to NULL on success and on failure alike. A partially consumed result is accepted, and the remainder is - * what gets rendered. + * Retrieves the bind data set by the function's bind callback. * - * The whole remaining result materializes in memory before rendering. max_rows bounds what is DISPLAYED, not what is - * read, so with limit 0 the footer's row count is exact. A caller who cannot afford full materialization should bound - * the query itself (e.g. LIMIT n) and pass n as limit; the footer then renders "? rows" whenever the result fills that - * bound, since the true total is unknown at that point. + * history: + * - stable: v2.0.0 * - * Zero selects the renderer default for each sizing knob: max_rows 20, max_width the probed terminal width or 80 when - * that is unavailable, max_col_width 20. An empty null_value renders NULL cells as the default "NULL" text. + * @param info The partitioning info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR + */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_bind_data( + duckdb_v2_table_function_partitioning_info_handle info, void **data, duckdb_v2_error_info_handle *err); + +/*! + * Returns the number of columns the optimizer is asking about: the candidate `GROUP BY` column set. * - * The rendered text goes to sink rather than being returned: a box can be large, and this way nothing allocates a - * buffer the caller has to free. The sink is called exactly once with the whole box, so its view carries the full - * length — write it straight to a stream, or copy it into your own string. sink must not be NULL. + * Valid indices for `duckdb_v2_table_function_partitioning_get_partition_column_index()` are `0` up to (but excluding) + * this count. * * history: * - stable: v2.0.0 * - * @param result The result to render; consumed and set to NULL. Left intact only when the call rejects null arguments. - * @param max_rows Maximum rows displayed; 0 selects the renderer default (20). - * @param max_width Maximum total width in characters; 0 selects the renderer default. - * @param max_col_width Maximum width of one column; 0 selects the renderer default (20). - * @param null_value Text rendered for NULL cells; empty selects "NULL". - * @param render_mode 0 renders rows (records down the page); 1 renders columns; other values are rejected with - * INVALID_INPUT. - * @param limit The row limit the caller applied to the query before rendering; 0 means none. When the materialized - * result holds exactly this many rows the true total is unknown, so the footer renders "? rows" instead of an exact - * count. - * @param sink Receives the whole rendered box in a single call. Borrowed for the duration of that call; see text_sink - * for the full contract. - * @param user_data Opaque pointer passed through to sink untouched. May be NULL. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partitioning info handle. + * @param count Receives the number of columns in the candidate set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_render_box(duckdb_v2_result_handle *result, idx_t max_rows, - idx_t max_width, idx_t max_col_width, - const duckdb_v2_str *null_value, idx_t render_mode, - idx_t limit, duckdb_v2_text_sink_fn sink, void *user_data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_partition_column_count( + duckdb_v2_table_function_partitioning_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); /*! - * Runs the result to completion and reports the changed-row count. - * - * A convenience over result_step: blocks until the stream is fully consumed, so every side effect is applied. Rows of a - * row-producing result are discarded. For a CHANGED_ROWS result *out_rows_changed receives the affected row count; for - * every other result type, and for a stream whose Count chunk was already consumed, it receives 0. + * Returns which declared column the candidate `GROUP BY` column at the given index stands for. * - * The result type (result_get_result_type) is prepare-time metadata, so a caller can choose between consuming rows and - * draining without inspecting the SQL. Draining an already FINISHED result succeeds. Cancellation surfaces as - * ERROR_RUNTIME_INTERRUPT; errors are sticky, and on failure *out_rows_changed is unspecified. + * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration + * order. Fails if the index is out of bounds. * * history: * - stable: v2.0.0 * - * @param result The result to drain. - * @param out_rows_changed Receives the affected row count for CHANGED_ROWS results, 0 otherwise. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partitioning info handle. + * @param index The index of the candidate column. + * @param column_index Receives the index of the declared column. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_drain(duckdb_v2_result_handle result, idx_t *out_rows_changed, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_partition_column_index( + duckdb_v2_table_function_partitioning_info_handle info, idx_t index, idx_t *column_index, + duckdb_v2_error_info_handle *err); /*! - * Returns the shape of the result: query, changed rows, or nothing. - * - * QUERY_RESULT for statements that produce rows (SELECT, RETURNING, EXPLAIN), CHANGED_ROWS for an INSERT, UPDATE, or - * DELETE without RETURNING, NOTHING for DDL and other statements with no row output. + * Reports whether every partition the scan produces carries exactly one distinct value for the candidate `GROUP BY` + * column set. * - * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a - * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. + * Only `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS` unlocks the partitioned aggregate optimization; any other value + * keeps the regular hash aggregate. A callback that never calls this is treated as reporting + * `TABLE_PARTITION_INFO_NOT_PARTITIONED`. Fails with `ERROR_INPUT_INVALID` when partition_info is not one of the enum's + * declared values. * * history: * - stable: v2.0.0 * - * @param result The result. - * @param out_type Receives the result shape. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param info The partitioning info handle. + * @param partition_info Whether, and how, the scan is partitioned by the candidate column set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_result_type(duckdb_v2_result_handle result, - DUCKDB_V2_RESULT_TYPE *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_set_partition_info( + duckdb_v2_table_function_partitioning_info_handle info, DUCKDB_V2_TABLE_PARTITION_INFO partition_info, + duckdb_v2_error_info_handle *err); /*! - * Returns the SQL statement type that produced the result. + * Registers the table function, making it available for use in SQL queries. * - * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a - * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. Registration requires a name, a bind callback and an exec callback, and rejects a signature + * that declares a return type, since a table function declares the columns it returns from its bind callback. The table + * function must have been created through a factory of the same database. The caller still owns the handle after + * registration and must destroy it with `duckdb_v2_table_function_destroy()`, which does not affect the registered + * function. * * history: * - stable: v2.0.0 * - * @param result The result. - * @param out_type Receives the statement type. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param conn The connection to register on. + * @param function The table function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_statement_type(duckdb_v2_result_handle result, - DUCKDB_V2_STATEMENT_TYPE *out_type, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_table_function(duckdb_v2_connection_handle conn, + duckdb_v2_table_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * Returns the result's output schema as a single schema handle. - * - * Builds an owned schema of the result's column names and types into *out_schema; destroy it via schema_destroy. - * Prepare-time metadata, available before the first step, or for an expanding statement once stepping has prepared the - * row-producing fragment. Never empty: a non-SELECT reports a single status column, either a BIGINT changed-rows count - * or a BOOLEAN success. - * - * This schema is the authoritative source for the column types of the chunks the result produces; vectors do not carry - * their own type. + * Registers the table function, making it available for use in SQL queries. * - * *out_schema is set to NULL on failure. + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. Registration requires a name, a bind callback and an exec callback, and rejects a + * signature that declares a return type, since a table function declares the columns it returns from its bind callback. + * The table function must have been created through a factory of the same database. The caller still owns the handle + * after registration and must destroy it with `duckdb_v2_table_function_destroy()`, which does not affect the + * registered function. * * history: * - stable: v2.0.0 * - * @param result The result. - * @param out_schema Receives the owned output schema. Destroy via schema_destroy. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. + * @param extension The loading extension. + * @param function The table function to register. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_schema(duckdb_v2_result_handle result, - duckdb_v2_schema_handle *out_schema, - duckdb_v2_error_info_handle *err); - -/* --- Struct definitions for query_result --- */ - -/* ============================================================================ - * MODULE: table - * ============================================================================ */ - -/* --- Enums for table --- */ - -/*! - * Whether a table function's scan is partitioned by a given set of columns, and how. Reported by - * `duckdb_v2_table_function_set_partitioning_callback()` via - * `duckdb_v2_table_function_partitioning_set_partition_info()`. - */ -typedef enum DUCKDB_V2_TABLE_PARTITION_INFO { - //! The scan is not known to be partitioned by the requested columns. - DUCKDB_V2_TABLE_PARTITION_INFO_NOT_PARTITIONED = 0, - - //! Each partition the scan produces carries exactly one distinct value for the requested columns. - DUCKDB_V2_TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS = 1, - - //! The partitions the scan produces overlap only at their boundaries. - DUCKDB_V2_TABLE_PARTITION_INFO_OVERLAPPING_PARTITIONS = 2, - - //! The partitions the scan produces are disjoint ranges. - DUCKDB_V2_TABLE_PARTITION_INFO_DISJOINT_PARTITIONS = 3, - DUCKDB_V2_TABLE_PARTITION_INFO_MAX_ENUM = 0x7FFFFFFF, -} DUCKDB_V2_TABLE_PARTITION_INFO; - -/* --- Struct forward declarations for table --- */ - -/* --- Types for table --- */ - -/*! - * An owned opaque handle to a custom table function being built. Created with - * `duckdb_v2_table_function_create_with_connection()` or `duckdb_v2_table_function_create_with_extension()`, configured - * with the setter functions (e.g. `duckdb_v2_table_function_set_name()`, - * `duckdb_v2_table_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_table_function_get_signature()`, made available with `duckdb_v2_table_function_register()`, and destroyed - * with `duckdb_v2_table_function_destroy()`. - */ -typedef struct _duckdb_v2_table_function { - void *internal_ptr; -} * duckdb_v2_table_function_handle; - -/*! - * A borrowed opaque handle to the result of a table function's "bind" phase. The "bind" callback receives this handle - * next to a `duckdb_v2_function_bind_info_handle`, which gives access to the arguments, the user data and the bind - * data. It must use this handle to declare the columns the function returns, and can use it to hint at the number of - * rows the scan will produce. - */ -typedef struct _duckdb_v2_table_function_bind_info { - void *internal_ptr; -} * duckdb_v2_table_function_bind_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_table_function(duckdb_v2_extension_handle extension, + duckdb_v2_table_function_handle function, + duckdb_v2_error_info_handle *err); /*! - * A borrowed opaque handle to the arguments supplied to a table function during the global state initialization "init - * global" phase. The "init global" callback receives this handle and can use it to e.g. set up the state shared by - * every thread scanning the function, and to declare how many threads may scan it. + * Destroys the table function, releasing its resources. + * + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. + * + * history: + * - stable: v2.0.0 + * + * @param function The function to destroy. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_init_global_info { - void *internal_ptr; -} * duckdb_v2_table_function_init_global_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_destroy(duckdb_v2_table_function_handle *function); +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the local state initialization "init - * local" phase. The "init local" callback receives this handle and can use it to e.g. set up worker-local state, - * typically by claiming work from the shared global state. + * Sets the optional claim batch callback of the table function. + * + * With a claim batch callback, a scanning thread works through the scan one batch at a time: the callback claims the + * next batch of work (e.g. the next block of a file) for the thread's local state, typically from the shared global + * state, and the exec callback then produces the rows of that batch only, leaving the output chunk empty once the batch + * is exhausted. A batch is claimed before the exec callback first runs on a thread, and again whenever the exec + * callback leaves the output chunk empty; the scan ends for a thread once the claim batch callback does not claim a + * batch. + * + * Telling the batches apart lets a caller that scans several batches in parallel put their rows back in the order of + * the batches, e.g. a multi-file function registered with `duckdb_v2_connection_register_multi_file_function()` or + * `duckdb_v2_extension_register_multi_file_function()` claims the batches of the function itself, so that the rows of a + * file keep their order also when several threads scan it. + * + * The callback runs while the work of the scan is handed out to the threads, possibly while every other scanning thread + * waits for it. It should therefore only claim the work - e.g. reserve the number of the next block - and leave reading + * it to the exec callback. + * + * history: + * - unstable: v2.0.0 + * + * @param function The function to set the claim batch callback of. + * @param callback The claim batch callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_init_local_info { - void *internal_ptr; -} * duckdb_v2_table_function_init_local_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_claim_batch_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_claim_batch_callback_fn callback, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the execution "exec" phase. The "exec" - * callback receives this handle and can use it to e.g. access the global and local state and write the next batch of - * rows to the output chunk. + * Retrieves the user data set on the table function via `duckdb_v2_table_function_set_user_data()`. + * + * history: + * - unstable: v2.0.0 + * + * @param info The claim batch info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_exec_info { - void *internal_ptr; -} * duckdb_v2_table_function_exec_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_user_data( + duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the "progress" phase. The "progress" - * callback receives this handle and can use it to report how far the scan has advanced. + * Retrieves the bind data set by the function's bind callback. + * + * history: + * - unstable: v2.0.0 + * + * @param info The claim batch info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_progress_info { - void *internal_ptr; -} * duckdb_v2_table_function_progress_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_bind_data( + duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the "filter pushdown" phase of query - * optimization. The "filter pushdown" callback receives this handle and can use it to inspect the filter predicates the - * query applies to the function's rows, and to accept the ones it will apply itself. + * Retrieves the global state set by the function's global init callback. + * + * Shared with every other thread scanning the function; access to it must be synchronized by the function. + * + * history: + * - unstable: v2.0.0 + * + * @param info The claim batch info handle. + * @param data Receives the global state pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_filter_pushdown_info { - void *internal_ptr; -} * duckdb_v2_table_function_filter_pushdown_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_global_state( + duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the "partition data" phase. The engine - * invokes the callback on the worker thread that just produced a batch of rows, once per batch, only when a downstream - * operator needs to know the batch's ordering position, the values of a set of partitioning columns, or both; the - * handle reports which of those were actually requested and receives the answer. + * Retrieves the local state set by the function's local init callback, for the thread claiming a batch. + * + * history: + * - unstable: v2.0.0 + * + * @param info The claim batch info handle. + * @param data Receives the local state pointer for the claiming thread, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_partition_data_info { - void *internal_ptr; -} * duckdb_v2_table_function_partition_data_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_local_state( + duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function during the "partitioning" phase of query - * optimization. The engine invokes the callback once per candidate `GROUP BY` column set, before execution starts, to - * decide whether the scan can feed a partitioned aggregate directly instead of hashing. + * Reports whether the callback claimed a batch for the thread. + * + * A callback that returns without calling this claimed nothing, which ends the scan for the thread. + * + * history: + * - unstable: v2.0.0 + * + * @param info The claim batch info handle. + * @param claimed Whether a batch was claimed. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_partitioning_info { - void *internal_ptr; -} * duckdb_v2_table_function_partitioning_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_set_claimed( + duckdb_v2_table_function_claim_batch_info_handle info, bool claimed, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function when a scanning thread claims its next batch - * of work. The "claim batch" callback receives this handle and can use it to claim the next part of the scan from the - * shared global state for the thread's local state, and to report whether it claimed one. + * Sets the optional "get bind info" callback of the table function. + * + * The engine invokes the callback with the bind data of a bound call when it needs to know more about the call than its + * columns. E.g. a multi-file function registered with `duckdb_v2_connection_register_multi_file_function()` or + * `duckdb_v2_extension_register_multi_file_function()` asks the function it wraps for every file it binds: it maps the + * columns of the files onto the columns of its scan by the identifiers the callback reports, and exposes the options it + * reports as the metadata of the reader of the file. The callback may be invoked more than once for the same bind data, + * so it should only report what the bind already determined. + * + * history: + * - unstable: v2.0.0 + * + * @param function The function to set the callback of. + * @param callback The "get bind info" callback to set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_claim_batch_info { - void *internal_ptr; -} * duckdb_v2_table_function_claim_batch_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_get_bind_info_callback( + duckdb_v2_table_function_handle function, duckdb_v2_table_function_get_bind_info_callback_fn callback, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * A borrowed opaque handle to the arguments supplied to a table function when the engine asks it to describe a bound - * call. The "get bind info" callback receives this handle, and can use it to describe the call in more detail than its - * columns: with identifiers of its columns (e.g. field ids), and with options - key-value facts about what the call - * reads. + * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * + * history: + * - unstable: v2.0.0 + * + * @param info The get bind info handle. + * @param data Receives the user data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_table_function_get_bind_info_info { - void *internal_ptr; -} * duckdb_v2_table_function_get_bind_info_info_handle; +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_get_user_data( + duckdb_v2_table_function_get_bind_info_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * An owned opaque handle to a multi-file table function being built. A multi-file function wraps an already registered - * table function that reads a single file, and adds everything that is needed to read many files at once on top of it: - * globbing, lists of files, hive partitioning, the `filename` column, `union_by_name` and the like. Created with - * `duckdb_v2_multi_file_function_create_with_connection()` or `duckdb_v2_multi_file_function_create_with_extension()`, - * configured with the setter functions (e.g. `duckdb_v2_multi_file_function_set_name()`, - * `duckdb_v2_multi_file_function_set_single_file_function()`), made available with - * `duckdb_v2_multi_file_function_register()`, and destroyed with `duckdb_v2_multi_file_function_destroy()`. + * Retrieves the bind data the function's bind callback set for the bound call being described. + * + * history: + * - unstable: v2.0.0 + * + * @param info The get bind info handle. + * @param data Receives the bind data pointer, or null if none was set. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via + * `duckdb_v2_error_info_destroy()`. + * @return DUCKDB_V2_ERROR */ -typedef struct _duckdb_v2_multi_file_function { - void *internal_ptr; -} * duckdb_v2_multi_file_function_handle; - -/* --- Constants for table --- */ - -/* --- Function pointer typedefs for table --- */ - -typedef void (*duckdb_v2_table_function_bind_callback_fn)(duckdb_v2_function_bind_info_handle info, - duckdb_v2_table_function_bind_info_handle result, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_init_global_callback_fn)(duckdb_v2_table_function_init_global_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_init_local_callback_fn)(duckdb_v2_table_function_init_local_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_exec_callback_fn)(duckdb_v2_table_function_exec_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_progress_callback_fn)(duckdb_v2_table_function_progress_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_filter_pushdown_callback_fn)( - duckdb_v2_table_function_filter_pushdown_info_handle info, duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_partition_data_callback_fn)( - duckdb_v2_table_function_partition_data_info_handle info, duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_partitioning_callback_fn)( - duckdb_v2_table_function_partitioning_info_handle info, duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_claim_batch_callback_fn)(duckdb_v2_table_function_claim_batch_info_handle info, - duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -typedef void (*duckdb_v2_table_function_get_bind_info_callback_fn)( - duckdb_v2_table_function_get_bind_info_info_handle info, duckdb_v2_context_handle context, - duckdb_v2_error_info_handle *err); - -/* --- Functions for table --- */ +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_get_bind_data( + duckdb_v2_table_function_get_bind_info_info_handle info, void **data, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Creates a new table function that will be registered on the connection's database. + * Attaches an identifier to a result column of the bound call, or to a field nested inside one. * - * The function starts out empty: configure it with the setter functions (e.g. `duckdb_v2_table_function_set_name()`, - * `duckdb_v2_table_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_table_function_get_signature()`, then make it available with `duckdb_v2_table_function_register()`. The - * caller owns the returned handle and must destroy it with `duckdb_v2_table_function_destroy()`, also after - * registration. + * A multi-file reader can then map the columns of every file onto the columns of its scan by identifier rather than by + * name - e.g. by the field ids of a file format that carries them, so that renamed columns and fields are still found. + * An identifier is an INTEGER field id or a VARCHAR name. + * + * The nested field is addressed by a path of child indexes, starting at the result column at `column_index`: a STRUCT + * field by its index, the elements of a LIST or ARRAY by `0`, the keys of a MAP by `0` and its values by `1`, and a + * UNION member by its index. An empty path addresses the result column itself. Setting an identifier again for the same + * column or field replaces it. Fails with `ERROR_INPUT_INVALID` when the column index is out of range, or the path does + * not address a nested field of the column's type. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created table function. Owned by the caller. + * @param info The get bind info handle. + * @param column_index The index of the result column, in the order the bind callback declared the columns. + * @param child_path The path of child indexes addressing the nested field, or null for the column itself. + * @param child_path_length The number of entries in the path, 0 for the column itself. + * @param identifier The identifier, an INTEGER or VARCHAR value. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_set_column_identifier( + duckdb_v2_table_function_get_bind_info_info_handle info, idx_t column_index, const idx_t *child_path, + idx_t child_path_length, duckdb_v2_value_handle identifier, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Creates a new table function that will be registered on the loading extension's database. + * Sets an option describing the bound call: a key-value fact about what the call reads, e.g. a property of the file it + * reads. Setting a key again replaces its value. * - * Use this from an extension load callback, where an extension handle is available. The function starts out empty: - * configure it with the setter functions (e.g. `duckdb_v2_table_function_set_name()`, - * `duckdb_v2_table_function_set_exec_callback()`, etc.) and the signature obtained via - * `duckdb_v2_table_function_get_signature()`, then make it available with `duckdb_v2_table_function_register()`. The - * caller owns the returned handle and must destroy it with `duckdb_v2_table_function_destroy()`, also after - * registration. + * A multi-file function registered with `duckdb_v2_connection_register_multi_file_function()` or + * `duckdb_v2_extension_register_multi_file_function()` exposes the options of the function it wraps as the metadata of + * the reader of each file, e.g. to a custom multi-file reader of another extension. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param extension The extension to create the function in. - * @param function On success, receives the newly created table function. Owned by the caller. + * @param info The get bind info handle. + * @param key The key of the option. Borrowed and copied. + * @param value The value of the option. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_set_option( + duckdb_v2_table_function_get_bind_info_info_handle info, duckdb_v2_str *key, duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the name of the table function. + * Creates a new multi-file function through a factory. * - * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * The function starts out empty: give it a name with `duckdb_v2_multi_file_function_set_name()` and the single-file + * function it wraps with `duckdb_v2_multi_file_function_set_single_file_function()`, then make it available with + * `duckdb_v2_connection_register_multi_file_function()` or `duckdb_v2_extension_register_multi_file_function()`. The + * caller owns the returned handle and must destroy it with `duckdb_v2_multi_file_function_destroy()`, also after * registration. * + * The multi-file function can only be registered on the database the factory belongs to. + * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the name of. - * @param name The name to set. Borrowed and copied. + * @param factory The factory to create the multi-file function through. + * @param function On success, receives the newly created multi-file function. Owned by the caller. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_name(duckdb_v2_table_function_handle function, - const duckdb_v2_identifier_t *name, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_multi_file_function_handle *function, + duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Returns the function's signature so it can be configured. + * Sets the name of the multi-file function, as SQL will call it. * - * Add parameters with `duckdb_v2_function_signature_add_parameter()`. The signature is modified in place. A table - * function declares the columns it returns from its bind callback instead of through a return type, so registration - * rejects a signature whose return type was set with `duckdb_v2_function_signature_set_return_type()`. + * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before + * registration. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to get the signature of. - * @param sig The returned signature. Borrowed and valid for the lifetime of the function handle. + * @param function The function to set the name of. + * @param name The name to set. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_signature(duckdb_v2_table_function_handle function, - duckdb_v2_function_signature_handle *sig, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_name(duckdb_v2_multi_file_function_handle function, + duckdb_v2_str *name, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets arbitrary user data on the table function. + * Sets the name of the table function that reads a single file, which the multi-file function wraps. * - * Associates an opaque pointer with the function, retrievable from the callbacks via - * `duckdb_v2_function_bind_get_user_data()`, `duckdb_v2_table_function_init_global_get_user_data()`, - * `duckdb_v2_table_function_exec_get_user_data()` and their counterparts on the other phases. The opaque handle bundles - * the pointer with an optional destructor, invoked when the data is no longer needed. + * The single-file function must be registered before the multi-file function is, and must take the file to read as its + * only positional parameter, of type ANY. It is bound once for every file that is read, and receives the file the way a + * multi-file function takes one: as the VARCHAR path of the file when there is nothing more to know about it, and + * otherwise as a STRUCT holding the path in its `filename` field and the options to open the file with in its other + * fields - e.g. the size of the file, as a file system reported it while globbing, or the options a caller passed along + * with the file (`read_x([{'filename': 'f.x', 'file_size': 42}])`). Opening the file with those options, set with + * `duckdb_v2_file_open_options_set_value()`, lets the file system skip work it would otherwise repeat, such as asking a + * remote store for the size of the file. A single-file function that takes a VARCHAR parameter instead is still + * accepted, and only receives the path. Every named parameter the single-file function declares is also a named + * parameter of the multi-file function, forwarded to it as given. Everything that involves several files - globbing, + * lists of files, hive partitioning, the `filename` column, `union_by_name` and the like - is provided by the + * multi-file function on top of it. The name is borrowed and copied. A single-file function must be set before + * registration. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the user data of. - * @param data Opaque handle bundling the user data pointer plus an optional destructor. + * @param function The function to set the single-file function of. + * @param name The name of the registered single-file table function. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_user_data(duckdb_v2_table_function_handle function, - duckdb_v2_opaque *data, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_single_file_function( + duckdb_v2_multi_file_function_handle function, duckdb_v2_str *name, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the bind callback of the table function. + * Sets how the files the function reads are referred to in messages, e.g. "Avro" or "JSON". * - * The bind callback is invoked during query planning for each call site of the function. It must declare the columns - * the function returns via `duckdb_v2_table_function_bind_add_result_column()`. Through its - * `duckdb_v2_function_bind_info_handle` it can also inspect the constant argument values and set "bind data" that is - * shared with all later callbacks. A bind callback must be set before registration. + * Optional: without it, the files are referred to by the name of the multi-file function. The name is borrowed and + * copied. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the bind callback of. - * @param callback The bind callback to set. + * @param function The function to set the reader type of. + * @param reader_type The reader type. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_bind_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_bind_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_reader_type( + duckdb_v2_multi_file_function_handle function, duckdb_v2_str *reader_type, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the optional global init callback of the table function. + * Sets the file extension of the files the function reads, e.g. "avro". * - * The global init callback is invoked once per scan, at the start of execution. It can set "global state" shared by - * every thread scanning the function, and declare how many threads may scan it in parallel via - * `duckdb_v2_table_function_init_global_set_max_threads()`. + * Optional. With a file extension, a path that names a directory rather than a file reads the files with that extension + * inside it. Without one, the paths passed to the function must match at least one file. The extension is given without + * a leading dot, and is borrowed and copied. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the global init callback of. - * @param callback The global init callback to set. + * @param function The function to set the file extension of. + * @param extension The file extension, without a leading dot. Borrowed and copied. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_init_global_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_init_global_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_file_extension( + duckdb_v2_multi_file_function_handle function, duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the optional local init callback of the table function. + * Registers the multi-file function, making it available for use in SQL queries. * - * The local init callback is invoked once per thread that will scan the function. It can set worker-local "local - * state", retrievable from the exec callback, typically derived from the shared global state. + * The function is registered on the connection's database, visible to every connection, in the connection's + * transaction: inside an explicit transaction, it is invisible to other connections until the transaction commits, and + * a rollback undoes it. It can then be called with the path of a single file, a glob pattern, or a list of either, and + * accepts the options every multi-file function accepts (e.g. `filename`, `hive_partitioning`, `union_by_name`) next to + * the named parameters of the single-file function. Registration requires a name and a single-file function, and fails + * when no table function by that name is registered or it does not take the path of the file to read as its only + * positional parameter (see `duckdb_v2_multi_file_function_set_single_file_function()`). The multi-file function must + * have been created through a factory of the same database. The caller still owns the handle after registration and + * must destroy it with `duckdb_v2_multi_file_function_destroy()`, which does not affect the registered function. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the local init callback of. - * @param callback The local init callback to set. + * @param conn The connection to register on. + * @param function The multi-file function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_init_local_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_init_local_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_connection_register_multi_file_function( + duckdb_v2_connection_handle conn, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the exec callback of the table function. + * Registers the multi-file function, making it available for use in SQL queries. * - * The exec callback implements the function's logic: it is invoked repeatedly during query execution and writes the - * next batch of rows to the output chunk, until it produces an empty batch to signal the end of the scan. An exec - * callback must be set before registration. + * The function is registered on the loading extension's database and attributed to the extension. Use this from the + * extension's load callback. It can then be called with the path of a single file, a glob pattern, or a list of either, + * and accepts the options every multi-file function accepts (e.g. `filename`, `hive_partitioning`, `union_by_name`) + * next to the named parameters of the single-file function. Registration requires a name and a single-file function, + * and fails when no table function by that name is registered or it does not take the path of the file to read as its + * only positional parameter (see `duckdb_v2_multi_file_function_set_single_file_function()`). The multi-file function + * must have been created through a factory of the same database. The caller still owns the handle after registration + * and must destroy it with `duckdb_v2_multi_file_function_destroy()`, which does not affect the registered function. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the exec callback of. - * @param callback The exec callback to set. + * @param extension The loading extension. + * @param function The multi-file function to register. * @param err Optional. On failure, receives an opaque info handle the caller must destroy via * `duckdb_v2_error_info_destroy()`. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_exec_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_exec_callback_fn callback, +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_extension_register_multi_file_function( + duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle function, duckdb_v2_error_info_handle *err); +#endif +#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the optional progress callback of the table function. + * Destroys the multi-file function, releasing its resources. * - * The progress callback is invoked on demand during execution to report how far the scan has advanced, which the engine - * surfaces as the query's progress. It is invoked concurrently with the exec callback, so it must read the global state - * in a thread-safe way. + * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent + * double-destruction. Destroying the handle after registration does not affect the registered function. * * history: - * - stable: v2.0.0 + * - unstable: v2.0.0 * - * @param function The function to set the progress callback of. - * @param callback The progress callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param function The function to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_progress_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_progress_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_destroy(duckdb_v2_multi_file_function_handle *function); +#endif + +/* --- Struct definitions for table --- */ + +/* ============================================================================ + * MODULE: value + * ============================================================================ */ + +/* --- Enums for value --- */ + +/* --- Struct forward declarations for value --- */ + +/* --- Types for value --- */ + +/* --- Constants for value --- */ + +/* --- Function pointer typedefs for value --- */ + +/* --- Functions for value --- */ /*! - * Sets whether the table function supports projection pushdown. Defaults to false. + * Destroys a value handle. * - * With projection pushdown, the engine asks the function for only the columns a query actually uses: the exec - * callback's output chunk holds one vector per requested column rather than one per column declared in bind, and the - * global init, local init and exec callbacks can look up which declared column each vector stands for via e.g. - * `duckdb_v2_table_function_exec_get_column_index()`. Without it the output chunk always holds every declared column, - * and the engine drops the unused ones itself. + * Null-safe: passing nullptr or a slot already set to nullptr is a no-op. On success the slot is set to nullptr. * * history: * - stable: v2.0.0 * - * @param function The function to configure. - * @param enable Whether the function supports projection pushdown. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_projection_pushdown(duckdb_v2_table_function_handle function, - bool enable, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_destroy(duckdb_v2_value_handle *value); /*! - * Sets the optional filter pushdown callback of the table function. + * Returns the value as a BOOLEAN. * - * The filter pushdown callback is invoked while the query is optimized, after the bind callback and before any init - * callback, with the filter predicates the query applies to the function's rows. Each predicate is a bound expression - * the callback can inspect with the `expression` functions; the callback accepts the predicates it will apply itself - * via `duckdb_v2_table_function_filter_pushdown_accept()`, typically after recording what they select in the bind data. - * The engine then stops applying the accepted predicates and keeps applying the rest above the scan, so accepting a - * predicate is a promise to filter the rows exactly as it would have. The optimizer may invoke the callback more than - * once for the same query, each time with the predicates not yet accepted; it is not invoked when there are none. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param function The function to set the filter pushdown callback of. - * @param callback The filter pushdown callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a bool. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_filter_pushdown_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_filter_pushdown_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_bool(duckdb_v2_value_handle value, bool *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional "partition data" callback of the table function. + * Returns the value as a UTINYINT. * - * The callback reports, for the batch the exec callback just produced, an ordering batch index, the values of a set of - * partitioning columns, or both, depending on what a downstream operator requires; it runs on the worker thread that - * produced the batch. Set `duckdb_v2_table_function_set_partitioning_callback()` too when the function can answer - * partitioning-column requests, since the engine only asks a scan for those when that callback reports - * `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS`. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param function The function to set the partition data callback of. - * @param callback The partition data callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a uint8_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_partition_data_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_partition_data_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_utinyint(duckdb_v2_value_handle value, uint8_t *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the optional "partitioning" callback of the table function. + * Returns the value as a USMALLINT. * - * The callback tells the query optimizer, for a candidate `GROUP BY` column set, whether every partition the scan - * produces carries exactly one distinct value for those columns; only `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS` - * unlocks the partitioned aggregate optimization; any other result, or leaving this callback unset, keeps the regular - * hash aggregate. It runs on the planning thread, before execution starts, and must be deterministic for a given column - * set since it may be called on a plan the optimizer later discards. Registration fails unless - * `duckdb_v2_table_function_set_partition_data_callback()` is set too, since the engine asks that callback for the - * partitioning column values once this one claims single-value partitions. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param function The function to set the partitioning callback of. - * @param callback The partitioning callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a uint16_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_partitioning_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_partitioning_callback_fn callback, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_usmallint(duckdb_v2_value_handle value, uint16_t *out, + duckdb_v2_error_info_handle *err); /*! - * Declares one of the columns the function returns. + * Returns the value as a UINTEGER. * - * Call this once per column, in order: the columns declared here are the columns of the table the function produces, - * and the vectors of the output chunk the exec callback fills follow the same order. At least one column must be - * declared. The type must be a fully defined concrete type; ANY is rejected, as a result column carries data. The name - * and type are borrowed and copied. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param name The name of the column. Borrowed and copied. - * @param type The type of the column. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a uint32_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_add_result_column( - duckdb_v2_table_function_bind_info_handle info, const duckdb_v2_identifier_t *name, - duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uint(duckdb_v2_value_handle value, uint32_t *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the estimated number of rows the scan will produce. + * Returns the value as a UBIGINT. * - * The estimate is a hint for the optimizer, not a limit: producing a different number of rows is not an error. Without - * it, the optimizer falls back on its own defaults. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param cardinality The estimated number of rows. - * @param is_exact Whether the estimate is exact, which also makes it an upper bound on the row count. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a uint64_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_set_cardinality( - duckdb_v2_table_function_bind_info_handle info, idx_t cardinality, bool is_exact, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_ubigint(duckdb_v2_value_handle value, uint64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the order guarantee of the rows this call of the function produces. Defaults to - * `ORDER_PRESERVATION_INSERTION_ORDER`. + * Returns the value as a UHUGEINT. * - * With `ORDER_PRESERVATION_NO_ORDER`, a scan that reports more than one thread via - * `duckdb_v2_table_function_init_global_set_max_threads()` runs in parallel into query results, INSERT and COPY without - * a `duckdb_v2_table_function_set_partition_data_callback()`, and the rows arrive in no particular order. With - * insertion order kept, those consumers need the partition data callback to run in parallel. Fails with - * `ERROR_INPUT_INVALID` when order is not one of the enum's declared values. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The bind info handle. - * @param order The order guarantee of the produced rows. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a uint128_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_bind_set_order_preservation( - duckdb_v2_table_function_bind_info_handle info, DUCKDB_V2_ORDER_PRESERVATION order, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uhugeint(duckdb_v2_value_handle value, duckdb_v2_uhugeint_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Returns the value as a TINYINT. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as an int8_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_user_data( - duckdb_v2_table_function_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_tinyint(duckdb_v2_value_handle value, int8_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Returns the value as a SMALLINT. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as an int16_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_bind_data( - duckdb_v2_table_function_init_global_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_smallint(duckdb_v2_value_handle value, int16_t *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's "global state" from the global init callback. + * Returns the value as an INTEGER. * - * The global state lives for the duration of the scan and is retrievable from the local init, exec and progress - * callbacks. Every thread scanning the function shares it, so the function must synchronize its own access to it. The - * opaque handle bundles the pointer with an optional destructor, invoked when the scan is done with the state. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param data Opaque handle bundling the global state pointer plus an optional destructor. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as an int32_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_set_global_state( - duckdb_v2_table_function_init_global_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_int(duckdb_v2_value_handle value, int32_t *out, + duckdb_v2_error_info_handle *err); /*! - * Sets how many threads may scan the function in parallel. + * Returns the value as a BIGINT. * - * Defaults to 1, a single-threaded scan. The engine creates at most this many local states, and therefore runs at most - * this many exec callbacks concurrently. It is an upper bound, not a request: the engine may use fewer threads. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param max_threads The maximum number of threads. Must be at least 1. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as an int64_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_set_max_threads( - duckdb_v2_table_function_init_global_info_handle info, idx_t max_threads, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_bigint(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the scan produces. + * Returns the value as a HUGEINT. * - * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the - * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of - * columns declared in bind. Valid indices for `duckdb_v2_table_function_init_global_get_column_index()` are `0` up to - * (but excluding) this count. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param count Receives the number of columns the scan produces. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as an int128_t. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_column_count( - duckdb_v2_table_function_init_global_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_hugeint(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns which declared column the scan's column at the given index stands for. + * Returns the value as a VARCHAR. * - * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration - * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection - * pushdown the mapping is the identity. Fails if the index is out of bounds. + * The string is borrowed from the value and stays valid until the value is destroyed. That borrow is why this getter + * does not convert: a converted copy would not outlive the call. A value of any other type id, and a NULL value, return + * ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param info The global init info handle. - * @param index The index of the column in the scan's output. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a UTF-8 string. The returned string is borrowed and valid until the value handle + * is destroyed. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_global_get_column_index( - duckdb_v2_table_function_init_global_info_handle info, idx_t index, idx_t *column_index, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_varchar(duckdb_v2_value_handle value, duckdb_v2_str *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Returns the value as a byte string: a BLOB, or the storage bytes of a BIT or BIGNUM. + * + * The string is borrowed from the value and stays valid until the value is destroyed. That borrow is why this getter + * does not convert: a converted copy would not outlive the call. A value of any other type id, and a NULL value, return + * ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a byte string. The returned string is borrowed and valid until the value handle is + * destroyed. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_user_data( - duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_blob(duckdb_v2_value_handle value, duckdb_v2_str *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Returns the value as a FLOAT. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a float. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_bind_data( - duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_float(duckdb_v2_value_handle value, float *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Returns the value as a DOUBLE. * - * Shared with every other thread scanning the function; access to it must be synchronized by the function. + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Receives the global state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload as a double. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_global_state( - duckdb_v2_table_function_init_local_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_double(duckdb_v2_value_handle value, double *out, + duckdb_v2_error_info_handle *err); /*! - * Sets the function's worker-local "local state" from the local init callback. + * Returns the value as a DATE. * - * The local state is associated with the executing thread for the duration of the scan and retrievable from the exec - * callback via `duckdb_v2_table_function_exec_get_local_state()`. No other thread observes it, so it needs no - * synchronization. The opaque handle bundles the pointer with an optional destructor, invoked when the local state is - * no longer needed. + * The payload is days since 1970-01-01, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param data Opaque handle bundling the local state pointer plus an optional destructor. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_set_local_state( - duckdb_v2_table_function_init_local_info_handle info, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_date(duckdb_v2_value_handle value, int32_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the scan produces. + * Returns the value as a TIME. * - * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the - * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of - * columns declared in bind. Valid indices for `duckdb_v2_table_function_init_local_get_column_index()` are `0` up to - * (but excluding) this count. + * The payload is microseconds since midnight, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param count Receives the number of columns the scan produces. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_init_local_get_column_count( - duckdb_v2_table_function_init_local_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns which declared column the scan's column at the given index stands for. + * Returns the value as a TIME_NS. * - * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration - * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection - * pushdown the mapping is the identity. Fails if the index is out of bounds. + * The payload is nanoseconds since midnight, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The local init info handle. - * @param index The index of the column in the scan's output. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR -duckdb_v2_table_function_init_local_get_column_index(duckdb_v2_table_function_init_local_info_handle info, idx_t index, - idx_t *column_index, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time_ns(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Returns the value as a TIME_TZ. + * + * The payload packs the time of day and the UTC offset into one integer, in the committed layout: micros in the high 40 + * bits, a biased offset in the low 24. The same form a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_user_data(duckdb_v2_table_function_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_time_tz(duckdb_v2_value_handle value, uint64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Returns the value as a TIMESTAMP. + * + * The payload is microseconds since 1970-01-01, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_bind_data(duckdb_v2_table_function_exec_info_handle info, - void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Returns the value as a TIMESTAMP_SEC. * - * Shared with every other thread scanning the function; access to it must be synchronized by the function. + * The payload is seconds since 1970-01-01, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the global state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_global_state( - duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_sec(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the worker-local local state set by the function's local init callback. + * Returns the value as a TIMESTAMP_MS. + * + * The payload is milliseconds since 1970-01-01, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param data Receives the local state pointer for the executing thread, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_local_state( - duckdb_v2_table_function_exec_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_ms(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the output chunk the exec callback must write the next batch of rows into. + * Returns the value as a TIMESTAMP_NS. * - * The chunk holds one vector per column declared with `duckdb_v2_table_function_bind_add_result_column()`, in the same - * order; reach them with `duckdb_v2_data_chunk_get_vector()`. The chunk starts out empty on every invocation: write the - * rows, then declare how many there are with `duckdb_v2_vector_set_size()` on the first vector, which the engine takes - * as the batch's row count and propagates to the other vectors. Producing an empty batch signals the end of the scan, - * after which the callback is not invoked again on that thread. Borrowed; valid only for the duration of the callback. + * The payload is nanoseconds since 1970-01-01, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param chunk Receives the borrowed output chunk to write into. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR -duckdb_v2_table_function_exec_get_output_chunk(duckdb_v2_table_function_exec_info_handle info, - duckdb_v2_data_chunk_handle *chunk, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_ns(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the scan produces. + * Returns the value as a TIMESTAMP_TZ. * - * With projection pushdown (see `duckdb_v2_table_function_set_projection_pushdown()`) this is the number of columns the - * query uses, which is the number of vectors in the exec callback's output chunk; without it, it is the number of - * columns declared in bind. Valid indices for `duckdb_v2_table_function_exec_get_column_index()` are `0` up to (but - * excluding) this count. + * The payload is microseconds since 1970-01-01, in UTC, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param count Receives the number of columns the scan produces. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_column_count( - duckdb_v2_table_function_exec_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_tz(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Returns which declared column the scan's column at the given index stands for. + * Returns the value as a TIMESTAMP_TZ_NS. * - * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration - * order: the exec callback fills the output chunk's vector at `index` with that column's data. Without projection - * pushdown the mapping is the identity. Fails if the index is out of bounds. + * The payload is nanoseconds since 1970-01-01, in UTC, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The exec info handle. - * @param index The index of the column in the scan's output. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_exec_get_column_index( - duckdb_v2_table_function_exec_info_handle info, idx_t index, idx_t *column_index, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_timestamp_tz_ns(duckdb_v2_value_handle value, int64_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Returns the value as an INTERVAL. + * + * The payload is the (months, days, micros) triple, the same unit a vector of this type exposes. + * + * A value of a different type is converted through the default cast set, on a copy, so reading never alters the value. + * An unsupported conversion returns ERROR_INPUT_INVALID, as does a NULL value. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the payload. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_user_data( - duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_interval(duckdb_v2_value_handle value, duckdb_v2_interval_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Returns the value as a UUID, in its internal 128-bit form. + * + * The dual of value_create_uuid: the storage form a vector element holds, with the high bit flipped so the integer + * sorts, rather than the canonical byte order. A value of any other type id, and a NULL value, return + * ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the internal 128-bit storage form. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_bind_data( - duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_uuid(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Returns the value as a DECIMAL: its backing integer plus the width and scale of its type. * - * The progress callback runs concurrently with the exec callbacks scanning the function, so it must read the global - * state in a thread-safe way. + * The integer is the value scaled by 10^scale — the dual of value_create_decimal — widened to 128 bits from whatever + * storage tier the width selects. The integer getters convert, so a DECIMAL read through value_get_bigint is its + * numeric value with the fraction dropped; this one always reports storage instead, together with the scale needed to + * interpret it. A value of any other type id, and a NULL value, return ERROR_INPUT_INVALID. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param data Receives the global state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out Receives the backing integer, scaled by 10^scale. + * @param out_width Receives the total digit count of the value's type. + * @param out_scale Receives the digits after the decimal point. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_get_global_state( - duckdb_v2_table_function_progress_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_decimal(duckdb_v2_value_handle value, duckdb_v2_hugeint_t *out, + uint8_t *out_width, uint8_t *out_scale, + duckdb_v2_error_info_handle *err); /*! - * Reports how far the scan has advanced. + * Unwraps the logical type carried by a TYPE value. * - * A fraction between 0.0 (nothing scanned yet) and 1.0 (done); values outside that range are clamped. A progress - * callback that returns without calling this reports no progress. + * Returns ERROR_INPUT_INVALID unless the value is a non-NULL TYPE value. The returned logical type is caller-owned and + * must be destroyed via logical_type_destroy. * * history: * - stable: v2.0.0 * - * @param info The progress info handle. - * @param progress The fraction of the scan that is complete, in [0.0, 1.0]. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out_type Receives the owned logical type. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_progress_set_progress( - duckdb_v2_table_function_progress_info_handle info, double progress, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_type(duckdb_v2_value_handle value, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`, or null if none was set. + * Creates a NULL value of the given logical type. + * + * The logical type is borrowed and copied into the value, so the caller can destroy it independently. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param data Receives the user data pointer. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param type The borrowed logical type to attach to the NULL value. + * @param out_value Receives the new NULL value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_user_data( - duckdb_v2_table_function_filter_pushdown_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_null(duckdb_v2_factory_handle factory, + duckdb_v2_logical_type_handle type, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set via `duckdb_v2_function_bind_set_bind_data()`, or null if none was set. + * Creates a BOOLEAN value from a bool. * - * This is the same object the init and exec callbacks later receive, so a predicate the callback accepts can be - * recorded in it for the scan to apply. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param data Receives the bind data pointer. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The bool to wrap. + * @param out_value Receives the new BOOLEAN value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_bind_data( - duckdb_v2_table_function_filter_pushdown_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bool(duckdb_v2_factory_handle factory, bool in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of filter predicates offered to the function. + * Creates a UTINYINT value from a uint8_t. * - * The predicates are combined with `AND`: every row the scan produces must satisfy all of them. Valid indices for - * `duckdb_v2_table_function_filter_pushdown_get_filter()` and `duckdb_v2_table_function_filter_pushdown_accept()` are - * `0` up to (but excluding) this count. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param count Receives the number of predicates. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The uint8_t to wrap. + * @param out_value Receives the new UTINYINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_filter_count( - duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint(duckdb_v2_factory_handle factory, uint8_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the filter predicate at the given index. + * Creates a USMALLINT value from a uint16_t. * - * Fails if the index is out of bounds. The predicate is a bound expression evaluating to `BOOLEAN`; inspect it with - * `duckdb_v2_expression_get_type()` and the other `expression` functions, and resolve the column references it contains - * via `duckdb_v2_table_function_filter_pushdown_get_column_index()`. Borrowed; valid only for the duration of the - * callback. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param index The index of the predicate to retrieve. - * @param filter Receives the borrowed predicate. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The uint16_t to wrap. + * @param out_value Receives the new USMALLINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_filter( - duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, duckdb_v2_expression_handle *filter, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint(duckdb_v2_factory_handle factory, uint16_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Accepts the filter predicate at the given index: the function will apply it itself. + * Creates a UINTEGER value from a uint32_t. * - * The engine stops applying an accepted predicate, so the scan must produce only rows that satisfy it, or the query - * returns rows it should not. Predicates left unaccepted are applied by the engine as usual, so a callback that - * recognizes nothing can simply return. Fails if the index is out of bounds. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param index The index of the predicate to accept. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The uint32_t to wrap. + * @param out_value Receives the new UINTEGER value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_accept( - duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uint(duckdb_v2_factory_handle factory, uint32_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the predicates can refer to. + * Creates a UBIGINT value from a uint64_t. * - * A column reference inside a predicate (`duckdb_v2_expression_column_ref_get_index()`) indexes the columns the query - * reads from the function, not the columns declared in bind. Valid indices for - * `duckdb_v2_table_function_filter_pushdown_get_column_index()` are `0` up to (but excluding) this count. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param count Receives the number of referable columns. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The uint64_t to wrap. + * @param out_value Receives the new UBIGINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_column_count( - duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint(duckdb_v2_factory_handle factory, uint64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Resolves a column reference found in a predicate to the declared column it refers to. + * Creates a UHUGEINT value from a uint128_t. * - * Takes the index a `EXPRESSION_TYPE_BOUND_COLUMN_REF` node reports and returns the index of the column among the ones - * declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration order. Fails if the index is out of - * bounds. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The filter pushdown info handle. - * @param index The column index reported by a column reference node. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The uint128_t to wrap. + * @param out_value Receives the new UHUGEINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_filter_pushdown_get_column_index( - duckdb_v2_table_function_filter_pushdown_info_handle info, idx_t index, idx_t *column_index, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint(duckdb_v2_factory_handle factory, + const duckdb_v2_uhugeint_t *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Creates a TINYINT value from an int8_t. + * + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The int8_t to wrap. + * @param out_value Receives the new TINYINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_user_data( - duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint(duckdb_v2_factory_handle factory, int8_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Creates a SMALLINT value from an int16_t. + * + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The int16_t to wrap. + * @param out_value Receives the new SMALLINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_bind_data( - duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_smallint(duckdb_v2_factory_handle factory, int16_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the global state set by the function's global init callback. + * Creates an INTEGER value from an int32_t. * - * Shared with every other thread scanning the function; access to it must be synchronized by the function. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param data Receives the global state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The int32_t to wrap. + * @param out_value Receives the new INTEGER value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_global_state( - duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_int(duckdb_v2_factory_handle factory, int32_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the worker-local local state set by the function's local init callback, for the thread that produced the - * batch this call reports on. No other thread observes it, so it needs no synchronization. + * Creates a BIGINT value from an int64_t. + * + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param data Receives the local state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The int64_t to wrap. + * @param out_value Receives the new BIGINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_local_state( - duckdb_v2_table_function_partition_data_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bigint(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns whether a downstream operator needs this batch's ordering position. + * Creates a HUGEINT value from an int128_t. * - * `duckdb_v2_table_function_partition_data_set_batch_index()` must still be called even when this is false: the engine - * validates the reported value regardless of whether it is actually used for ordering, so `0` is always a safe answer - * when this reports false. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param required Receives whether a batch index is required. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The int128_t to wrap. + * @param out_value Receives the new HUGEINT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_requires_batch_index( - duckdb_v2_table_function_partition_data_info_handle info, bool *required, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint(duckdb_v2_factory_handle factory, + const duckdb_v2_hugeint_t *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns whether a downstream operator needs this batch's values for a set of partitioning columns. + * Creates a VARCHAR value from a UTF-8 string. * - * When true, call `duckdb_v2_table_function_partition_data_set_partition_value()` once for every index in `[0, count)`, - * where `count` comes from `duckdb_v2_table_function_partition_data_get_partition_column_count()`. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param required Receives whether partitioning column values are required. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The UTF-8 string to wrap. May be null only when len is 0 (empty string). + * @param out_value Receives the new VARCHAR value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_requires_partition_columns( - duckdb_v2_table_function_partition_data_info_handle info, bool *required, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_varchar(duckdb_v2_factory_handle factory, + const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of partitioning columns a downstream operator is requesting values for. + * Creates a BLOB value from a byte string. * - * `0` when `duckdb_v2_table_function_partition_data_requires_partition_columns()` is false. Valid indices for - * `duckdb_v2_table_function_partition_data_get_partition_column_index()` and - * `duckdb_v2_table_function_partition_data_set_partition_value()` are `0` up to (but excluding) this count. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param count Receives the number of requested partitioning columns. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The byte string to wrap. May be null only when len is 0 (empty string). + * @param out_value Receives the new BLOB value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_partition_column_count( - duckdb_v2_table_function_partition_data_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_blob(duckdb_v2_factory_handle factory, + const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns which declared column the requested partitioning column at the given index stands for. + * Creates a FLOAT value from a float. * - * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration - * order. Fails if the index is out of bounds. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param index The index of the requested partitioning column. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The float to wrap. + * @param out_value Receives the new FLOAT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_get_partition_column_index( - duckdb_v2_table_function_partition_data_info_handle info, idx_t index, idx_t *column_index, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_float(duckdb_v2_factory_handle factory, float in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Reports the ordering position of the batch the exec callback just produced. + * Creates a DOUBLE value from a double. * - * Required on every call, whether or not `duckdb_v2_table_function_partition_data_requires_batch_index()` is true: the - * engine validates the reported value regardless. Not calling this before the callback returns fails the query; `0` is - * always a safe answer when the batch index is not actually required. Must not decrease across successive calls on the - * same thread, must be unique across threads for the ordering to be meaningful, must be less than roughly `10^13`, and, - * when partitioning column values are also being reported, must change whenever those values change: the engine only - * re-reads them when the batch index changes, so reporting a new value under an unchanged batch index is treated as a - * caller error rather than applied. Fails with `ERROR_INPUT_INVALID` for an out-of-range value; a decreasing value or - * an unchanged value paired with changed partitioning columns fails the query once the callback returns. + * The input is copied in. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param batch_index The batch's ordering position. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The double to wrap. + * @param out_value Receives the new DOUBLE value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_set_batch_index( - duckdb_v2_table_function_partition_data_info_handle info, idx_t batch_index, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_double(duckdb_v2_factory_handle factory, double in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Reports the single value of the partitioning column at the given index, for the batch the exec callback just - * produced. + * Creates a TYPE value from a logical type. * - * Every partition reported this way carries exactly one distinct value for the column, so the value serves as both the - * minimum and maximum of the partition's range. The value is borrowed and copied, and must be of the declared type of - * the corresponding result column. Required once for every index in `[0, - * `duckdb_v2_table_function_partition_data_get_partition_column_count()`)` before the callback returns; calling it - * again for the same index overwrites the previous value. The reported value only takes effect together with a changed - * `duckdb_v2_table_function_partition_data_set_batch_index()`: reporting a different value under an unchanged batch - * index fails the query once the callback returns. Fails with `ERROR_INPUT_INVALID` when the index is out of bounds or - * the value's type does not match. + * The logical type is borrowed and copied into the value, so the caller can destroy it independently. * * history: * - stable: v2.0.0 * - * @param info The partition data info handle. - * @param index The index of the partitioning column being reported. - * @param value The single value of the partitioning column. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_type The borrowed logical type to wrap. + * @param out_value Receives the new TYPE value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partition_data_set_partition_value( - duckdb_v2_table_function_partition_data_info_handle info, idx_t index, duckdb_v2_value_handle value, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_type(duckdb_v2_factory_handle factory, + duckdb_v2_logical_type_handle in_type, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Creates a DATE value. + * + * The payload is days since 1970-01-01, the same unit value_get_date reports. It is not range-checked here; value_cast + * is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partitioning info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new DATE value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_user_data( - duckdb_v2_table_function_partitioning_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_date(duckdb_v2_factory_handle factory, int32_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Retrieves the bind data set by the function's bind callback. + * Creates a TIME value. + * + * The payload is microseconds since midnight, the same unit value_get_time reports. It is not range-checked here; + * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partitioning info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIME value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_bind_data( - duckdb_v2_table_function_partitioning_info_handle info, void **data, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns the number of columns the optimizer is asking about: the candidate `GROUP BY` column set. + * Creates a TIME_NS value. * - * Valid indices for `duckdb_v2_table_function_partitioning_get_partition_column_index()` are `0` up to (but excluding) - * this count. + * The payload is nanoseconds since midnight, the same unit value_get_time_ns reports. It is not range-checked here; + * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partitioning info handle. - * @param count Receives the number of columns in the candidate set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIME_NS value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_partition_column_count( - duckdb_v2_table_function_partitioning_info_handle info, idx_t *count, duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Returns which declared column the candidate `GROUP BY` column at the given index stands for. + * Creates a TIME_TZ value. * - * The result indexes the columns declared with `duckdb_v2_table_function_bind_add_result_column()`, in declaration - * order. Fails if the index is out of bounds. + * The payload packs the time of day and the UTC offset into one integer, in the committed layout: micros in the high 40 + * bits, a biased offset in the low 24. The same form value_get_time_tz reports. It is not range-checked here; + * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partitioning info handle. - * @param index The index of the candidate column. - * @param column_index Receives the index of the declared column. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIME_TZ value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_get_partition_column_index( - duckdb_v2_table_function_partitioning_info_handle info, idx_t index, idx_t *column_index, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz(duckdb_v2_factory_handle factory, uint64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Reports whether every partition the scan produces carries exactly one distinct value for the candidate `GROUP BY` - * column set. + * Creates a TIMESTAMP value. * - * Only `TABLE_PARTITION_INFO_SINGLE_VALUE_PARTITIONS` unlocks the partitioned aggregate optimization; any other value - * keeps the regular hash aggregate. A callback that never calls this is treated as reporting - * `TABLE_PARTITION_INFO_NOT_PARTITIONED`. Fails with `ERROR_INPUT_INVALID` when partition_info is not one of the enum's - * declared values. + * The payload is microseconds since 1970-01-01, the same unit value_get_timestamp reports. It is not range-checked + * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param info The partitioning info handle. - * @param partition_info Whether, and how, the scan is partitioned by the candidate column set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_partitioning_set_partition_info( - duckdb_v2_table_function_partitioning_info_handle info, DUCKDB_V2_TABLE_PARTITION_INFO partition_info, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Registers the table function, making it available for use in SQL queries. + * Creates a TIMESTAMP_SEC value. * - * The function is registered on the target given at creation: the connection's database or the loading extension. - * Registration requires a name, a bind callback and an exec callback, and rejects a signature that declares a return - * type, since a table function declares the columns it returns from its bind callback. The caller still owns the handle - * after registration and must destroy it with `duckdb_v2_table_function_destroy()`, which does not affect the - * registered function. + * The payload is seconds since 1970-01-01, the same unit value_get_timestamp_sec reports. It is not range-checked here; + * value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param function The function to register. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP_SEC value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_register(duckdb_v2_table_function_handle function, - duckdb_v2_error_info_handle *err); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); /*! - * Destroys the table function, releasing its resources. + * Creates a TIMESTAMP_MS value. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered function. + * The payload is milliseconds since 1970-01-01, the same unit value_get_timestamp_ms reports. It is not range-checked + * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: * - stable: v2.0.0 * - * @param function The function to destroy. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP_MS value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_destroy(duckdb_v2_table_function_handle *function); +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the optional claim batch callback of the table function. - * - * With a claim batch callback, a scanning thread works through the scan one batch at a time: the callback claims the - * next batch of work (e.g. the next block of a file) for the thread's local state, typically from the shared global - * state, and the exec callback then produces the rows of that batch only, leaving the output chunk empty once the batch - * is exhausted. A batch is claimed before the exec callback first runs on a thread, and again whenever the exec - * callback leaves the output chunk empty; the scan ends for a thread once the claim batch callback does not claim a - * batch. - * - * Telling the batches apart lets a caller that scans several batches in parallel put their rows back in the order of - * the batches, e.g. a multi-file function registered with `duckdb_v2_multi_file_function_register()` claims the batches - * of the function itself, so that the rows of a file keep their order also when several threads scan it. + * Creates a TIMESTAMP_NS value. * - * The callback runs while the work of the scan is handed out to the threads, possibly while every other scanning thread - * waits for it. It should therefore only claim the work - e.g. reserve the number of the next block - and leave reading - * it to the exec callback. + * The payload is nanoseconds since 1970-01-01, the same unit value_get_timestamp_ns reports. It is not range-checked + * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the claim batch callback of. - * @param callback The claim batch callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP_NS value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_claim_batch_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_claim_batch_callback_fn callback, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the user data set on the table function via `duckdb_v2_table_function_set_user_data()`. + * Creates a TIMESTAMP_TZ value. + * + * The payload is microseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz reports. It is not + * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via + * value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The claim batch info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP_TZ value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_user_data( - duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the bind data set by the function's bind callback. + * Creates a TIMESTAMP_TZ_NS value. + * + * The payload is nanoseconds since 1970-01-01, in UTC, the same unit value_get_timestamp_tz_ns reports. It is not + * range-checked here; value_cast is the validating path. The returned value is caller-owned; destroy it via + * value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The claim batch info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new TIMESTAMP_TZ_NS value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_bind_data( - duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the global state set by the function's global init callback. + * Creates a INTERVAL value. * - * Shared with every other thread scanning the function; access to it must be synchronized by the function. + * The payload is the (months, days, micros) triple, the same unit value_get_interval reports. It is not range-checked + * here; value_cast is the validating path. The returned value is caller-owned; destroy it via value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The claim batch info handle. - * @param data Receives the global state pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The payload to wrap. + * @param out_value Receives the new INTERVAL value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_global_state( - duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_interval(duckdb_v2_factory_handle factory, + const duckdb_v2_interval_t *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the local state set by the function's local init callback, for the thread claiming a batch. + * Creates a DECIMAL value from its backing integer. + * + * The value is scaled by 10^scale, so (18500, 18, 3) is 18.500. width is the total digit count and must be 1..38; scale + * is the number of digits after the point and must not exceed width. Violating either returns ERROR_INPUT_INVALID, as + * does a value too wide for the storage tier the width selects. The value itself is not range-checked against the + * width; value_cast is the validating path. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The claim batch info handle. - * @param data Receives the local state pointer for the claiming thread, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The backing integer, scaled by 10^scale. + * @param width Total digit count, 1..38. + * @param scale Digits after the decimal point; must not exceed width. + * @param out_value Receives the new DECIMAL value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_get_local_state( - duckdb_v2_table_function_claim_batch_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_decimal(duckdb_v2_factory_handle factory, + const duckdb_v2_hugeint_t *in_value, uint8_t width, + uint8_t scale, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Reports whether the callback claimed a batch for the thread. + * Creates a UUID value from its internal 128-bit form. * - * A callback that returns without calling this claimed nothing, which ends the scan for the thread. + * The payload is the storage form a vector element holds, not the canonical byte order: the high bit is flipped so the + * integer sorts. To build one from canonical text, cast a VARCHAR with value_cast instead, which is also how a UUID + * renders back. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The claim batch info handle. - * @param claimed Whether a batch was claimed. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The internal 128-bit storage form. + * @param out_value Receives the new UUID value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_claim_batch_set_claimed( - duckdb_v2_table_function_claim_batch_info_handle info, bool claimed, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_uuid(duckdb_v2_factory_handle factory, + const duckdb_v2_hugeint_t *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the optional "get bind info" callback of the table function. + * Creates a BIT value from its wire bytes. * - * The engine invokes the callback with the bind data of a bound call when it needs to know more about the call than its - * columns. E.g. a multi-file function registered with `duckdb_v2_multi_file_function_register()` asks the function it - * wraps for every file it binds: it maps the columns of the files onto the columns of its scan by the identifiers the - * callback reports, and exposes the options it reports as the metadata of the reader of the file. The callback may be - * invoked more than once for the same bind data, so it should only report what the bind already determined. + * The wire form is a mandatory padding-header byte — the count of leading bits in the first data byte that are not part + * of the bit string — followed by the data bytes, so the input must be at least 1 byte. These are the same bytes + * value_get_blob reports back. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the callback of. - * @param callback The "get bind info" callback to set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The wire bytes, a padding header byte followed by data. Must be at least 1 byte. + * @param out_value Receives the new BIT value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_set_get_bind_info_callback( - duckdb_v2_table_function_handle function, duckdb_v2_table_function_get_bind_info_callback_fn callback, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bit(duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the user data set via `duckdb_v2_table_function_set_user_data()`. + * Creates a BIGNUM value from its storage bytes. + * + * The bytes are opaque storage, as produced by bignum_encode from a magnitude and a sign flag; a negative is stored + * bit-inverted behind a header, so these are not the magnitude bytes. The header plus at least one magnitude byte means + * the input must exceed 3 bytes. These are the same bytes value_get_blob reports back, for bignum_decode to translate. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The get bind info handle. - * @param data Receives the user data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param in_value The opaque storage bytes from bignum_encode. Must exceed 3 bytes. + * @param out_value Receives the new BIGNUM value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_get_user_data( - duckdb_v2_table_function_get_bind_info_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_bignum(duckdb_v2_factory_handle factory, + const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Retrieves the bind data the function's bind callback set for the bound call being described. + * Creates a LIST value from its elements. + * + * The child type is the common type of the elements, resolved by the same rule a SQL list literal follows, and every + * element is cast to it; a set with no common type surfaces the cast error. Pass child_type to name the type instead, + * which is also how an empty list is built: with child_type NULL, an empty element array has no type to resolve and + * returns ERROR_INPUT_INVALID. + * + * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. The type is rebuilt from its child, + * so an alias on the outer LIST type is not preserved; value_cast is the alias-preserving path. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The get bind info handle. - * @param data Receives the bind data pointer, or null if none was set. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. + * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. + * @param child_count The number of elements. + * @param out_value Receives the new composite value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_get_bind_data( - duckdb_v2_table_function_get_bind_info_info_handle info, void **data, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_list(duckdb_v2_factory_handle factory, + duckdb_v2_logical_type_handle child_type, + const duckdb_v2_value_handle *children, idx_t child_count, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Attaches an identifier to a result column of the bound call, or to a field nested inside one. + * Creates an ARRAY value from its elements, sized by their count. * - * A multi-file reader can then map the columns of every file onto the columns of its scan by identifier rather than by - * name - e.g. by the field ids of a file format that carries them, so that renamed columns and fields are still found. - * An identifier is an INTEGER field id or a VARCHAR name. + * The child type is resolved exactly as for value_create_list, and child_type names it explicitly the same way. The + * minimum array size is 1, so an empty element array returns ERROR_INPUT_INVALID whether or not child_type is given. * - * The nested field is addressed by a path of child indexes, starting at the result column at `column_index`: a STRUCT - * field by its index, the elements of a LIST or ARRAY by `0`, the keys of a MAP by `0` and its values by `1`, and a - * UNION member by its index. An empty path addresses the result column itself. Setting an identifier again for the same - * column or field replaces it. Fails with `ERROR_INPUT_INVALID` when the column index is out of range, or the path does - * not address a nested field of the column's type. + * The elements are borrowed and copied in, and a NULL element becomes a typed NULL. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The get bind info handle. - * @param column_index The index of the result column, in the order the bind callback declared the columns. - * @param child_path The path of child indexes addressing the nested field, or null for the column itself. - * @param child_path_length The number of entries in the path, 0 for the column itself. - * @param identifier The identifier, an INTEGER or VARCHAR value. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param child_type Optional. The element type. Pass NULL to resolve it from the elements. + * @param children An array of child_count elements. Borrowed (copied in). Pass NULL when child_count is 0. + * @param child_count The number of elements. + * @param out_value Receives the new composite value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_set_column_identifier( - duckdb_v2_table_function_get_bind_info_info_handle info, idx_t column_index, const idx_t *child_path, - idx_t child_path_length, duckdb_v2_value_handle identifier, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_array(duckdb_v2_factory_handle factory, + duckdb_v2_logical_type_handle child_type, + const duckdb_v2_value_handle *children, idx_t child_count, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets an option describing the bound call: a key-value fact about what the call reads, e.g. a property of the file it - * reads. Setting a key again replaces its value. + * Creates a STRUCT value from named fields, in order. + * + * Each field keeps its own child's type, so there is nothing to resolve across them; names and children are parallel + * arrays of field_count entries. An empty field list builds the empty struct, which is a real type. * - * A multi-file function registered with `duckdb_v2_multi_file_function_register()` exposes the options of the function - * it wraps as the metadata of the reader of each file, e.g. to a custom multi-file reader of another extension. + * The names and children are borrowed and copied in, and a NULL child becomes a typed NULL. Duplicate field names are + * rejected. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param info The get bind info handle. - * @param key The key of the option. Borrowed and copied. - * @param value The value of the option. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param names An array of field_count field names. Pass NULL when field_count is 0. + * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. + * @param field_count The number of fields. + * @param out_value Receives the new composite value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_table_function_get_bind_info_set_option( - duckdb_v2_table_function_get_bind_info_info_handle info, duckdb_v2_str *key, duckdb_v2_value_handle value, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_struct(duckdb_v2_factory_handle factory, + const duckdb_v2_identifier_t *names, + const duckdb_v2_value_handle *children, idx_t field_count, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Creates a new multi-file function that will be registered on the connection's database. + * Creates a TUPLE value from positional fields. * - * The function starts out empty: give it a name with `duckdb_v2_multi_file_function_set_name()` and the single-file - * function it wraps with `duckdb_v2_multi_file_function_set_single_file_function()`, then make it available with - * `duckdb_v2_multi_file_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_multi_file_function_destroy()`, also after registration. + * TUPLE is the unnamed struct: the same positional children as value_create_struct, under a distinct type id and with + * no names. Each field keeps its own child's type. An empty field list builds the empty tuple, which is a real type. + * + * The children are borrowed and copied in, and a NULL child becomes a typed NULL. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param connection The connection to create the function in. - * @param function On success, receives the newly created multi-file function. Owned by the caller. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param children An array of field_count field values. Borrowed (copied in). Pass NULL when field_count is 0. + * @param field_count The number of fields. + * @param out_value Receives the new composite value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create_with_connection( - duckdb_v2_connection_handle connection, duckdb_v2_multi_file_function_handle *function, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_tuple(duckdb_v2_factory_handle factory, + const duckdb_v2_value_handle *children, idx_t field_count, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Creates a new multi-file function that will be registered on the loading extension's database. + * Creates a MAP value from parallel key and value arrays. + * + * The key and value types are the common types of the keys and of the values, resolved as for value_create_list, and + * every entry is cast to them. Pass key_type and value_type to name them instead, which is also how an empty map is + * built: with both NULL, empty key and value arrays have no types to resolve and return ERROR_INPUT_INVALID. * - * Use this from an extension load callback, where an extension handle is available. The function starts out empty: give - * it a name with `duckdb_v2_multi_file_function_set_name()` and the single-file function it wraps with - * `duckdb_v2_multi_file_function_set_single_file_function()`, then make it available with - * `duckdb_v2_multi_file_function_register()`. The caller owns the returned handle and must destroy it with - * `duckdb_v2_multi_file_function_destroy()`, also after registration. + * Keys and values are borrowed and copied in, and the two arrays must be the same length. Keys must be non-NULL and + * unique; both are enforced. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param extension The extension to create the function in. - * @param function On success, receives the newly created multi-file function. Owned by the caller. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory to create with. + * @param key_type Optional. The key type. Pass NULL to resolve it from the keys. + * @param value_type Optional. The value type. Pass NULL to resolve it from the values. + * @param keys An array of entry_count keys. Borrowed (copied in). Pass NULL when entry_count is 0. + * @param values An array of entry_count values, parallel to keys. Borrowed (copied in). Pass NULL when entry_count is + * 0. + * @param entry_count The number of entries. + * @param out_value Receives the new composite value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create_with_extension( - duckdb_v2_extension_handle extension, duckdb_v2_multi_file_function_handle *function, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_create_map( + duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, + const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the name of the multi-file function, as SQL will call it. + * Returns the number of child values of a value. * - * The name is borrowed and copied. Calling this again replaces the previous name. A name must be set before - * registration. + * Per type kind: the element count for LIST and ARRAY; the field count for STRUCT and TUPLE; twice the entry count for + * MAP, whose children alternate key, value; and 2 for UNION, the tag plus the active member. Everything else reports 0, + * NULL values of any nested type included. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the name of. - * @param name The name to set. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out_count Receives the number of children. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_name(duckdb_v2_multi_file_function_handle function, - duckdb_v2_str *name, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_child_count(duckdb_v2_value_handle value, idx_t *out_count, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the name of the table function that reads a single file, which the multi-file function wraps. + * Returns one child of a composite value as an owned copy. * - * The single-file function must be registered before the multi-file function is, and must take the file to read as its - * only positional parameter, of type ANY. It is bound once for every file that is read, and receives the file the way a - * multi-file function takes one: as the VARCHAR path of the file when there is nothing more to know about it, and - * otherwise as a STRUCT holding the path in its `filename` field and the options to open the file with in its other - * fields - e.g. the size of the file, as a file system reported it while globbing, or the options a caller passed along - * with the file (`read_x([{'filename': 'f.x', 'file_size': 42}])`). Opening the file with those options, set with - * `duckdb_v2_file_open_options_set_value()`, lets the file system skip work it would otherwise repeat, such as asking a - * remote store for the size of the file. A single-file function that takes a VARCHAR parameter instead is still - * accepted, and only receives the path. Every named parameter the single-file function declares is also a named - * parameter of the multi-file function, forwarded to it as given. Everything that involves several files - globbing, - * lists of files, hive partitioning, the `filename` column, `union_by_name` and the like - is provided by the - * multi-file function on top of it. The name is borrowed and copied. A single-file function must be set before - * registration. + * LIST and ARRAY children are the elements. STRUCT and TUPLE children are the fields in declared order, and a STRUCT's + * field names come from its type, via logical_type_get_param. MAP children alternate key, value, symmetric with + * value_create_map. UNION children are [0] the tag, as a UTINYINT value, and [1] the active member, because a union + * value holds only that one member. + * + * Note the divergence from vector_get_child, which descends structurally and so exposes [0] = tag and [1..N] = ALL + * members. Code written to descend generically over both must account for that difference. + * + * An out-of-range index returns ERROR_INPUT_INVALID, as does any index on a non-composite or NULL value. The returned + * value is caller-owned; destroy it via value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the single-file function of. - * @param name The name of the registered single-file table function. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param index The child index, in [0, child_count). + * @param out_child Receives the owned child value. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_single_file_function( - duckdb_v2_multi_file_function_handle function, duckdb_v2_str *name, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_child(duckdb_v2_value_handle value, idx_t index, + duckdb_v2_value_handle *out_child, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets how the files the function reads are referred to in messages, e.g. "Avro" or "JSON". + * Casts a value to a target type, using a factory. * - * Optional: without it, the files are referred to by the name of the multi-file function. The name is borrowed and - * copied. + * The conversion is the SQL-faithful, non-strict one, registered custom casts included, and a cast failure surfaces + * from the call. Together with a VARCHAR built through value_create_varchar, this constructs any value from text, + * extension values included; casting a member value to a union type, or a VARCHAR to an enum type, is the sanctioned + * way to build UNION and ENUM values. + * + * A connection's or callback's factory casts with that client's cast functions and settings (e.g. TimeZone). An + * instance's factory supports only the built-in casts, with default settings. + * + * The input value and target type are borrowed. The returned value is caller-owned; destroy it via value_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the reader type of. - * @param reader_type The reader type. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param factory The factory supplying the cast functions. + * @param value The borrowed value to cast. + * @param target_type The borrowed target logical type. + * @param out_value Receives the owned cast result. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_reader_type( - duckdb_v2_multi_file_function_handle function, duckdb_v2_str *reader_type, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_cast(duckdb_v2_factory_handle factory, duckdb_v2_value_handle value, + duckdb_v2_logical_type_handle target_type, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Sets the file extension of the files the function reads, e.g. "avro". - * - * Optional. With a file extension, a path that names a directory rather than a file reads the files with that extension - * inside it. Without one, the paths passed to the function must match at least one file. The extension is given without - * a leading dot, and is borrowed and copied. + * Returns whether the value is NULL. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to set the file extension of. - * @param extension The file extension, without a leading dot. Borrowed and copied. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out_is_null Receives true if the value is NULL, false otherwise. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_file_extension( - duckdb_v2_multi_file_function_handle function, duckdb_v2_str *extension, duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_is_null(duckdb_v2_value_handle value, bool *out_is_null, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Registers the multi-file function, making it available for use in SQL queries. + * Returns the logical type of the value. * - * The function is registered on the target given at creation: the connection's database or the loading extension. It - * can then be called with the path of a single file, a glob pattern, or a list of either, and accepts the options every - * multi-file function accepts (e.g. `filename`, `hive_partitioning`, `union_by_name`) next to the named parameters of - * the single-file function. Registration requires a name and a single-file function, and fails when no table function - * by that name is registered or it does not take the file to read as its only positional parameter (see - * `duckdb_v2_multi_file_function_set_single_file_function()`). The caller still owns the handle after registration and - * must destroy it with `duckdb_v2_multi_file_function_destroy()`, which does not affect the registered function. + * The returned logical type is caller-owned; destroy it via logical_type_destroy. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to register. - * @param err Optional. On failure, receives an opaque info handle the caller must destroy via - * `duckdb_v2_error_info_destroy()`. + * @param value The value to read. + * @param out_type Receives the owned logical type. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_register(duckdb_v2_multi_file_function_handle function, - duckdb_v2_error_info_handle *err); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_get_logical_type(duckdb_v2_value_handle value, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err); -#if DUCKDB_V2_API_ALLOW_UNSTABLE /*! - * Destroys the multi-file function, releasing its resources. + * Renders the value as a human-readable string. Diagnostic only. * - * Null-safe: passing a null pointer or null handle is a no-op. The handle is set to null on return to prevent - * double-destruction. Destroying the handle after registration does not affect the registered function. + * Writes into a caller-supplied buffer, so nothing is allocated on the caller's behalf and nothing has to be freed. + * + * Pass out_string = NULL to size the buffer without rendering into it: out_length then receives the length, and + * out_capacity is ignored. With out_string != NULL, out_capacity must be at least out_length + 1, or the call returns + * ERROR_INPUT_OBJECT_SIZE with out_length set to the required length and out_string left untouched. + * + * out_length never counts the terminator, but a successful write always appends one, so the buffer is usable as a C + * string. * * history: - * - unstable: v2.0.0 + * - stable: v2.0.0 * - * @param function The function to destroy. + * @param value The value to read. + * @param out_string Caller-owned buffer receiving the text plus a null terminator, or NULL to only report the required + * length in out_length. + * @param out_capacity Bytes available in out_string, terminator included. Ignored when out_string is NULL. + * @param out_length Receives the text length excluding the null terminator — written on success and on + * ERROR_INPUT_OBJECT_SIZE. + * @param err Optional. On failure, receives an opaque info handle the caller must destroy via error_info_destroy. * @return DUCKDB_V2_ERROR */ -DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_multi_file_function_destroy(duckdb_v2_multi_file_function_handle *function); -#endif +DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_value_to_string(duckdb_v2_value_handle value, char *out_string, + idx_t out_capacity, idx_t *out_length, + duckdb_v2_error_info_handle *err); -/* --- Struct definitions for table --- */ +/* --- Struct definitions for value --- */ /* ============================================================================ * MODULE: arrow diff --git a/src/duckdb/src/logging/log_manager.cpp b/src/duckdb/src/logging/log_manager.cpp index 7f5bf160a..e1b3b174d 100644 --- a/src/duckdb/src/logging/log_manager.cpp +++ b/src/duckdb/src/logging/log_manager.cpp @@ -34,6 +34,7 @@ RegisteredLoggingContext LogManager::RegisterLoggingContext(LoggingContext &cont } bool LogManager::RegisterLogStorage(const string &name, shared_ptr &storage) { + unique_lock lck(lock); if (registered_log_storages.find(name) != registered_log_storages.end()) { return false; } diff --git a/src/duckdb/src/main/capi/v1/helper-c.cpp b/src/duckdb/src/main/capi/v1/helper-c.cpp index 7fb8ff627..9b9a7a655 100644 --- a/src/duckdb/src/main/capi/v1/helper-c.cpp +++ b/src/duckdb/src/main/capi/v1/helper-c.cpp @@ -411,6 +411,8 @@ duckdb_error_type ErrorTypeToC(const ExceptionType type) { return DUCKDB_ERROR_DATA_CORRUPTION; case ExceptionType::INVALID_CONFIGURATION: return DUCKDB_INVALID_CONFIGURATION; + case ExceptionType::RESOURCE_IN_USE: + return DUCKDB_ERROR_RESOURCE_IN_USE; default: return DUCKDB_ERROR_INVALID; } @@ -506,6 +508,8 @@ ExceptionType ErrorTypeFromC(const duckdb_error_type type) { return ExceptionType::DATA_CORRUPTION; case DUCKDB_INVALID_CONFIGURATION: return ExceptionType::INVALID_CONFIGURATION; + case DUCKDB_ERROR_RESOURCE_IN_USE: + return ExceptionType::RESOURCE_IN_USE; default: return ExceptionType::INVALID; } diff --git a/src/duckdb/src/main/capi/v1/pending-c.cpp b/src/duckdb/src/main/capi/v1/pending-c.cpp index d09e7e5c4..1bf7ea633 100644 --- a/src/duckdb/src/main/capi/v1/pending-c.cpp +++ b/src/duckdb/src/main/capi/v1/pending-c.cpp @@ -21,10 +21,17 @@ duckdb_state duckdb_pending_prepared_internal(duckdb_prepared_statement prepared } auto wrapper = reinterpret_cast(prepared_statement); auto result = new PendingStatementWrapper(); - result->allow_streaming = allow_streaming; + result->allow_streaming = false; try { result->statement = wrapper->statement->Submit(wrapper->values); + result->allow_streaming = + allow_streaming && duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), + result->statement->GetStatementProperties()); + if (!result->allow_streaming) { + // Settled before the caller drives it, so each execute_task call stays one unit of work + result->statement->Materialize(); + } } catch (std::exception &ex) { result->statement = make_uniq(duckdb::ErrorData(ex)); } @@ -156,9 +163,7 @@ duckdb_state duckdb_execute_pending(duckdb_pending_result pending_result, duckdb } auto result = std::move(wrapper->statement); - // A streaming request drains the result through a stream; every other one collects it - const bool stream_result = wrapper->allow_streaming && !result->HasError() && - result->GetStatementProperties().result_eagerness != duckdb::ResultEagerness::FORCED; + const bool stream_result = wrapper->allow_streaming && !result->HasError(); try { if (stream_result) { return DuckDBTranslateStreamResult(duckdb::make_uniq>(std::move(result)), diff --git a/src/duckdb/src/main/capi/v1/prepared-c.cpp b/src/duckdb/src/main/capi/v1/prepared-c.cpp index 4d0aa2e88..bf7a268cb 100644 --- a/src/duckdb/src/main/capi/v1/prepared-c.cpp +++ b/src/duckdb/src/main/capi/v1/prepared-c.cpp @@ -446,9 +446,9 @@ duckdb_state duckdb_execute_prepared_streaming(duckdb_prepared_statement prepare try { auto result = wrapper->statement->Submit(wrapper->values); - if (result->HasError() || - result->GetStatementProperties().result_eagerness == duckdb::ResultEagerness::FORCED) { - // The statement cannot be streamed: it completes before its result is returned + // Without an out_result the stream is dropped unread, which rolls back what the statement wrote + if (result->HasError() || !duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), + result->GetStatementProperties(), out_result != nullptr)) { result->Complete(); return DuckDBTranslateResult(std::move(result), out_result); } diff --git a/src/duckdb/src/main/capi/v1/stream-c.cpp b/src/duckdb/src/main/capi/v1/stream-c.cpp index 07ead5b67..f5a39f5f4 100644 --- a/src/duckdb/src/main/capi/v1/stream-c.cpp +++ b/src/duckdb/src/main/capi/v1/stream-c.cpp @@ -6,11 +6,8 @@ duckdb_data_chunk duckdb_stream_fetch_chunk(duckdb_result result) { if (!result.internal_data) { return nullptr; } - auto &result_data = *((duckdb::DuckDBResultData *)result.internal_data); - if (!result_data.IsStreaming()) { - // We can only fetch from a streaming result - return nullptr; - } + // A streaming entry point hands out a retained result for a statement it does not stream, and its rows are read + // here too, since this is the reader its caller was told to use return duckdb_fetch_chunk(result); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2.cpp b/src/duckdb/src/main/capi/v2/capi_v2.cpp index 33ed31500..7e2ff1921 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2.cpp @@ -328,18 +328,34 @@ auto NullArgumentError(duckdb_v2_error_info_handle *err, const char *function, c // Map DuckDB's SettingScopeTarget to the V2 enum. // Legacy options (declared via DUCKDB_GLOBAL / DUCKDB_LOCAL / DUCKDB_GLOBAL_LOCAL) carry SettingScopeTarget::INVALID; // we surface that as UNKNOWN so V2 callers can distinguish "unconstrained legacy" from a declared scope. -static DUCKDB_V2_OPTION_TARGET_SCOPE MapScopeTarget(SettingScopeTarget s) { - switch (s) { +// The scopes a core option may be written at, and where an AUTOMATIC write goes; mirrors PhysicalSet::GetSettingScope. +static void PopulateOptionScopes(const unique_ptr &out, const ConfigurationOption &option) { + if (option.set_local || option.set_global) { + // legacy option: the setters it has decide + out->supports_session = option.set_local != nullptr; + out->supports_global = option.set_global != nullptr; + out->default_scope = option.set_local ? DUCKDB_V2_SETTING_SCOPE_SESSION : DUCKDB_V2_SETTING_SCOPE_GLOBAL; + return; + } + switch (option.scope) { case SettingScopeTarget::GLOBAL_ONLY: - return DUCKDB_V2_OPTION_TARGET_SCOPE_GLOBAL_ONLY; + out->supports_global = true; + out->default_scope = DUCKDB_V2_SETTING_SCOPE_GLOBAL; + break; case SettingScopeTarget::LOCAL_ONLY: - return DUCKDB_V2_OPTION_TARGET_SCOPE_LOCAL_ONLY; + out->supports_session = true; + out->default_scope = DUCKDB_V2_SETTING_SCOPE_SESSION; + break; case SettingScopeTarget::GLOBAL_DEFAULT: - return DUCKDB_V2_OPTION_TARGET_SCOPE_GLOBAL_DEFAULT; + out->supports_global = out->supports_session = true; + out->default_scope = DUCKDB_V2_SETTING_SCOPE_GLOBAL; + break; case SettingScopeTarget::LOCAL_DEFAULT: - return DUCKDB_V2_OPTION_TARGET_SCOPE_LOCAL_DEFAULT; + out->supports_global = out->supports_session = true; + out->default_scope = DUCKDB_V2_SETTING_SCOPE_SESSION; + break; default: - return DUCKDB_V2_OPTION_TARGET_SCOPE_UNKNOWN; + break; } } @@ -358,19 +374,22 @@ static void PopulateOptionAliases(const unique_ptr &out, const Identi } } -string CV2OptionSource::ReadSetting(const Identifier &name, const string &fallback) const { - if (staged_settings) { - auto staged = staged_settings->find(name); - if (staged != staged_settings->end()) { - return staged->second; - } +DUCKDB_V2_SETTING_SCOPE CV2OptionSource::ReadValue(std::string_view name, Value &result) const { + Identifier name_id(name); + auto option = DBConfig::GetOptionByName(name_id); + ExtensionOption extension_option; + if (!option && !config.TryGetExtensionOption(name_id, extension_option)) { + throw InvalidInputException("unknown configuration option: %s", name_id.GetIdentifierName()); } - Value result; - auto found = context ? context->TryGetCurrentSetting(name, result) : config.TryGetCurrentSetting(name, result); - if (found && !result.IsNull()) { - return result.ToString(); + auto canonical_name = option ? Identifier(option->name) : name_id; + auto lookup = context ? context->TryGetCurrentSetting(canonical_name, result) + : config.TryGetCurrentSetting(canonical_name, result); + if (!lookup) { + // neither set nor defaulted + result = Value(); + return DUCKDB_V2_SETTING_SCOPE_GLOBAL; } - return fallback; + return lookup.GetScope() == SettingScope::LOCAL ? DUCKDB_V2_SETTING_SCOPE_SESSION : DUCKDB_V2_SETTING_SCOPE_GLOBAL; } static unique_ptr PopulateOptionFromCore(const ConfigurationOption &option, const CV2OptionSource &source) { @@ -378,27 +397,30 @@ static unique_ptr PopulateOptionFromCore(const ConfigurationOption &o out->name = option.name ? option.name : ""; out->description = option.description ? option.description : ""; - out->target_scope = MapScopeTarget(option.scope); - out->default_setting = option.default_value ? option.default_value : ""; + PopulateOptionScopes(out, option); + if (!DBConfig::TryGetDefaultValue(&option, out->default_value)) { + out->default_value = Value(); + } out->aliases.clear(); PopulateOptionAliases(out, out->name); - out->setting = source.ReadSetting(out->name, out->default_setting); return out; } -// Populate `out` from an extension option. Extension options carry no -// SettingScopeTarget (the V2 enum reports UNKNOWN) and no aliases. +// Populate `out` from an extension option. Extension options carry no aliases. static unique_ptr PopulateOptionFromExtension(const Identifier &name, const ExtensionOption &ext_option, const CV2OptionSource &source) { auto out = make_uniq(); out->name = name; out->description = ext_option.description; - out->target_scope = DUCKDB_V2_OPTION_TARGET_SCOPE_UNKNOWN; - out->default_setting = ext_option.default_value.IsNull() ? std::string() : ext_option.default_value.ToString(); + // AUTOMATIC writes the session unless the option declares GLOBAL + out->supports_global = ext_option.supports_global; + out->supports_session = ext_option.supports_session; + out->default_scope = + ext_option.default_scope == SetScope::GLOBAL ? DUCKDB_V2_SETTING_SCOPE_GLOBAL : DUCKDB_V2_SETTING_SCOPE_SESSION; + out->default_value = ext_option.default_value; out->aliases.clear(); - out->setting = source.ReadSetting(name, out->default_setting); return out; } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_arrow.cpp b/src/duckdb/src/main/capi/v2/capi_v2_arrow.cpp index ee6cffbab..ba944aa9d 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_arrow.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_arrow.cpp @@ -368,9 +368,9 @@ DUCKDB_V2_ERROR duckdb_v2_arrow_importer_create(duckdb_v2_context_handle context *out_importer = nullptr; return WithErrorHandler(err, [&]() { auto importer = duckdb::make_uniq(); - importer->context = Convert(context); + importer->context = &Convert(context)->context; importer->batch_size = batch_size; - duckdb::ArrowTableFunction::PopulateArrowTableSchema(*Convert(context), importer->table, *schema); + duckdb::ArrowTableFunction::PopulateArrowTableSchema(Convert(context)->context, importer->table, *schema); *out_importer = Convert(importer.release()); }); } @@ -509,7 +509,7 @@ DUCKDB_V2_ERROR duckdb_v2_arrow_exporter_create(duckdb_v2_context_handle context return NullArgumentError(err, __func__, "names"); } return WithErrorHandler(err, [&]() { - auto &ctx = *Convert(context); + auto &ctx = Convert(context)->context; auto exporter = duckdb::make_uniq(); exporter->batch_size = batch_size; exporter->types.reserve(count); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_column_data_collection.cpp b/src/duckdb/src/main/capi/v2/capi_v2_column_data_collection.cpp index 78668fd4f..c4c520b3f 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_column_data_collection.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_column_data_collection.cpp @@ -1,5 +1,6 @@ #include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/storage/buffer_manager.hpp" #include "duckdb/common/type_visitor.hpp" #include "duckdb/common/types/column/column_data_collection.hpp" @@ -30,7 +31,7 @@ static auto Convert(duckdb_v2_column_data_collection_worker_scan_state_handle st return reinterpret_cast(state); } -static void CreateColumnDataCollection(ClientContext &context, const duckdb_v2_logical_type_handle *types_array, +static void CreateColumnDataCollection(DatabaseInstance &db, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, duckdb_v2_column_data_collection_handle *out_collection, const char *function_name) { *out_collection = nullptr; @@ -52,7 +53,7 @@ static void CreateColumnDataCollection(ClientContext &context, const duckdb_v2_l } types.push_back(type); } - auto collection = make_uniq(context, std::move(types)); + auto collection = make_uniq(BufferManager::GetBufferManager(db), std::move(types)); *out_collection = Convert(collection.release()); } @@ -78,27 +79,17 @@ static void VerifyChunkTypes(const ColumnDataCollection &collection, const DataC using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create_with_connection( - duckdb_v2_connection_handle conn, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, - duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(types_array); - DUCKDB_CHECK_ARG(out_collection); - return WithErrorHandler(err, [&]() { - CreateColumnDataCollection(*Convert(conn)->context, types_array, types_count, out_collection, - "duckdb_v2_column_data_collection_create_with_connection"); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create_with_context( - duckdb_v2_context_handle context, const duckdb_v2_logical_type_handle *types_array, idx_t types_count, - duckdb_v2_column_data_collection_handle *out_collection, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(context); +DUCKDB_V2_ERROR duckdb_v2_column_data_collection_create(duckdb_v2_factory_handle factory, + const duckdb_v2_logical_type_handle *types_array, + idx_t types_count, + duckdb_v2_column_data_collection_handle *out_collection, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(types_array); DUCKDB_CHECK_ARG(out_collection); return WithErrorHandler(err, [&]() { - CreateColumnDataCollection(*Convert(context), types_array, types_count, out_collection, - "duckdb_v2_column_data_collection_create_with_context"); + CreateColumnDataCollection(Convert(factory)->GetDatabase(), types_array, types_count, out_collection, + "duckdb_v2_column_data_collection_create"); }); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_config.cpp b/src/duckdb/src/main/capi/v2/capi_v2_config.cpp new file mode 100644 index 000000000..3c8a6c748 --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_config.cpp @@ -0,0 +1,153 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" + +namespace duckdb { +namespace capiv2 { + +//---------------------------------------------------------------------------------------------------------------------- +// Instance Config +//---------------------------------------------------------------------------------------------------------------------- + +DUCKDB_V2_SETTING_SCOPE CV2InstanceConfig::ReadValue(std::string_view name, Value &result) { + // Legacy options are read through a client context; a fresh one has no SESSION settings to shadow GLOBAL. + auto connection = Connection::CreateInternal(*instance.GetDatabase().instance); + CV2OptionSource(*connection.context).ReadValue(name, result); + return DUCKDB_V2_SETTING_SCOPE_GLOBAL; +} + +void CV2InstanceConfig::Write(const Identifier &name, const Value &value, DUCKDB_V2_SETTING_SCOPE scope) { + if (scope == DUCKDB_V2_SETTING_SCOPE_SESSION) { + throw InvalidInputException("an instance has no SESSION scope: write the option through a connection"); + } + auto connection = Connection::CreateInternal(*instance.GetDatabase().instance); + PhysicalSet::SetVariable(*connection.context, name, SetScope::GLOBAL, value); +} + +unique_ptr CV2InstanceConfig::GetOption(std::string_view name) { + return CV2Option::FromName(CV2OptionSource(*instance.GetDatabase().instance), name); +} + +unique_ptr CV2InstanceConfig::GetOptionByIndex(idx_t index) { + return CV2Option::FromIndex(CV2OptionSource(*instance.GetDatabase().instance), index); +} + +idx_t CV2InstanceConfig::GetOptionCount() { + return CV2Option::Count(CV2OptionSource(*instance.GetDatabase().instance)); +} + +//---------------------------------------------------------------------------------------------------------------------- +// Client Config +//---------------------------------------------------------------------------------------------------------------------- + +DUCKDB_V2_SETTING_SCOPE CV2ClientConfig::ReadValue(std::string_view name, Value &result) { + return CV2OptionSource(context).ReadValue(name, result); +} + +void CV2ClientConfig::Write(const Identifier &name, const Value &value, DUCKDB_V2_SETTING_SCOPE scope) { + PhysicalSet::SetVariable(context, name, ConvertSetScope(scope), value); +} + +unique_ptr CV2ClientConfig::GetOption(std::string_view name) { + return CV2Option::FromName(CV2OptionSource(context), name); +} + +unique_ptr CV2ClientConfig::GetOptionByIndex(idx_t index) { + return CV2Option::FromIndex(CV2OptionSource(context), index); +} + +idx_t CV2ClientConfig::GetOptionCount() { + return CV2Option::Count(CV2OptionSource(context)); +} + +} // namespace capiv2 +} // namespace duckdb + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_instance_get_config(duckdb_v2_instance_handle instance, duckdb_v2_config_handle *out_config, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); + DUCKDB_CHECK_ARG(out_config); + *out_config = nullptr; + return WithErrorHandler(err, [&]() { *out_config = Convert(&Convert(instance)->config_handle); }); +} + +DUCKDB_V2_ERROR duckdb_v2_connection_get_config(duckdb_v2_connection_handle conn, duckdb_v2_config_handle *out_config, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(out_config); + *out_config = nullptr; + return WithErrorHandler(err, [&]() { *out_config = Convert(&Convert(conn)->context_handle.config_handle); }); +} + +DUCKDB_V2_ERROR duckdb_v2_context_get_config(duckdb_v2_context_handle ctx, duckdb_v2_config_handle *out_config, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(ctx); + DUCKDB_CHECK_ARG(out_config); + *out_config = nullptr; + return WithErrorHandler(err, [&]() { *out_config = Convert(&Convert(ctx)->config_handle); }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_get_option_value(duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, + duckdb_v2_value_handle *out_value, DUCKDB_V2_SETTING_SCOPE *out_scope, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(name); + DUCKDB_CHECK_ARG(out_value); + DUCKDB_CHECK_ARG(out_scope); + *out_value = nullptr; + return WithErrorHandler(err, [&]() { + duckdb::Value value; + *out_scope = Convert(config)->ReadValue(ConvertIdentifierName(name), value); + *out_value = Convert(new duckdb::Value(std::move(value))); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_set_option_value(duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, + duckdb_v2_value_handle value, DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(name); + DUCKDB_CHECK_ARG(value); + return WithErrorHandler(err, [&]() { + Convert(config)->Write(duckdb::Identifier(ConvertIdentifierName(name)), *Convert(value), scope); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_set_option_text(duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, + const duckdb_v2_str *setting, DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(name); + DUCKDB_CHECK_ARG(setting); + return WithErrorHandler(err, [&]() { + Convert(config)->Write(duckdb::Identifier(ConvertIdentifierName(name)), + duckdb::Value(duckdb::string(Convert(setting))), scope); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_get_option_by_name(duckdb_v2_config_handle config, const duckdb_v2_identifier_t *name, + duckdb_v2_option_handle *out_option, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(name); + DUCKDB_CHECK_ARG(out_option); + *out_option = nullptr; + return WithErrorHandler( + err, [&]() { *out_option = Convert(Convert(config)->GetOption(ConvertIdentifierName(name)).release()); }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_get_option_count(duckdb_v2_config_handle config, idx_t *out_count, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(out_count); + return WithErrorHandler(err, [&]() { *out_count = Convert(config)->GetOptionCount(); }); +} + +DUCKDB_V2_ERROR duckdb_v2_config_get_option_by_index(duckdb_v2_config_handle config, idx_t index, + duckdb_v2_option_handle *out_option, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(config); + DUCKDB_CHECK_ARG(out_option); + *out_option = nullptr; + return WithErrorHandler(err, [&]() { *out_option = Convert(Convert(config)->GetOptionByIndex(index).release()); }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_connection.cpp b/src/duckdb/src/main/capi/v2/capi_v2_connection.cpp index 824bf29d8..2d88d63cb 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_connection.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_connection.cpp @@ -1,26 +1,5 @@ #include "duckdb/main/capi_v2/capi_v2_internal.hpp" -namespace duckdb { -namespace capiv2 { -namespace { - -// Map V2's user-facing scope choice to DuckDB's SetScope. -SetScope MapSettingScope(DUCKDB_V2_SETTING_SCOPE s) { - switch (s) { - case DUCKDB_V2_SETTING_SCOPE_GLOBAL: - return SetScope::GLOBAL; - case DUCKDB_V2_SETTING_SCOPE_LOCAL: - return SetScope::SESSION; - default: - return SetScope::AUTOMATIC; - } -} - -} // namespace - -} // namespace capiv2 -} // namespace duckdb - using namespace duckdb::capiv2; DUCKDB_V2_ERROR duckdb_v2_connection_create(duckdb_v2_instance_handle instance, duckdb_v2_connection_handle *out_conn, @@ -30,8 +9,7 @@ DUCKDB_V2_ERROR duckdb_v2_connection_create(duckdb_v2_instance_handle instance, *out_conn = nullptr; return WithErrorHandler(err, [&]() { auto &instance_wrapper = *Convert(instance); - duckdb::lock_guard guard(instance_wrapper.lock); - auto connection = duckdb::make_uniq(instance_wrapper.GetDatabase()); + auto connection = duckdb::make_uniq(instance_wrapper.GetDatabase()); *out_conn = Convert(connection.release()); }); } @@ -48,52 +26,6 @@ DUCKDB_V2_ERROR duckdb_v2_connection_destroy(duckdb_v2_connection_handle *conn) }); } -DUCKDB_V2_ERROR duckdb_v2_connection_set_option(duckdb_v2_connection_handle conn, const duckdb_v2_identifier_t *name, - const duckdb_v2_str *setting, DUCKDB_V2_SETTING_SCOPE scope, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(name); - DUCKDB_CHECK_ARG(setting); - return WithErrorHandler(err, [&]() { - auto &client = *Convert(conn)->context; - duckdb::PhysicalSet::SetVariable(client, duckdb::Identifier(ConvertIdentifierName(name)), - MapSettingScope(scope), duckdb::Value(duckdb::string(Convert(setting)))); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_connection_get_option_by_name(duckdb_v2_connection_handle conn, - const duckdb_v2_identifier_t *name, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(name); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; - return WithErrorHandler(err, [&]() { - CV2OptionSource source(*Convert(conn)->context); - *out_option = Convert(CV2Option::FromName(source, ConvertIdentifierName(name)).release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_connection_get_option_count(duckdb_v2_connection_handle conn, idx_t *out_count, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_count); - return WithErrorHandler(err, [&]() { *out_count = CV2Option::Count(CV2OptionSource(*Convert(conn)->context)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_connection_get_option_by_index(duckdb_v2_connection_handle conn, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; - return WithErrorHandler(err, [&]() { - CV2OptionSource source(*Convert(conn)->context); - *out_option = Convert(CV2Option::FromIndex(source, index).release()); - }); -} - // --------------------------------------------------------------------------- // Query process management // --------------------------------------------------------------------------- @@ -103,7 +35,7 @@ DUCKDB_V2_ERROR duckdb_v2_connection_interrupt(duckdb_v2_connection_handle conn, return WithErrorHandler(err, [&]() { // Record that the cancellation was user-initiated. auto &context = *Convert(conn)->context; - GetBusySlot(context)->cancel_requested.store(true); + GetBusySlot(context)->cancel_requests.fetch_add(1); // ClientContext::Interrupt is an atomic store; safe to call from any thread context.Interrupt(); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_custom_option.cpp b/src/duckdb/src/main/capi/v2/capi_v2_custom_option.cpp new file mode 100644 index 000000000..beeb84e3b --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_custom_option.cpp @@ -0,0 +1,303 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/main/config.hpp" + +namespace duckdb::capiv2 { + +class CV2CustomOptionUpdateInfo { +public: + CV2CustomOptionUpdateInfo(ClientContext &context, const LogicalType &type, Value &value, SetScope scope, + bool is_reset, void *user_data) + : context(context), type(type), value(value), scope(scope), is_reset(is_reset), in_user_data(user_data) { + } + + ClientContext &context; + const LogicalType &type; + //! The value the option is updated to, replaceable by the callback + Value &value; + SetScope scope; + bool is_reset; + void *in_user_data; +}; + +static auto Convert(duckdb_v2_custom_option_update_info_handle info) -> CV2CustomOptionUpdateInfo * { + return reinterpret_cast(info); +} +static auto Convert(CV2CustomOptionUpdateInfo *info) -> duckdb_v2_custom_option_update_info_handle { + return reinterpret_cast(info); +} + +class CV2CustomOptionInfo final : public ExtensionOptionInfo { +public: + void OnSet(ClientContext &context, SetScope scope, Value ¶meter, bool is_reset) const override { + if (!update_cb) { + return; + } + CV2CustomOptionUpdateInfo info(context, type, parameter, scope, is_reset, + user_data ? user_data->GetData() : nullptr); + + CV2ErrorInfo err = {}; + auto err_ptr = Convert(&err); + CV2CallbackContext callback_context(context); + update_cb(Convert(&info), Convert(&callback_context), &err_ptr); + + if (err.HasError()) { + err.ThrowAsException(); + } + } + +public: + duckdb_v2_custom_option_update_callback_fn update_cb = nullptr; + shared_ptr user_data = nullptr; + LogicalType type; +}; + +class CV2CustomOption { +public: + explicit CV2CustomOption(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and registers the option on `target`. + void Register(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "custom option"); + if (name.empty()) { + throw InvalidInputException("Option name cannot be empty."); + } + auto option_type = type; + if (option_type.id() == LogicalTypeId::INVALID) { + option_type = default_value.type(); + } + if (option_type.id() == LogicalTypeId::INVALID || option_type.id() == LogicalTypeId::SQLNULL) { + throw InvalidInputException( + "A type, or a default value to take the type from, must be set for the option."); + } + if (!option_type.IsComplete()) { + throw InvalidInputException("The type of the option must be a fully defined concrete type."); + } + if (!supports_global && !supports_session) { + throw InvalidInputException("The option must support at least one of the GLOBAL and SESSION scopes."); + } + auto scope = default_scope; + if (scope == SetScope::AUTOMATIC) { + scope = supports_session ? SetScope::SESSION : SetScope::GLOBAL; + } + if ((scope == SetScope::GLOBAL && !supports_global) || (scope == SetScope::SESSION && !supports_session)) { + throw InvalidInputException("The default scope of the option must be a scope it supports."); + } + + auto &config = DBConfig::GetConfig(target); + if (DBConfig::GetOptionByName(name) || config.HasExtensionOption(name)) { + throw InvalidInputException("An option named \"%s\" already exists.", name); + } + + ExtensionOption option; + option.description = description; + option.type = option_type; + option.default_value = default_value.IsNull() ? Value(option_type) : default_value.DefaultCastAs(option_type); + option.default_scope = scope; + option.supports_global = supports_global; + option.supports_session = supports_session; + auto option_info = make_shared_ptr(); + option_info->update_cb = update_cb; + option_info->user_data = user_data; + option_info->type = option_type; + option.info = std::move(option_info); + config.AddExtensionOption(name, std::move(option)); + } + +public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; + Identifier name; + LogicalType type; + Value default_value; + string description; + //! AUTOMATIC until set: resolved at registration from the supported scopes + SetScope default_scope = SetScope::AUTOMATIC; + bool supports_global = true; + bool supports_session = true; + duckdb_v2_custom_option_update_callback_fn update_cb = nullptr; + shared_ptr user_data = nullptr; +}; + +static auto Convert(duckdb_v2_custom_option_handle option) -> CV2CustomOption * { + return reinterpret_cast(option); +} +static auto Convert(CV2CustomOption *option) -> duckdb_v2_custom_option_handle { + return reinterpret_cast(option); +} + +static auto ConvertWriteScope(DUCKDB_V2_SETTING_SCOPE scope) -> SetScope { + switch (scope) { + case DUCKDB_V2_SETTING_SCOPE_GLOBAL: + return SetScope::GLOBAL; + case DUCKDB_V2_SETTING_SCOPE_SESSION: + return SetScope::SESSION; + default: + throw InvalidInputException("The scope must be GLOBAL or SESSION."); + } +} + +} // namespace duckdb::capiv2 + +//---------------------------------------------------------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------------------------------------------------------- + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_custom_option_create(duckdb_v2_factory_handle factory, duckdb_v2_custom_option_handle *option, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(option); + *option = nullptr; + return WithErrorHandler(err, [&]() { + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *option = Convert(result.release()); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_name(duckdb_v2_custom_option_handle option, + const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(name); + return WithErrorHandler(err, [&]() { Convert(option)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_type(duckdb_v2_custom_option_handle option, + duckdb_v2_logical_type_handle type, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(type); + return WithErrorHandler(err, [&]() { + auto option_type = Convert(type); + Convert(option)->type = *option_type; + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_default_value(duckdb_v2_custom_option_handle option, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(value); + return WithErrorHandler(err, [&]() { Convert(option)->default_value = *Convert(value); }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_description(duckdb_v2_custom_option_handle option, + const duckdb_v2_str *description, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(description); + return WithErrorHandler(err, [&]() { Convert(option)->description = duckdb::string(Convert(description)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_default_scope(duckdb_v2_custom_option_handle option, + DUCKDB_V2_SETTING_SCOPE scope, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + return WithErrorHandler(err, [&]() { Convert(option)->default_scope = ConvertWriteScope(scope); }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_supports_scope(duckdb_v2_custom_option_handle option, + DUCKDB_V2_SETTING_SCOPE scope, bool supported, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(option); + if (ConvertWriteScope(scope) == duckdb::SetScope::GLOBAL) { + self.supports_global = supported; + } else { + self.supports_session = supported; + } + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_user_data(duckdb_v2_custom_option_handle option, duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(data); + return WithErrorHandler(err, [&]() { + Convert(option)->user_data = duckdb::make_shared_ptr(data->ptr, data->destroy, data->equals); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_set_update_callback(duckdb_v2_custom_option_handle option, + duckdb_v2_custom_option_update_callback_fn callback, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + return WithErrorHandler(err, [&]() { Convert(option)->update_cb = callback; }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_user_data(duckdb_v2_custom_option_update_info_handle info, + void **data, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(data); + return WithErrorHandler(err, [&]() { *data = Convert(info)->in_user_data; }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_value(duckdb_v2_custom_option_update_info_handle info, + duckdb_v2_value_handle *value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(value); + *value = nullptr; + return WithErrorHandler(err, [&]() { *value = Convert(new duckdb::Value(Convert(info)->value)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_update_set_value(duckdb_v2_custom_option_update_info_handle info, + duckdb_v2_value_handle value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(value); + return WithErrorHandler(err, [&]() { + auto &update = *Convert(info); + update.value = Convert(value)->CastAs(update.context, update.type); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_update_get_scope(duckdb_v2_custom_option_update_info_handle info, + DUCKDB_V2_SETTING_SCOPE *scope, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(scope); + return WithErrorHandler(err, [&]() { + *scope = Convert(info)->scope == duckdb::SetScope::GLOBAL ? DUCKDB_V2_SETTING_SCOPE_GLOBAL + : DUCKDB_V2_SETTING_SCOPE_SESSION; + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_update_is_reset(duckdb_v2_custom_option_update_info_handle info, bool *is_reset, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(is_reset); + return WithErrorHandler(err, [&]() { *is_reset = Convert(info)->is_reset; }); +} + +DUCKDB_V2_ERROR duckdb_v2_connection_register_custom_option(duckdb_v2_connection_handle conn, + duckdb_v2_custom_option_handle option, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(option); + return WithErrorHandler(err, [&]() { Convert(option)->Register(*Convert(conn)->context->db); }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_custom_option(duckdb_v2_extension_handle extension, + duckdb_v2_custom_option_handle option, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(option); + return WithErrorHandler(err, [&]() { + auto &loader = GetExtensionLoader(extension); + Convert(option)->Register(loader.GetDatabaseInstance()); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_custom_option_destroy(duckdb_v2_custom_option_handle *option) { + return WithErrorHandler(nullptr, [&]() { + if (!option) { + return; + } + if (*option) { + delete Convert(*option); + *option = nullptr; + } + }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_custom_type.cpp b/src/duckdb/src/main/capi/v2/capi_v2_custom_type.cpp index 0b5c98e3e..74ba3a4fd 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_custom_type.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_custom_type.cpp @@ -6,9 +6,13 @@ namespace duckdb::capiv2 { class CV2CustomType { public: + explicit CV2CustomType(DatabaseInstance &db) : db(db) { + } + // Validates the configuration and returns the type to install: the base type carrying the custom type's name as // its alias, which is what makes it logically distinct from the base type. - LogicalType Build() { + LogicalType Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "custom type"); if (name.empty()) { throw InvalidInputException("Type name cannot be empty."); } @@ -21,56 +25,13 @@ class CV2CustomType { return base_type.WithAlias(name.GetIdentifierName()); } - void Register() { - RegisterToCatalog(Build()); - } - - virtual ~CV2CustomType() = default; - virtual void RegisterToCatalog(LogicalType type) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; Identifier name; LogicalType base_type; }; -class CV2ConnectionCustomType : public CV2CustomType { -public: - explicit CV2ConnectionCustomType(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(LogicalType type) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - // Read the name before the type is moved out: sibling arguments have no evaluation order. - auto name = type.GetAlias(); - CreateTypeInfo type_info(std::move(name), std::move(type)); - type_info.temporary = true; - type_info.internal = true; - type_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateType(context, type_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionCustomType : public CV2CustomType { -public: - explicit CV2ExtensionCustomType(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(LogicalType type) override { - auto name = type.GetAlias(); - loader.RegisterType(std::move(name), std::move(type)); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_custom_type_handle type) -> CV2CustomType * { return reinterpret_cast(type); } @@ -86,29 +47,14 @@ static auto Convert(CV2CustomType *type) -> duckdb_v2_custom_type_handle { using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_custom_type_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_custom_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(out_type); - *out_type = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto type = duckdb::make_uniq(conn); - *out_type = Convert(type.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_custom_type_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_custom_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); - DUCKDB_CHECK_ARG(out_type); - *out_type = nullptr; +DUCKDB_V2_ERROR duckdb_v2_custom_type_create(duckdb_v2_factory_handle factory, duckdb_v2_custom_type_handle *type, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(type); + *type = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto type = duckdb::make_uniq(loader); - *out_type = Convert(type.release()); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *type = Convert(result.release()); }); } @@ -130,9 +76,37 @@ DUCKDB_V2_ERROR duckdb_v2_custom_type_set_base_type(duckdb_v2_custom_type_handle }); } -DUCKDB_V2_ERROR duckdb_v2_custom_type_register(duckdb_v2_custom_type_handle type, duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_custom_type(duckdb_v2_connection_handle conn, + duckdb_v2_custom_type_handle type, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); DUCKDB_CHECK_ARG(type); - return WithErrorHandler(err, [&]() { Convert(type)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto type_value = Convert(type)->Build(*context.db); + // Read the name before the type is moved out: sibling arguments have no evaluation order. + auto name = type_value.GetAlias(); + duckdb::CreateTypeInfo info(std::move(name), std::move(type_value)); + info.temporary = true; + info.internal = true; + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + duckdb::Catalog::GetSystemCatalog(context).CreateType(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_custom_type(duckdb_v2_extension_handle extension, + duckdb_v2_custom_type_handle type, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(type); + return WithErrorHandler(err, [&]() { + auto &loader = GetExtensionLoader(extension); + auto type_value = Convert(type)->Build(loader.GetDatabaseInstance()); + auto name = type_value.GetAlias(); + loader.RegisterType(std::move(name), std::move(type_value)); + }); } DUCKDB_V2_ERROR duckdb_v2_custom_type_destroy(duckdb_v2_custom_type_handle *type) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_data_chunk.cpp b/src/duckdb/src/main/capi/v2/capi_v2_data_chunk.cpp index 82c0b34b7..b4da83873 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_data_chunk.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_data_chunk.cpp @@ -29,12 +29,12 @@ static void CreateDataChunk(Allocator &allocator, const duckdb_v2_logical_type_h *out_chunk = Convert(chunk.release()); } -static void CopyDataChunk(ClientContext &context, duckdb_v2_data_chunk_handle chunk, +static void CopyDataChunk(DatabaseInstance &db, duckdb_v2_data_chunk_handle chunk, duckdb_v2_data_chunk_handle *out_chunk) { *out_chunk = nullptr; auto &source = *Convert(chunk); auto copy = make_uniq(); - copy->Initialize(Allocator::Get(context), source.GetTypes(), MaxValue(source.size(), STANDARD_VECTOR_SIZE)); + copy->Initialize(Allocator::Get(db), source.GetTypes(), MaxValue(source.size(), STANDARD_VECTOR_SIZE)); source.Copy(*copy); copy->SetCardinalityUnsafe(source.size()); *out_chunk = Convert(copy.release()); @@ -46,55 +46,28 @@ static void CopyDataChunk(ClientContext &context, duckdb_v2_data_chunk_handle ch using namespace duckdb::capiv2; // TODO: this should be removed. -DUCKDB_V2_ERROR duckdb_v2_data_chunk_create(const duckdb_v2_logical_type_handle *types, idx_t column_count, +DUCKDB_V2_ERROR duckdb_v2_data_chunk_create(duckdb_v2_factory_handle factory, + const duckdb_v2_logical_type_handle *types, idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(types); - DUCKDB_CHECK_ARG(out_chunk); - return WithErrorHandler( - err, [&]() { CreateDataChunk(duckdb::Allocator::DefaultAllocator(), types, column_count, out_chunk); }); -} - -DUCKDB_V2_ERROR duckdb_v2_data_chunk_create_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_logical_type_handle *types, - idx_t column_count, duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(types); DUCKDB_CHECK_ARG(out_chunk); return WithErrorHandler(err, [&]() { - CreateDataChunk(duckdb::Allocator::Get(*Convert(conn)->context), types, column_count, out_chunk); + CreateDataChunk(duckdb::Allocator::Get(Convert(factory)->GetDatabase()), types, column_count, out_chunk); }); } -DUCKDB_V2_ERROR duckdb_v2_data_chunk_create_with_context(duckdb_v2_context_handle context, - const duckdb_v2_logical_type_handle *types, idx_t column_count, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(context); - DUCKDB_CHECK_ARG(types); - DUCKDB_CHECK_ARG(out_chunk); - return WithErrorHandler( - err, [&]() { CreateDataChunk(duckdb::Allocator::Get(*Convert(context)), types, column_count, out_chunk); }); -} - -DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_data_chunk_handle chunk, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy(duckdb_v2_factory_handle factory, duckdb_v2_data_chunk_handle chunk, + duckdb_v2_data_chunk_handle *out_chunk, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(chunk); DUCKDB_CHECK_ARG(out_chunk); - return WithErrorHandler(err, [&]() { CopyDataChunk(*Convert(conn)->context, chunk, out_chunk); }); + return WithErrorHandler(err, [&]() { CopyDataChunk(Convert(factory)->GetDatabase(), chunk, out_chunk); }); } -DUCKDB_V2_ERROR duckdb_v2_data_chunk_copy_with_context(duckdb_v2_context_handle context, - duckdb_v2_data_chunk_handle chunk, - duckdb_v2_data_chunk_handle *out_chunk, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(context); +DUCKDB_V2_ERROR duckdb_v2_data_chunk_clear(duckdb_v2_data_chunk_handle chunk, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(chunk); - DUCKDB_CHECK_ARG(out_chunk); - return WithErrorHandler(err, [&]() { CopyDataChunk(*Convert(context), chunk, out_chunk); }); + return WithErrorHandler(err, [&]() { Convert(chunk)->Reset(); }); } DUCKDB_V2_ERROR duckdb_v2_data_chunk_destroy(duckdb_v2_data_chunk_handle *chunk) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_extension.cpp b/src/duckdb/src/main/capi/v2/capi_v2_extension.cpp index 805cbc090..b1145dcc9 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_extension.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_extension.cpp @@ -50,7 +50,7 @@ namespace { //! context to lend (startup, static linking, autoloading). Rolls back unless the load finished. class CAPIV2LoadScope { public: - explicit CAPIV2LoadScope(DatabaseInstance &db) : con(db) { + explicit CAPIV2LoadScope(DatabaseInstance &db) : con(Connection::CreateInternal(db)) { con.BeginTransaction(); } @@ -130,7 +130,8 @@ void CallEntrypoint(DuckDBExtensionLoadStateV2 &load_state, const string &extens ::duckdb_v2_extension_input input; input.get_api = statically_linked ? nullptr : ExtensionGetAPIV2; input.extension = load_state.ToCStruct(); - input.context = capiv2::Convert(&context); + capiv2::CV2CallbackContext callback_context(context); + input.context = capiv2::Convert(&callback_context); input.err = &err_handle; (*init_fun)(&input); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_factory.cpp b/src/duckdb/src/main/capi/v2/capi_v2_factory.cpp new file mode 100644 index 000000000..6084f4d08 --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_factory.cpp @@ -0,0 +1,29 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_instance_get_factory(duckdb_v2_instance_handle instance, + duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); + DUCKDB_CHECK_ARG(out_factory); + *out_factory = nullptr; + return WithErrorHandler(err, [&]() { *out_factory = Convert(&Convert(instance)->factory); }); +} + +DUCKDB_V2_ERROR duckdb_v2_connection_get_factory(duckdb_v2_connection_handle conn, + duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(out_factory); + *out_factory = nullptr; + return WithErrorHandler(err, [&]() { *out_factory = Convert(&Convert(conn)->context_handle.factory); }); +} + +DUCKDB_V2_ERROR duckdb_v2_context_get_factory(duckdb_v2_context_handle ctx, duckdb_v2_factory_handle *out_factory, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(ctx); + DUCKDB_CHECK_ARG(out_factory); + *out_factory = nullptr; + return WithErrorHandler(err, [&]() { *out_factory = Convert(&Convert(ctx)->factory); }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_file_system.cpp b/src/duckdb/src/main/capi/v2/capi_v2_file_system.cpp index 7850f04e8..e0704579a 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_file_system.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_file_system.cpp @@ -7,24 +7,6 @@ namespace duckdb::capiv2 { -// The file system handle, borrowed. It carries the context it was taken from alongside the file system itself, so -// that reads and writes can hand the engine a QueryContext and have their bytes attributed to the query. Kept on the -// context's own state, so the handle stays borrowed -- one per context, alive exactly as long as the context is. -class CV2FileSystem : public ClientContextState { -public: - optional_ptr fs; - //! The context the file system was taken from, so reads and writes can be attributed to the query. - QueryContext query; -}; - -inline auto GetFileSystemSlot(ClientContext &context) -> shared_ptr { - constexpr auto FILE_SYSTEM_SLOT_KEY = "c_api_v2_file_system"; - auto slot = context.registered_state->GetOrCreate(FILE_SYSTEM_SLOT_KEY); - slot->fs = &FileSystem::GetFileSystem(context); - slot->query = context; - return slot; -} - // How a file is opened, owned. Holds the flag word as given rather than the engine's FileOpenFlags, so that // "no flags set yet" stays distinguishable and is reported when the options are actually used. class CV2FileOpenOptions { @@ -118,25 +100,31 @@ static void ApplyFileFlag(CV2FileOpenOptions &options, DUCKDB_V2_FILE_FLAG flag) using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_file_system_get_from_context(duckdb_v2_context_handle context, - duckdb_v2_file_system_handle *out_file_system, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_context_get_file_system(duckdb_v2_context_handle context, + duckdb_v2_file_system_handle *out_file_system, + duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(context); DUCKDB_CHECK_ARG(out_file_system); *out_file_system = nullptr; - return WithErrorHandler(err, [&]() { *out_file_system = Convert(GetFileSystemSlot(*Convert(context)).get()); }); + return WithErrorHandler(err, [&]() { *out_file_system = Convert(&Convert(context)->file_system); }); } -DUCKDB_V2_ERROR duckdb_v2_file_system_get_from_connection(duckdb_v2_connection_handle connection, - duckdb_v2_file_system_handle *out_file_system, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); +DUCKDB_V2_ERROR duckdb_v2_instance_get_file_system(duckdb_v2_instance_handle instance, + duckdb_v2_file_system_handle *out_file_system, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); DUCKDB_CHECK_ARG(out_file_system); *out_file_system = nullptr; - return WithErrorHandler(err, [&]() { - auto &context = *Convert(connection)->context; - *out_file_system = Convert(GetFileSystemSlot(context).get()); - }); + return WithErrorHandler(err, [&]() { *out_file_system = Convert(&Convert(instance)->file_system); }); +} + +DUCKDB_V2_ERROR duckdb_v2_connection_get_file_system(duckdb_v2_connection_handle conn, + duckdb_v2_file_system_handle *out_file_system, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(out_file_system); + *out_file_system = nullptr; + return WithErrorHandler(err, [&]() { *out_file_system = Convert(&Convert(conn)->context_handle.file_system); }); } DUCKDB_V2_ERROR duckdb_v2_file_open_options_create(duckdb_v2_file_system_handle file_system, @@ -208,7 +196,7 @@ DUCKDB_V2_ERROR duckdb_v2_file_system_open(duckdb_v2_file_system_handle file_sys // No opener is passed: FileSystem::GetFileSystem hands back the context's own OpenerFileSystem, which // pushes the opener itself -- which is how a remote file system reaches settings and secrets. Supplying one // here is rejected outright ("the opener is pushed automatically"). - auto handle = slot.fs->OpenFile(info, opts.flags); + auto handle = slot.fs.OpenFile(info, opts.flags); if (!handle) { throw duckdb::IOException("Failed to open file: %s", info.path); } @@ -219,6 +207,79 @@ DUCKDB_V2_ERROR duckdb_v2_file_system_open(duckdb_v2_file_system_handle file_sys }); } +DUCKDB_V2_ERROR duckdb_v2_file_system_path_exists(duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, + bool *out_exists, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(path); + DUCKDB_CHECK_ARG(out_exists); + return WithErrorHandler( + err, [&]() { *out_exists = Convert(file_system)->fs.FileExists(duckdb::string(Convert(path))); }); +} + +DUCKDB_V2_ERROR duckdb_v2_file_system_path_is_remote(duckdb_v2_file_system_handle file_system, + const duckdb_v2_str *path, bool *out_is_remote, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(path); + DUCKDB_CHECK_ARG(out_is_remote); + return WithErrorHandler( + err, [&]() { *out_is_remote = duckdb::FileSystem::IsRemoteFile(duckdb::string(Convert(path))); }); +} + +DUCKDB_V2_ERROR duckdb_v2_file_system_path_expand(duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, + duckdb_v2_text_sink_fn sink, void *user_data, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(path); + DUCKDB_CHECK_ARG(sink); + return WithErrorHandler(err, [&]() { + auto expanded = Convert(file_system)->fs.ExpandPath(duckdb::string(Convert(path))); + InvokeTextSink(sink, duckdb_v2_str {expanded.data(), expanded.size()}, user_data); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_file_system_path_glob(duckdb_v2_file_system_handle file_system, const duckdb_v2_str *pattern, + duckdb_v2_file_system_path_glob_callback_fn callback, void *user_data, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(pattern); + DUCKDB_CHECK_ARG(callback); + return WithErrorHandler(err, [&]() { + auto matches = Convert(file_system)->fs.Glob(duckdb::string(Convert(pattern))); + for (auto &match : matches) { + CV2ErrorInfo info; + auto handle = Convert(&info); + duckdb_v2_str path {match.path.data(), match.path.size()}; + callback(&path, user_data, &handle); + if (info.HasError()) { + info.ThrowAsException(); + } + } + }); +} + +DUCKDB_V2_ERROR duckdb_v2_file_system_path_move(duckdb_v2_file_system_handle file_system, const duckdb_v2_str *source, + const duckdb_v2_str *target, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(source); + DUCKDB_CHECK_ARG(target); + return WithErrorHandler(err, [&]() { + Convert(file_system)->fs.MoveFile(duckdb::string(Convert(source)), duckdb::string(Convert(target))); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_file_system_path_remove(duckdb_v2_file_system_handle file_system, const duckdb_v2_str *path, + bool *out_removed, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(file_system); + DUCKDB_CHECK_ARG(path); + return WithErrorHandler(err, [&]() { + auto removed = Convert(file_system)->fs.TryRemoveFile(duckdb::string(Convert(path))); + if (out_removed) { + *out_removed = removed; + } + }); +} + DUCKDB_V2_ERROR duckdb_v2_file_read(duckdb_v2_file_handle file, void *buffer, idx_t buffer_size, idx_t *bytes_read, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(file); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func.cpp index 9365f1cdb..b1db328e5 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func.cpp @@ -32,9 +32,10 @@ Value CV2ExpressionBindInfo::GetArgValue(idx_t index) const { return input.GetConstant(index); } -CV2ConstantBindInfo::CV2ConstantBindInfo(const BoundTableFunction &function, void *user_data, - const vector &positional, const named_argument_map_t &named) - : CV2FunctionBindInfo(function, user_data), positional(positional), named(named) { +CV2ConstantBindInfo::CV2ConstantBindInfo(const BoundSimpleFunction &function, const FunctionSignature &signature, + void *user_data, const vector &positional, + const named_argument_map_t &named) + : CV2FunctionBindInfo(function, signature, user_data), positional(positional), named(named) { D_ASSERT(function.GetPositionalArgumentCount() == positional.size()); } @@ -45,7 +46,7 @@ const Value &CV2ConstantBindInfo::GetArg(idx_t index) const { const auto &name = function.GetNamedArguments()[index - positional.size()]; auto entry = named.find(name); if (entry == named.end()) { - throw InternalException("Named argument \"%s\" of a table function call has no value", name); + throw InternalException("Named argument \"%s\" of a function call has no value", name); } return entry->second; } @@ -84,9 +85,9 @@ DUCKDB_V2_ERROR duckdb_v2_function_bind_set_bind_data(duckdb_v2_function_bind_in DUCKDB_CHECK_ARG(info); DUCKDB_CHECK_ARG(data); return WithErrorHandler(err, [&]() { - if (!Convert(info)->can_set_bind_data) { - throw duckdb::InvalidInputException( - "Scalar functions set the bind data through duckdb_v2_scalar_function_bind_set_bind_data"); + auto &bind_data_error = Convert(info)->bind_data_error; + if (!bind_data_error.empty()) { + throw duckdb::InvalidInputException(bind_data_error); } Convert(info)->out_bind_data = data->ptr ? duckdb::make_shared_ptr(data->ptr, data->destroy, data->equals) : nullptr; diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_aggregate.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_aggregate.cpp index c3d8e1962..d3d64d9da 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func_aggregate.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_aggregate.cpp @@ -167,7 +167,8 @@ static auto CV2AggregateBind(BindAggregateFunctionInput &input) -> unique_ptr result = nullptr; if (bind_info.out_bind_data) { @@ -312,13 +313,15 @@ static auto CV2AggregateDestroy(Vector &state, AggregateInputData &aggr_input_da class CV2AggregateFunction { public: - CV2AggregateFunction() { + explicit CV2AggregateFunction(DatabaseInstance &db) : db(db) { // The callbacks' error slots make failure part of the API contract, so the function defaults to fallible: // a non-fallible function turns any execution error reported through the slot into an internal error. properties.SetFallible(); } - void Register() { + //! Validates the configuration and builds the aggregate function to register on `target`. + AggregateFunction Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "aggregate function"); if (name.empty()) { throw InvalidInputException("Function name cannot be empty."); } @@ -371,53 +374,19 @@ class CV2AggregateFunction { } function.SetExtraFunctionInfo(std::move(info)); - // Call the implementation to register - RegisterToCatalog(std::move(function)); + return function; } - virtual ~CV2AggregateFunction() = default; - virtual void RegisterToCatalog(AggregateFunction function) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; FunctionSignature signature; + CV2FunctionDocs docs; CV2AggregateFunctionInfo info; Identifier name; AggregateFunctionProperties properties; }; -class CV2ConnectionAggregateFunction : public CV2AggregateFunction { -public: - explicit CV2ConnectionAggregateFunction(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(AggregateFunction function) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - CreateAggregateFunctionInfo af_info(std::move(function)); - af_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateFunction(context, af_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionAggregateFunction : public CV2AggregateFunction { -public: - explicit CV2ExtensionAggregateFunction(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(AggregateFunction function) override { - loader.RegisterFunction(std::move(function)); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_aggregate_function_handle func) -> CV2AggregateFunction * { return reinterpret_cast(func); } @@ -433,29 +402,15 @@ static auto Convert(CV2AggregateFunction *func) -> duckdb_v2_aggregate_function_ using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_aggregate_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto function = duckdb::make_uniq(conn); - *out_function = Convert(function.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_aggregate_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; +DUCKDB_V2_ERROR duckdb_v2_aggregate_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_aggregate_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(function); + *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto function = duckdb::make_uniq(loader); - *out_function = Convert(function.release()); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *function = Convert(result.release()); }); } @@ -466,6 +421,14 @@ DUCKDB_V2_ERROR duckdb_v2_aggregate_function_set_name(duckdb_v2_aggregate_functi return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); } +DUCKDB_V2_ERROR duckdb_v2_aggregate_function_get_docs(duckdb_v2_aggregate_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(docs); + return WithErrorHandler(err, [&]() { *docs = Convert(&Convert(function)->docs); }); +} + DUCKDB_V2_ERROR duckdb_v2_aggregate_function_get_signature(duckdb_v2_aggregate_function_handle function, duckdb_v2_function_signature_handle *sig, duckdb_v2_error_info_handle *err) { @@ -786,10 +749,37 @@ DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy_get_states(duckdb_v2_aggreg return WithErrorHandler(err, [&]() { *states = Convert(info)->states; }); } -DUCKDB_V2_ERROR duckdb_v2_aggregate_function_register(duckdb_v2_aggregate_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_aggregate_function(duckdb_v2_connection_handle conn, + duckdb_v2_aggregate_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto &self = *Convert(function); + auto info = duckdb::CreateAggregateFunctionInfo(self.Build(*context.db)); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + catalog.CreateFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_aggregate_function(duckdb_v2_extension_handle extension, + duckdb_v2_aggregate_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + duckdb::CreateAggregateFunctionInfo info(self.Build(loader.GetDatabaseInstance())); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + loader.RegisterFunction(std::move(info)); + }); } DUCKDB_V2_ERROR duckdb_v2_aggregate_function_destroy(duckdb_v2_aggregate_function_handle *function) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_cast.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_cast.cpp index 802abdb33..60dc596a4 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func_cast.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_cast.cpp @@ -23,7 +23,7 @@ class CV2CastBoundData final : public BoundCastData { // one. The state exists purely to forward it to the exec callback. class CV2CastLocalState final : public FunctionLocalState { public: - optional_ptr context; + unique_ptr context; }; class CV2CastExecInfo { @@ -50,7 +50,7 @@ static auto CV2CastInitLocalState(CastLocalStateParameters ¶meters) -> uniqu throw InvalidInputException("Cannot initialize local state for extension cast function without a context"); } - state->context = parameters.context; + state->context = make_uniq(*parameters.context); return std::move(state); } @@ -67,9 +67,9 @@ static auto CV2CastExec(Vector &input, Vector &output, idx_t count, CastParamete args.count = count; args.mode = is_try_cast ? DUCKDB_V2_CAST_MODE_TRY : DUCKDB_V2_CAST_MODE_NORMAL; - optional_ptr context = nullptr; + optional_ptr context = nullptr; if (parameters.local_state) { - context = parameters.local_state->Cast().context; + context = parameters.local_state->Cast().context.get(); } CV2ErrorInfo err = {}; @@ -89,7 +89,12 @@ static auto CV2CastExec(Vector &input, Vector &output, idx_t count, CastParamete class CV2CastFunction { public: - void Register() { + explicit CV2CastFunction(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the cast to register on `target`. + BoundCastInfo Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "cast function"); if (!source_type.IsComplete()) { throw InvalidInputException("Source type must be set to a fully defined concrete type"); } @@ -101,13 +106,12 @@ class CV2CastFunction { } BoundCastInfo cast_info(CV2CastExec, make_uniq(exec_cb, user_data), CV2CastInitLocalState); - RegisterToCatalog(std::move(cast_info)); + return cast_info; } - virtual ~CV2CastFunction() = default; - virtual void RegisterToCatalog(BoundCastInfo cast_info) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; LogicalType source_type; LogicalType target_type; int64_t implicit_cast_cost = -1; @@ -115,37 +119,6 @@ class CV2CastFunction { shared_ptr user_data = nullptr; }; -class CV2ConnectionCastFunction : public CV2CastFunction { -public: - explicit CV2ConnectionCastFunction(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(BoundCastInfo cast_info) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &casts = CastFunctionSet::Get(context); - casts.RegisterCastFunction(source_type, target_type, std::move(cast_info), implicit_cast_cost); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionCastFunction : public CV2CastFunction { -public: - explicit CV2ExtensionCastFunction(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(BoundCastInfo cast_info) override { - loader.RegisterCastFunction(source_type, target_type, std::move(cast_info), implicit_cast_cost); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_cast_function_handle func) -> CV2CastFunction * { return reinterpret_cast(func); } @@ -161,29 +134,15 @@ static auto Convert(CV2CastFunction *func) -> duckdb_v2_cast_function_handle { using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_cast_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_cast_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto function = duckdb::make_uniq(conn); - *out_function = Convert(function.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_cast_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_cast_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; +DUCKDB_V2_ERROR duckdb_v2_cast_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_cast_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(function); + *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto function = duckdb::make_uniq(loader); - *out_function = Convert(function.release()); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *function = Convert(result.release()); }); } @@ -269,10 +228,31 @@ DUCKDB_V2_ERROR duckdb_v2_cast_function_exec_get_mode(duckdb_v2_cast_function_ex return WithErrorHandler(err, [&]() { *mode = Convert(info)->mode; }); } -DUCKDB_V2_ERROR duckdb_v2_cast_function_register(duckdb_v2_cast_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_cast_function(duckdb_v2_connection_handle conn, + duckdb_v2_cast_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &db = *Convert(conn)->context->db; + auto cast_info = self.Build(db); + duckdb::DBConfig::GetConfig(db).GetCastFunctions().RegisterCastFunction( + self.source_type, self.target_type, std::move(cast_info), self.implicit_cast_cost); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_cast_function(duckdb_v2_extension_handle extension, + duckdb_v2_cast_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + auto cast_info = self.Build(loader.GetDatabaseInstance()); + loader.RegisterCastFunction(self.source_type, self.target_type, std::move(cast_info), self.implicit_cast_cost); + }); } DUCKDB_V2_ERROR duckdb_v2_cast_function_destroy(duckdb_v2_cast_function_handle *function) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_copy.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_copy.cpp index bf9e9f8ec..9f02cd3ec 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func_copy.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_copy.cpp @@ -382,7 +382,8 @@ static auto CV2CopyToBind(ClientContext &context, CopyFunctionBindInput &input, CV2ErrorInfo err = {}; if (info.to_bind_cb) { auto err_ptr = Convert(&err); - info.to_bind_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_bind_cb(Convert(&args), Convert(&callback_context), &err_ptr); } auto result = MakeBindData(input.function_info, std::move(args.out_bind_data)); @@ -401,7 +402,8 @@ static auto CV2CopyToBatchSize(ClientContext &context, FunctionData &bind_data) CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.to_batch_size_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_batch_size_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -425,7 +427,8 @@ static auto CV2CopyToInitGlobal(ClientContext &context, FunctionData &bind_data, CV2ErrorInfo err = {}; if (info.to_init_cb) { auto err_ptr = Convert(&err); - info.to_init_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_init_cb(Convert(&args), Convert(&callback_context), &err_ptr); } auto result = make_uniq(); @@ -452,7 +455,8 @@ static auto CV2CopyToPrepareBatch(ClientContext &context, FunctionData &bind_dat CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.to_batch_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_batch_cb(Convert(&args), Convert(&callback_context), &err_ptr); auto result = make_uniq(); if (args.out_batch_data.ptr) { @@ -478,7 +482,8 @@ static auto CV2CopyToFlushBatch(ClientContext &context, FunctionData &bind_data, CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.to_flush_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_flush_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -496,7 +501,8 @@ static auto CV2CopyToReportStatistics(ClientContext &context, FunctionData &bind CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.to_statistics_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_statistics_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -518,7 +524,8 @@ static auto CV2CopyToFinalize(ClientContext &context, FunctionData &bind_data, G CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.to_finalize_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.to_finalize_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -582,7 +589,8 @@ static auto CV2CopyFromBind(ClientContext &context, CopyFromFunctionBindInput &i CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.from_bind_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.from_bind_cb(Convert(&args), Convert(&callback_context), &err_ptr); auto result = MakeBindData(function_info, std::move(args.out_bind_data)); result->cardinality = args.out_cardinality; @@ -611,7 +619,8 @@ static auto CV2CopyFromInitGlobal(ClientContext &context, TableFunctionInitInput CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.from_init_global_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.from_init_global_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (args.out_global_state.ptr) { result->handle = @@ -645,7 +654,8 @@ static auto CV2CopyFromInitLocal(ExecutionContext &context, TableFunctionInitInp CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.from_init_local_cb(Convert(&args), Convert(&context.client), &err_ptr); + CV2CallbackContext callback_context(context.client); + info.from_init_local_cb(Convert(&args), Convert(&callback_context), &err_ptr); unique_ptr result = nullptr; if (args.out_local_state.ptr) { @@ -680,7 +690,8 @@ static auto CV2CopyFromExec(ClientContext &context, TableFunctionInput &input, D CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.from_exec_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.from_exec_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -720,7 +731,8 @@ static auto CV2CopyFromProgress(ClientContext &context, const FunctionData *bind CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.from_progress_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.from_progress_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -736,7 +748,12 @@ static auto CV2CopyFromProgress(ClientContext &context, const FunctionData *bind class CV2CopyFunction { public: - void Register() { + explicit CV2CopyFunction(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the copy function to register on `target`. + CopyFunction Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "copy function"); if (name.empty()) { throw InvalidInputException("Function name cannot be empty."); } @@ -795,50 +812,17 @@ class CV2CopyFunction { function.copy_from_function = std::move(reader); } function.function_info = std::move(function_info); + function.extension = extension; - // Call the implementation to register - RegisterToCatalog(std::move(function)); + return function; } - virtual ~CV2CopyFunction() = default; - virtual void RegisterToCatalog(CopyFunction function) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; CV2CopyFunctionInfo info; Identifier name; -}; - -class CV2ConnectionCopyFunction : public CV2CopyFunction { -public: - explicit CV2ConnectionCopyFunction(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(CopyFunction function) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - CreateCopyFunctionInfo cf_info(std::move(function)); - cf_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateCopyFunction(context, cf_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionCopyFunction : public CV2CopyFunction { -public: - explicit CV2ExtensionCopyFunction(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(CopyFunction function) override { - loader.RegisterFunction(std::move(function)); - } - -private: - ExtensionLoader &loader; + string extension; }; static auto Convert(duckdb_v2_copy_function_handle func) -> CV2CopyFunction * { @@ -901,28 +885,14 @@ static auto GetOptionValue(INFO &args, idx_t index, const char *function) -> duc using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_copy_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(function); - *function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto result = duckdb::make_uniq(conn); - *function = Convert(result.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_copy_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_copy_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); +DUCKDB_V2_ERROR duckdb_v2_copy_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_copy_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(function); *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto result = duckdb::make_uniq(loader); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); *function = Convert(result.release()); }); } @@ -934,6 +904,21 @@ DUCKDB_V2_ERROR duckdb_v2_copy_function_set_name(duckdb_v2_copy_function_handle return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); } +DUCKDB_V2_ERROR duckdb_v2_copy_function_set_file_extension(duckdb_v2_copy_function_handle function, + const duckdb_v2_str *extension, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(extension); + return WithErrorHandler(err, [&]() { + duckdb::string value(extension->ptr, extension->len); + if (!value.empty() && value[0] == '.') { + throw duckdb::InvalidInputException("The file extension of a copy function is set without its leading dot, " + "e.g. \"parquet\" rather than \".parquet\"."); + } + Convert(function)->extension = std::move(value); + }); +} + DUCKDB_V2_ERROR duckdb_v2_copy_function_set_user_data(duckdb_v2_copy_function_handle function, duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(function); @@ -1505,10 +1490,31 @@ DUCKDB_V2_ERROR duckdb_v2_copy_from_progress_set_progress(duckdb_v2_copy_from_pr // Register / destroy //---------------------------------------------------------------------------------------------------------------------- -DUCKDB_V2_ERROR duckdb_v2_copy_function_register(duckdb_v2_copy_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_copy_function(duckdb_v2_connection_handle conn, + duckdb_v2_copy_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto info = duckdb::CreateCopyFunctionInfo(Convert(function)->Build(*context.db)); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + catalog.CreateCopyFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_copy_function(duckdb_v2_extension_handle extension, + duckdb_v2_copy_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &loader = GetExtensionLoader(extension); + loader.RegisterFunction(Convert(function)->Build(loader.GetDatabaseInstance())); + }); } DUCKDB_V2_ERROR duckdb_v2_copy_function_destroy(duckdb_v2_copy_function_handle *function) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_docs.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_docs.cpp new file mode 100644 index 000000000..cc3f07cfa --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_docs.cpp @@ -0,0 +1,35 @@ +#include "duckdb/main/capi_v2/capi_v2_function_internal.hpp" + +//---------------------------------------------------------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------------------------------------------------------- + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_function_docs_set_description(duckdb_v2_function_docs_handle docs, + const duckdb_v2_str *description, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(docs); + DUCKDB_CHECK_ARG(description); + return WithErrorHandler(err, [&]() { Convert(docs)->description = duckdb::string(Convert(description)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_function_docs_add_example(duckdb_v2_function_docs_handle docs, const duckdb_v2_str *title, + const duckdb_v2_str *example, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(docs); + if (title) { + DUCKDB_CHECK_ARG(title); + } + DUCKDB_CHECK_ARG(example); + return WithErrorHandler(err, [&]() { + duckdb::string title_text = title ? duckdb::string(Convert(title)) : duckdb::string(); + Convert(docs)->examples.push_back({std::move(title_text), duckdb::string(Convert(example))}); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_function_docs_add_category(duckdb_v2_function_docs_handle docs, const duckdb_v2_str *category, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(docs); + DUCKDB_CHECK_ARG(category); + return WithErrorHandler(err, [&]() { Convert(docs)->categories.emplace_back(Convert(category)); }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_macro.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_macro.cpp new file mode 100644 index 000000000..098cdef4b --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_macro.cpp @@ -0,0 +1,198 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/main/capi_v2/capi_v2_function_internal.hpp" +#include "duckdb/function/scalar_macro_function.hpp" +#include "duckdb/function/table_macro_function.hpp" +#include "duckdb/parser/expression/columnref_expression.hpp" +#include "duckdb/parser/expression/constant_expression.hpp" +#include "duckdb/parser/parsed_data/create_macro_info.hpp" +#include "duckdb/parser/parser.hpp" +#include "duckdb/parser/statement/select_statement.hpp" + +namespace duckdb::capiv2 { + +class CV2MacroFunction { +public: + CV2MacroFunction(DatabaseInstance &db, MacroType type) : db(db), type(type) { + } + + //! Validates the configuration and builds the macro to register on `target`. + unique_ptr Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "macro function"); + if (name.empty()) { + throw InvalidInputException("Function name cannot be empty."); + } + if (body.empty()) { + throw InvalidInputException("A body must be set for the macro function."); + } + if (signature.GetReturnType().id() != LogicalTypeId::INVALID) { + throw InvalidInputException( + "A macro function signature cannot set a return type: the result follows from the body."); + } + if (signature.GetTypedKwargs()) { + throw InvalidInputException("A macro function cannot have variadic parameters."); + } + signature.Verify(); + + auto macro = ParseBody(); + for (idx_t i = 0; i < signature.GetParameterCount(); i++) { + auto ¶meter = signature.GetParameter(i); + switch (parameter.GetKind()) { + case FunctionParameterKind::STANDARD: + break; + case FunctionParameterKind::VAR_POSITIONAL: + case FunctionParameterKind::VAR_KEYWORD: + throw InvalidInputException("A macro function cannot have variadic parameters, but \"%s\" is one.", + parameter.GetName().GetIdentifierName()); + default: + throw InvalidInputException( + "The parameters of a macro function can always be passed by position or by name, so \"%s\" must " + "be a standard parameter.", + parameter.GetName().GetIdentifierName()); + } + auto ¶meter_name = parameter.GetName(); + // ANY leaves the parameter untyped, as a parameter declared without a type + auto parameter_type = + parameter.GetType().id() == LogicalTypeId::ANY ? LogicalType::UNKNOWN : parameter.GetType(); + if (parameter.HasDefaultValue()) { + auto default_value = *parameter.GetDefaultValue(); + if (parameter_type.id() != LogicalTypeId::UNKNOWN) { + default_value = default_value.DefaultCastAs(parameter_type); + } + auto default_expr = ConstantExpression::FromValue(default_value); + default_expr->SetAlias(parameter_name); + macro->default_parameters[parameter_name] = std::move(default_expr); + } + macro->parameters.push_back(make_uniq(parameter_name)); + macro->types.push_back(std::move(parameter_type)); + } + + auto catalog_type = type == MacroType::TABLE_MACRO ? CatalogType::TABLE_MACRO_ENTRY : CatalogType::MACRO_ENTRY; + auto parameter_types = macro->types; + auto info = make_uniq(catalog_type); + info->macros.push_back(std::move(macro)); + info->SetQualifiedName(QualifiedName(Identifier::InvalidCatalog(), Identifier::DefaultSchema(), name)); + info->internal = true; + docs.AddTo(std::move(parameter_types), *info); + return info; + } + +public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; + MacroType type; + Identifier name; + string body; + FunctionSignature signature; + CV2FunctionDocs docs; + +private: + unique_ptr ParseBody() const { + auto parser = Parser::GetBuiltinParser(); + if (type == MacroType::SCALAR_MACRO) { + auto expressions = parser.ParseExpressionList(body); + if (expressions.size() != 1) { + throw InvalidInputException("The body of a scalar macro function must be a single expression."); + } + return make_uniq(std::move(expressions[0])); + } + parser.ParseQuery(body); + if (parser.statements.size() != 1 || parser.statements[0]->type != StatementType::SELECT_STATEMENT) { + throw InvalidInputException("The body of a table macro function must be a single SELECT query."); + } + auto &select = parser.statements[0]->Cast(); + return make_uniq(std::move(select.node)); + } +}; + +static auto Convert(duckdb_v2_macro_function_handle function) -> CV2MacroFunction * { + return reinterpret_cast(function); +} +static auto Convert(CV2MacroFunction *function) -> duckdb_v2_macro_function_handle { + return reinterpret_cast(function); +} + +} // namespace duckdb::capiv2 + +//---------------------------------------------------------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------------------------------------------------------- + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_macro_function_create(duckdb_v2_factory_handle factory, DUCKDB_V2_MACRO_FUNCTION_TYPE type, + duckdb_v2_macro_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(function); + *function = nullptr; + return WithErrorHandler(err, [&]() { + duckdb::MacroType macro_type; + switch (type) { + case DUCKDB_V2_MACRO_FUNCTION_TYPE_SCALAR: + macro_type = duckdb::MacroType::SCALAR_MACRO; + break; + case DUCKDB_V2_MACRO_FUNCTION_TYPE_TABLE: + macro_type = duckdb::MacroType::TABLE_MACRO; + break; + default: + throw duckdb::InvalidInputException("Invalid macro function type %d", static_cast(type)); + } + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase(), macro_type); + *function = Convert(result.release()); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_macro_function_set_name(duckdb_v2_macro_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(name); + return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_macro_function_set_body(duckdb_v2_macro_function_handle function, const duckdb_v2_str *body, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(body); + return WithErrorHandler(err, [&]() { Convert(function)->body = duckdb::string(Convert(body)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_macro_function_get_signature(duckdb_v2_macro_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(sig); + return WithErrorHandler(err, [&]() { *sig = Convert(&Convert(function)->signature); }); +} + +DUCKDB_V2_ERROR duckdb_v2_macro_function_get_docs(duckdb_v2_macro_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(docs); + return WithErrorHandler(err, [&]() { *docs = Convert(&Convert(function)->docs); }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_macro_function(duckdb_v2_extension_handle extension, + duckdb_v2_macro_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &loader = GetExtensionLoader(extension); + auto info = Convert(function)->Build(loader.GetDatabaseInstance()); + loader.RegisterFunction(*info); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_macro_function_destroy(duckdb_v2_macro_function_handle *function) { + return WithErrorHandler(nullptr, [&]() { + if (!function) { + return; + } + if (*function) { + delete Convert(*function); + *function = nullptr; + } + }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_pragma.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_pragma.cpp new file mode 100644 index 000000000..444c0ddd6 --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_pragma.cpp @@ -0,0 +1,240 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/main/capi_v2/capi_v2_function_internal.hpp" +#include "duckdb/catalog/catalog.hpp" +#include "duckdb/function/pragma_function.hpp" +#include "duckdb/parser/parsed_data/create_pragma_function_info.hpp" + +namespace duckdb::capiv2 { + +class CV2PragmaFunctionInfo final : public PragmaFunctionInfo { +public: + duckdb_v2_pragma_function_query_callback_fn query_cb = nullptr; + duckdb_v2_pragma_function_exec_callback_fn exec_cb = nullptr; + shared_ptr user_data = nullptr; +}; + +class CV2PragmaQueryInfo { +public: + //! The query the callback set, if any + optional out_query; +}; + +static auto Convert(duckdb_v2_pragma_function_query_info_handle info) -> CV2PragmaQueryInfo * { + return reinterpret_cast(info); +} +static auto Convert(CV2PragmaQueryInfo *info) -> duckdb_v2_pragma_function_query_info_handle { + return reinterpret_cast(info); +} + +//! Hands the arguments of the PRAGMA statement to `callback`, and throws the error it reported +template +static auto CV2PragmaInvoke(ClientContext &context, const FunctionParameters ¶meters, FUNC &&callback) -> void { + if (!parameters.function) { + throw InternalException("A pragma function was called without its function"); + } + auto &function = *parameters.function; + auto &info = function.function_info->Cast(); + auto &signature = function.GetSignature(); + + // the binder places the arguments as it does for a table function, so the call is laid out the same way + BoundSimpleFunction bound_function; + bound_function.SetName(function.name); + bound_function.SetCallArguments(signature, parameters.values, parameters.named_parameters); + CV2ConstantBindInfo bind_info(bound_function, signature, info.user_data ? info.user_data->GetData() : nullptr, + parameters.values, parameters.named_parameters); + bind_info.bind_data_error = "Pragma functions have no bind data"; + + CV2ErrorInfo err = {}; + auto err_ptr = Convert(&err); + CV2CallbackContext callback_context(context); + callback(info, Convert(&bind_info), Convert(&callback_context), &err_ptr); + + if (err.HasError()) { + err.ThrowAsException(); + } +} + +static auto CV2PragmaQuery(ClientContext &context, const FunctionParameters ¶meters) -> string { + CV2PragmaQueryInfo result_info; + CV2PragmaInvoke(context, parameters, [&](const CV2PragmaFunctionInfo &info, auto bind_info, auto ctx, auto err) { + info.query_cb(bind_info, Convert(&result_info), ctx, err); + }); + if (!result_info.out_query) { + throw InvalidInputException("The query callback of pragma function \"%s\" did not set a query.", + parameters.function->name); + } + return std::move(*result_info.out_query); +} + +static auto CV2PragmaExec(ClientContext &context, const FunctionParameters ¶meters) -> void { + CV2PragmaInvoke(context, parameters, [&](const CV2PragmaFunctionInfo &info, auto bind_info, auto ctx, auto err) { + info.exec_cb(bind_info, ctx, err); + }); +} + +class CV2PragmaFunction { +public: + explicit CV2PragmaFunction(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the pragma function to register on `target`. + PragmaFunction Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "pragma function"); + if (name.empty()) { + throw InvalidInputException("Function name cannot be empty."); + } + if (info.query_cb && info.exec_cb) { + throw InvalidInputException("A pragma function cannot have both a query callback and an exec callback."); + } + if (!info.query_cb && !info.exec_cb) { + throw InvalidInputException("Either a query callback or an exec callback must be set for the function."); + } + if (signature.GetReturnType().id() != LogicalTypeId::INVALID) { + throw InvalidInputException("A pragma function signature cannot set a return type."); + } + + signature.Verify(); + + auto function = info.query_cb ? PragmaFunction::PragmaCall(name, CV2PragmaQuery, signature) + : PragmaFunction::PragmaCall(name, CV2PragmaExec, signature); + if (signature.GetParameterCount() == 0) { + function.type = PragmaType::PRAGMA_STATEMENT; + } + function.function_info = make_shared_ptr(std::move(info)); + return function; + } + +public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; + FunctionSignature signature; + CV2FunctionDocs docs; + CV2PragmaFunctionInfo info; + Identifier name; +}; + +static auto Convert(duckdb_v2_pragma_function_handle func) -> CV2PragmaFunction * { + return reinterpret_cast(func); +} +static auto Convert(CV2PragmaFunction *func) -> duckdb_v2_pragma_function_handle { + return reinterpret_cast(func); +} + +} // namespace duckdb::capiv2 + +//---------------------------------------------------------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------------------------------------------------------- + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_pragma_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(function); + *function = nullptr; + return WithErrorHandler(err, [&]() { + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *function = Convert(result.release()); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_name(duckdb_v2_pragma_function_handle function, + const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(name); + return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_get_docs(duckdb_v2_pragma_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(docs); + return WithErrorHandler(err, [&]() { *docs = Convert(&Convert(function)->docs); }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_get_signature(duckdb_v2_pragma_function_handle function, + duckdb_v2_function_signature_handle *sig, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(sig); + return WithErrorHandler(err, [&]() { *sig = Convert(&Convert(function)->signature); }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_user_data(duckdb_v2_pragma_function_handle function, + duckdb_v2_opaque *data, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(data); + return WithErrorHandler(err, [&]() { + Convert(function)->info.user_data = + duckdb::make_shared_ptr(data->ptr, data->destroy, data->equals); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_query_callback(duckdb_v2_pragma_function_handle function, + duckdb_v2_pragma_function_query_callback_fn callback, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { Convert(function)->info.query_cb = callback; }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_set_exec_callback(duckdb_v2_pragma_function_handle function, + duckdb_v2_pragma_function_exec_callback_fn callback, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { Convert(function)->info.exec_cb = callback; }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_query_set_query(duckdb_v2_pragma_function_query_info_handle info, + const duckdb_v2_str *query, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(query); + return WithErrorHandler(err, [&]() { Convert(info)->out_query = duckdb::string(Convert(query)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_connection_register_pragma_function(duckdb_v2_connection_handle conn, + duckdb_v2_pragma_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto &self = *Convert(function); + duckdb::CreatePragmaFunctionInfo info(self.Build(*context.db)); + self.docs.AddTo(self.signature, info); + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + catalog.CreatePragmaFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_pragma_function(duckdb_v2_extension_handle extension, + duckdb_v2_pragma_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + duckdb::CreatePragmaFunctionInfo info(self.Build(loader.GetDatabaseInstance())); + self.docs.AddTo(self.signature, info); + loader.RegisterFunction(std::move(info)); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_pragma_function_destroy(duckdb_v2_pragma_function_handle *function) { + return WithErrorHandler(nullptr, [&]() { + if (!function) { + return; + } + if (*function) { + delete Convert(*function); + *function = nullptr; + } + }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_scalar.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_scalar.cpp index d5727f548..5e5b1ff85 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func_scalar.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_scalar.cpp @@ -28,6 +28,9 @@ class CV2FunctionLocalState final : public FunctionLocalState { CV2UserData handle; }; +static constexpr const char *SCALAR_BIND_DATA_ERROR = + "Scalar functions set the bind data through duckdb_v2_scalar_function_bind_set_bind_data"; + //! The arguments of a scalar call before they are cast to the parameter types - the bind data cannot be set class CV2ResolveTypesBindInfo final : public CV2FunctionBindInfo { public: @@ -35,7 +38,7 @@ class CV2ResolveTypesBindInfo final : public CV2FunctionBindInfo { const ResolveScalarFunctionTypesInput &input) : CV2FunctionBindInfo(function, user_data), input(input) { D_ASSERT(GetArgCount() == input.GetArgumentCount()); - can_set_bind_data = false; + bind_data_error = SCALAR_BIND_DATA_ERROR; } LogicalType GetArgType(idx_t index) const override { @@ -129,7 +132,8 @@ static auto CV2ScalarResolveTypes(ResolveScalarFunctionTypesInput &input) -> voi CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.resolve_types_cb(Convert(&bind_info), Convert(&result_info), Convert(&input.GetClientContext()), &err_ptr); + CV2CallbackContext callback_context(input.GetClientContext()); + info.resolve_types_cb(Convert(&bind_info), Convert(&result_info), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -141,12 +145,13 @@ static auto CV2ScalarBind(BindScalarFunctionInput &input) -> unique_ptrGetData() : nullptr, input); - bind_info.can_set_bind_data = false; + bind_info.bind_data_error = SCALAR_BIND_DATA_ERROR; CV2BindInfo result_info; CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.bind_cb(Convert(&bind_info), Convert(&result_info), Convert(&input.GetClientContext()), &err_ptr); + CV2CallbackContext callback_context(input.GetClientContext()); + info.bind_cb(Convert(&bind_info), Convert(&result_info), Convert(&callback_context), &err_ptr); unique_ptr result = nullptr; if (result_info.out_bind_data) { @@ -174,7 +179,8 @@ static auto CV2ScalarInit(ExpressionState &state, const BoundFunctionExpression CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.init_cb(Convert(&args), Convert(&state.GetContext()), &err_ptr); + CV2CallbackContext callback_context(state.GetContext()); + info.init_cb(Convert(&args), Convert(&callback_context), &err_ptr); unique_ptr result = nullptr; if (args.out_init_data.ptr) { @@ -217,7 +223,8 @@ static auto CV2ScalarExec(DataChunk &input, ExpressionState &state, Vector &resu CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.exec_cb(Convert(&args), Convert(&state.GetContext()), &err_ptr); + CV2CallbackContext callback_context(state.GetContext()); + info.exec_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -226,13 +233,15 @@ static auto CV2ScalarExec(DataChunk &input, ExpressionState &state, Vector &resu class CV2ScalarFunction { public: - CV2ScalarFunction() { + explicit CV2ScalarFunction(DatabaseInstance &db) : db(db) { // The exec callback's error slot makes failure part of the API contract, so the function defaults to fallible: // a non-fallible function turns any execution error reported through the slot into an internal error. properties.SetFallible(); } - void Register() { + //! Validates the configuration and builds the scalar function to register on `target`. + ScalarFunction Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "scalar function"); if (name.empty()) { throw InvalidInputException("Function name cannot be empty."); } @@ -275,53 +284,19 @@ class CV2ScalarFunction { } function.SetExtraFunctionInfo(std::move(info)); - // Call the implementation to register - RegisterToCatalog(std::move(function)); + return function; } - virtual ~CV2ScalarFunction() = default; - virtual void RegisterToCatalog(ScalarFunction function) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; FunctionSignature signature; + CV2FunctionDocs docs; CV2ScalarFunctionInfo info; Identifier name; FunctionProperties properties; }; -class CV2ConnectionScalarFunction : public CV2ScalarFunction { -public: - explicit CV2ConnectionScalarFunction(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(ScalarFunction function) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - CreateScalarFunctionInfo sf_info(std::move(function)); - sf_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateFunction(context, sf_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionScalarFunction : public CV2ScalarFunction { -public: - explicit CV2ExtensionScalarFunction(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(ScalarFunction function) override { - loader.RegisterFunction(std::move(function)); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_scalar_function_handle func) -> CV2ScalarFunction * { return reinterpret_cast(func); } @@ -337,29 +312,15 @@ static auto Convert(CV2ScalarFunction *func) -> duckdb_v2_scalar_function_handle using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_scalar_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_scalar_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto function = duckdb::make_uniq(conn); - *out_function = Convert(function.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_scalar_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_scalar_function_handle *out_function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); - DUCKDB_CHECK_ARG(out_function); - *out_function = nullptr; +DUCKDB_V2_ERROR duckdb_v2_scalar_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_scalar_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(function); + *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto function = duckdb::make_uniq(loader); - *out_function = Convert(function.release()); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *function = Convert(result.release()); }); } @@ -370,6 +331,14 @@ DUCKDB_V2_ERROR duckdb_v2_scalar_function_set_name(duckdb_v2_scalar_function_han return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); } +DUCKDB_V2_ERROR duckdb_v2_scalar_function_get_docs(duckdb_v2_scalar_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(docs); + return WithErrorHandler(err, [&]() { *docs = Convert(&Convert(function)->docs); }); +} + DUCKDB_V2_ERROR duckdb_v2_scalar_function_get_signature(duckdb_v2_scalar_function_handle function, duckdb_v2_function_signature_handle *sig, duckdb_v2_error_info_handle *err) { @@ -548,10 +517,37 @@ DUCKDB_V2_ERROR duckdb_v2_scalar_function_exec_get_result(duckdb_v2_scalar_funct }); } -DUCKDB_V2_ERROR duckdb_v2_scalar_function_register(duckdb_v2_scalar_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_scalar_function(duckdb_v2_connection_handle conn, + duckdb_v2_scalar_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto &self = *Convert(function); + auto info = duckdb::CreateScalarFunctionInfo(self.Build(*context.db)); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + catalog.CreateFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_scalar_function(duckdb_v2_extension_handle extension, + duckdb_v2_scalar_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + duckdb::CreateScalarFunctionInfo info(self.Build(loader.GetDatabaseInstance())); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + loader.RegisterFunction(std::move(info)); + }); } DUCKDB_V2_ERROR duckdb_v2_scalar_function_destroy(duckdb_v2_scalar_function_handle *function) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_func_table.cpp b/src/duckdb/src/main/capi/v2/capi_v2_func_table.cpp index fae511ecb..31deaee5f 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_func_table.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_func_table.cpp @@ -374,7 +374,8 @@ static auto CV2TableGetBindInfo(TableFunctionGetBindInfoInput &input) -> BindInf CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.get_bind_info_cb(Convert(&args), Convert(&input.context), &err_ptr); + CV2CallbackContext callback_context(input.context); + info.get_bind_info_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -388,13 +389,15 @@ static auto CV2TableBind(ClientContext &context, TableFunctionBindInput &input, const auto &info = input.info->Cast(); // the binder places every argument in its signature slot, defaults included, so the call reads them as-is - CV2ConstantBindInfo bind_info(input.table_function, info.user_data ? info.user_data->GetData() : nullptr, - input.inputs, input.named_parameters); + CV2ConstantBindInfo bind_info(input.table_function, input.table_function.GetSignature(), + info.user_data ? info.user_data->GetData() : nullptr, input.inputs, + input.named_parameters); CV2TableBindInfo args = {}; CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.bind_cb(Convert(&bind_info), Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.bind_cb(Convert(&bind_info), Convert(&args), Convert(&callback_context), &err_ptr); // Take ownership of whatever the callback set before reporting an error, so it is destroyed either way. auto result = make_uniq(); @@ -460,7 +463,8 @@ static auto CV2TableInitGlobal(ClientContext &context, TableFunctionInitInput &i CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.init_global_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.init_global_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (args.out_global_state.ptr) { result->handle = @@ -499,7 +503,8 @@ static auto CV2TableInitLocal(ExecutionContext &context, TableFunctionInitInput CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.init_local_cb(Convert(&args), Convert(&context.client), &err_ptr); + CV2CallbackContext callback_context(context.client); + info.init_local_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (args.out_local_state.ptr) { result->handle = @@ -530,7 +535,8 @@ static auto CV2TableClaimNextBatch(ClientContext &context, TableFunctionInput &i CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.claim_batch_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.claim_batch_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -600,7 +606,8 @@ static auto CV2TableExecBatch(ClientContext &context, TableFunctionInput &input, CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.exec_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.exec_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -649,7 +656,8 @@ static auto CV2TableProgress(ClientContext &context, const FunctionData *bind_da CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.progress_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.progress_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -678,7 +686,8 @@ static auto CV2TableFilterPushdown(ClientContext &context, LogicalGet &get, Func CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.filter_pushdown_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.filter_pushdown_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -736,7 +745,8 @@ static auto CV2TableGetPartitionData(ClientContext &context, TableFunctionGetPar CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.partition_data_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.partition_data_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -759,7 +769,8 @@ static auto CV2TableGetPartitionInfo(ClientContext &context, TableFunctionPartit CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - info.partitioning_cb(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + info.partitioning_cb(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -770,7 +781,12 @@ static auto CV2TableGetPartitionInfo(ClientContext &context, TableFunctionPartit class CV2TableFunction { public: - void Register() { + explicit CV2TableFunction(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the table function to register on `target`. + TableFunction Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "table function"); if (name.empty()) { throw InvalidInputException("Function name cannot be empty."); } @@ -818,52 +834,18 @@ class CV2TableFunction { info.name = name; function.function_info = make_shared_ptr(std::move(info)); - // Call the implementation to register - RegisterToCatalog(std::move(function)); + return function; } - virtual ~CV2TableFunction() = default; - virtual void RegisterToCatalog(TableFunction function) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; FunctionSignature signature; + CV2FunctionDocs docs; CV2TableFunctionInfo info; Identifier name; }; -class CV2ConnectionTableFunction : public CV2TableFunction { -public: - explicit CV2ConnectionTableFunction(Connection &connection) : connection(connection) { - } - - void RegisterToCatalog(TableFunction function) override { - auto &context = *connection.context; - - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - CreateTableFunctionInfo tf_info(std::move(function)); - tf_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateTableFunction(context, tf_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionTableFunction : public CV2TableFunction { -public: - explicit CV2ExtensionTableFunction(ExtensionLoader &loader) : loader(loader) { - } - - void RegisterToCatalog(TableFunction function) override { - loader.RegisterFunction(std::move(function)); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_table_function_handle func) -> CV2TableFunction * { return reinterpret_cast(func); } @@ -874,9 +856,23 @@ static auto Convert(CV2TableFunction *func) -> duckdb_v2_table_function_handle { //---------------------------------------------------------------------------------------------------------------------- // Multi-file functions //---------------------------------------------------------------------------------------------------------------------- +//! The overloads of a table function catalog entry, copied so they outlive the transaction that read them. +static auto CopyTableFunctions(optional_ptr entry) -> unique_ptr { + if (!entry) { + return nullptr; + } + return make_uniq(entry->Cast().functions); +} + class CV2MultiFileFunction { public: - void Register() { + explicit CV2MultiFileFunction(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the function set to register on `target`, wrapping the overloads of + //! the single-file function registered there. + TableFunctionSet Build(DatabaseInstance &target, unique_ptr single_file_overloads) { + CheckRegistrationTarget(db, target, "multi-file function"); if (name.empty()) { throw InvalidInputException("Function name cannot be empty."); } @@ -888,7 +884,7 @@ class CV2MultiFileFunction { if (!file_extension.empty()) { settings.glob_input = FileGlobInput(FileGlobOptions::FALLBACK_GLOB, file_extension); } - auto single_file = GetSingleFileFunction(); + auto single_file = SelectSingleFileFunction(std::move(single_file_overloads)); if (single_file.function == CV2TableExec && single_file.function_info) { // the multi-file scan claims the batches of a C API function that scans one batch at a time if (single_file.function_info->Cast().claim_batch_cb) { @@ -896,23 +892,17 @@ class CV2MultiFileFunction { } } auto function = TableFunctionMultiFileWrapper::CreateFunctionSet(std::move(single_file), name, settings); - RegisterToCatalog(std::move(function)); + return function; } - virtual ~CV2MultiFileFunction() = default; - -protected: - //! Looks up the single-file function in the catalog, and selects the overload to wrap from it - virtual TableFunction GetSingleFileFunction() = 0; - virtual void RegisterToCatalog(TableFunctionSet function) = 0; - +private: //! The overload of the single-file function that takes the path of the file to read - TableFunction SelectSingleFileFunction(optional_ptr entry) const { - if (!entry) { + TableFunction SelectSingleFileFunction(unique_ptr overloads) const { + if (!overloads) { throw InvalidInputException("Table function \"%s\" to read single files with does not exist.", single_file_function); } - for (auto &function : entry->Cast().functions.functions) { + for (auto &function : overloads->functions) { auto &signature = function->GetSignature(); if (signature.GetRequiredParameterCount() != 1 || !signature.GetParameter(0).AcceptsPosition()) { continue; @@ -929,62 +919,14 @@ class CV2MultiFileFunction { } public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; Identifier name; Identifier single_file_function; string reader_type; string file_extension; }; -class CV2ConnectionMultiFileFunction : public CV2MultiFileFunction { -public: - explicit CV2ConnectionMultiFileFunction(Connection &connection) : connection(connection) { - } - -protected: - TableFunction GetSingleFileFunction() override { - auto &context = *connection.context; - TableFunction result; - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - auto entry = catalog.GetEntry(context, CatalogType::TABLE_FUNCTION_ENTRY, Identifier::DefaultSchema(), - single_file_function, OnEntryNotFound::RETURN_NULL); - result = SelectSingleFileFunction(entry); - }); - return result; - } - - void RegisterToCatalog(TableFunctionSet function) override { - auto &context = *connection.context; - context.RunFunctionInTransaction([&]() { - auto &catalog = Catalog::GetSystemCatalog(context); - CreateTableFunctionInfo tf_info(std::move(function)); - tf_info.on_conflict = OnCreateConflict::ALTER_ON_CONFLICT; - catalog.CreateTableFunction(context, tf_info); - }); - } - -private: - Connection &connection; -}; - -class CV2ExtensionMultiFileFunction : public CV2MultiFileFunction { -public: - explicit CV2ExtensionMultiFileFunction(ExtensionLoader &loader) : loader(loader) { - } - -protected: - TableFunction GetSingleFileFunction() override { - return SelectSingleFileFunction(loader.TryGetTableFunction(single_file_function)); - } - - void RegisterToCatalog(TableFunctionSet function) override { - loader.RegisterFunction(std::move(function)); - } - -private: - ExtensionLoader &loader; -}; - static auto Convert(duckdb_v2_multi_file_function_handle func) -> CV2MultiFileFunction * { return reinterpret_cast(func); } @@ -1000,28 +942,14 @@ static auto Convert(CV2MultiFileFunction *func) -> duckdb_v2_multi_file_function using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_table_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(function); - *function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto result = duckdb::make_uniq(conn); - *function = Convert(result.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_table_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_table_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); +DUCKDB_V2_ERROR duckdb_v2_table_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_table_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(function); *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto result = duckdb::make_uniq(loader); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); *function = Convert(result.release()); }); } @@ -1033,6 +961,14 @@ DUCKDB_V2_ERROR duckdb_v2_table_function_set_name(duckdb_v2_table_function_handl return WithErrorHandler(err, [&]() { Convert(function)->name = duckdb::Identifier(ConvertIdentifierName(name)); }); } +DUCKDB_V2_ERROR duckdb_v2_table_function_get_docs(duckdb_v2_table_function_handle function, + duckdb_v2_function_docs_handle *docs, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(function); + DUCKDB_CHECK_ARG(docs); + return WithErrorHandler(err, [&]() { *docs = Convert(&Convert(function)->docs); }); +} + DUCKDB_V2_ERROR duckdb_v2_table_function_get_signature(duckdb_v2_table_function_handle function, duckdb_v2_function_signature_handle *sig, duckdb_v2_error_info_handle *err) { @@ -1594,10 +1530,38 @@ duckdb_v2_table_function_partitioning_set_partition_info(duckdb_v2_table_functio [&]() { Convert(info)->out_partition_info = CV2ConvertPartitionInfo(partition_info); }); } -DUCKDB_V2_ERROR duckdb_v2_table_function_register(duckdb_v2_table_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_table_function(duckdb_v2_connection_handle conn, + duckdb_v2_table_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto &self = *Convert(function); + auto info = duckdb::CreateTableFunctionInfo(self.Build(*context.db)); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + // CreateFunction, unlike CreateTableFunction, adds the overloads to an existing function. + catalog.CreateFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_table_function(duckdb_v2_extension_handle extension, + duckdb_v2_table_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + duckdb::CreateTableFunctionInfo info(self.Build(loader.GetDatabaseInstance())); + self.docs.AddTo(self.signature, info); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + loader.RegisterFunction(std::move(info)); + }); } DUCKDB_V2_ERROR duckdb_v2_table_function_destroy(duckdb_v2_table_function_handle *function) { @@ -1750,28 +1714,14 @@ duckdb_v2_table_function_get_bind_info_set_option(duckdb_v2_table_function_get_b // Multi-file functions //---------------------------------------------------------------------------------------------------------------------- -DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_multi_file_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(function); - *function = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto result = duckdb::make_uniq(conn); - *function = Convert(result.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_multi_file_function_handle *function, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); +DUCKDB_V2_ERROR duckdb_v2_multi_file_function_create(duckdb_v2_factory_handle factory, + duckdb_v2_multi_file_function_handle *function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(function); *function = nullptr; return WithErrorHandler(err, [&]() { - auto &loader = GetExtensionLoader(extension); - auto result = duckdb::make_uniq(loader); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); *function = Convert(result.release()); }); } @@ -1815,10 +1765,38 @@ DUCKDB_V2_ERROR duckdb_v2_multi_file_function_set_file_extension(duckdb_v2_multi }); } -DUCKDB_V2_ERROR duckdb_v2_multi_file_function_register(duckdb_v2_multi_file_function_handle function, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_multi_file_function(duckdb_v2_connection_handle conn, + duckdb_v2_multi_file_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); DUCKDB_CHECK_ARG(function); - return WithErrorHandler(err, [&]() { Convert(function)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &context = *Convert(conn)->context; + context.RunFunctionInTransaction([&]() { + auto &catalog = duckdb::Catalog::GetSystemCatalog(context); + auto single_file = catalog.GetEntry(context, duckdb::CatalogType::TABLE_FUNCTION_ENTRY, + duckdb::Identifier::DefaultSchema(), self.single_file_function, + duckdb::OnEntryNotFound::RETURN_NULL); + duckdb::CreateTableFunctionInfo info(self.Build(*context.db, CopyTableFunctions(single_file))); + info.on_conflict = duckdb::OnCreateConflict::ALTER_ON_CONFLICT; + // CreateFunction, unlike CreateTableFunction, adds the overloads to an existing function. + catalog.CreateFunction(context, info); + }); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_multi_file_function(duckdb_v2_extension_handle extension, + duckdb_v2_multi_file_function_handle function, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(function); + return WithErrorHandler(err, [&]() { + auto &self = *Convert(function); + auto &loader = GetExtensionLoader(extension); + auto single_file = CopyTableFunctions(loader.TryGetTableFunction(self.single_file_function)); + loader.RegisterFunction(self.Build(loader.GetDatabaseInstance(), std::move(single_file))); + }); } DUCKDB_V2_ERROR duckdb_v2_multi_file_function_destroy(duckdb_v2_multi_file_function_handle *function) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_instance.cpp b/src/duckdb/src/main/capi/v2/capi_v2_instance.cpp index 6738d46c7..8225a6e9f 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_instance.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_instance.cpp @@ -12,30 +12,26 @@ namespace duckdb { namespace capiv2 { -CV2Instance::CV2Instance(CV2Environment &env) : env(env), config(make_uniq()) { +// Routed through the env's DBInstanceCache so every instance shares its path manager: opening one file twice is +// detected across instances, while no instance is memoized. +CV2Instance::CV2Instance(CV2Environment &env, DBConfig &startup_config) + : database(env.cache->CreateEmptyInstance(startup_config)), env(env), factory(*this), config_handle(*this), + file_system(FileSystem::GetFileSystem(*database->instance), QueryContext()) { } -void CV2Instance::Start() { - if (IsStarted()) { - return; - } - // Routed through the env's DBInstanceCache so every instance shares its path manager: opening one file twice is - // detected across instances, while no instance is memoized. - database = env.cache->CreateEmptyInstance(*config); - config.reset(); - staged_settings.clear(); - internal_connection = make_uniq(*database); +DatabaseInstance &CV2InstanceFactory::GetDatabase() { + return *instance.GetDatabase().instance; } -DuckDB &CV2Instance::GetDatabase() { - Start(); - return *database; +void CV2InstanceFactory::WithTransaction(const std::function &fn) { + throw InvalidInputException("this requires a catalog: use the factory of a connection or a context"); } -//! Runs `action` inside a transaction on the internal connection, the way the ATTACH / DETACH operators run inside +//! Runs `action` inside a transaction on a short-lived connection, the way the ATTACH / DETACH operators run inside //! their statement's transaction. template -static void WithTransaction(Connection &connection, T action) { +static void WithTransaction(DuckDB &database, T action) { + auto connection = Connection::CreateInternal(*database.instance); connection.BeginTransaction(); try { action(*connection.context); @@ -51,8 +47,7 @@ void CV2Instance::Attach(const string &path, const Identifier &name, if (attach_options && &attach_options->instance != this) { throw InvalidInputException("the attach options were created from a different instance handle"); } - Start(); - WithTransaction(*internal_connection, [&](ClientContext &context) { + WithTransaction(*database, [&](ClientContext &context) { // Mirrors PhysicalAttach for `ATTACH 'path' AS name (options)`. auto &instance = *database->instance; AttachInfo info; @@ -82,114 +77,26 @@ void CV2Instance::Attach(const string &path, const Identifier &name, }); } -//! Finds the database attached under `path` as a name, else the one attached from it as a path, disambiguated by -//! the name instance_attach derives from the path. Throws when nothing matches. -static shared_ptr FindAttachedDatabase(DatabaseInstance &instance, const string &path) { - auto &db_manager = DatabaseManager::Get(instance); - if (auto by_name = db_manager.GetDatabase(Identifier(path))) { - if (!by_name->IsSystem() && !by_name->IsTemporary()) { - return by_name; - } - } - auto &fs = FileSystem::GetFileSystem(instance); - string stripped = path; - string db_type; - DBPathAndType::ExtractExtensionPrefix(stripped, db_type); - const bool in_memory = DBConfig::IsInMemoryDatabase(stripped.c_str()); - // File-based DuckDB databases are attached under their canonical path; other attaches keep the path verbatim. - string canonical = stripped; - if (!in_memory && db_type.empty() && !FileSystem::IsRemoteFile(stripped)) { - try { - canonical = fs.CanonicalizePath(stripped); - } catch (...) { // NOLINT(bugprone-empty-catch): an uncanonicalizable path just falls back to verbatim matching - } - } - const auto derived_name = AttachedDatabase::ExtractDatabaseName(stripped, fs); - - shared_ptr match; - idx_t match_count = 0; - for (auto &db : db_manager.GetDatabases()) { - if (db->IsSystem() || db->IsTemporary()) { - continue; - } - auto &catalog = db->GetCatalog(); - bool matches; - if (in_memory) { - matches = catalog.InMemory(); - } else { - const auto db_path = catalog.GetDBPath(); - matches = db_path == canonical || db_path == stripped; - } - if (!matches) { - continue; - } - match_count++; - if (!match || db->GetName() == derived_name) { - match = db; - } - } - if (!match) { - throw InvalidInputException("no database is attached from or under '%s'", path); - } - if (match_count > 1 && match->GetName() != derived_name) { - throw InvalidInputException("several databases are attached from '%s'; refer to one by name in SQL", path); +//! Throws unless a user database is attached under `name`. +static void CheckAttached(DatabaseInstance &instance, const Identifier &name) { + auto attached = DatabaseManager::Get(instance).GetDatabase(name); + if (!attached || attached->IsSystem()) { + throw InvalidInputException("no database is attached under the name '%s'", name.GetIdentifierName()); } - return match; } -void CV2Instance::Detach(const string &path) { - if (!IsStarted()) { - throw InvalidInputException("no database is attached from or under '%s'", path); - } +void CV2Instance::Detach(const Identifier &name) { auto &instance = *database->instance; - auto attached = FindAttachedDatabase(instance, path); - WithTransaction(*internal_connection, [&](ClientContext &context) { - DatabaseManager::Get(instance).DetachDatabase(context, attached->GetName(), OnEntryNotFound::THROW_EXCEPTION, - true); + CheckAttached(instance, name); + WithTransaction(*database, [&](ClientContext &context) { + DatabaseManager::Get(instance).DetachDatabase(context, name, OnEntryNotFound::THROW_EXCEPTION, true); }); } -void CV2Instance::SetDefault(const string &path) { - if (!IsStarted()) { - throw InvalidInputException("no database is attached from or under '%s'", path); - } +void CV2Instance::SetDefault(const Identifier &name) { auto &instance = *database->instance; - auto attached = FindAttachedDatabase(instance, path); - DatabaseManager::Get(instance).SetDefaultDatabase(attached->GetName()); -} - -void CV2Instance::SetOption(const Identifier &name, const string &setting) { - if (!IsStarted()) { - // Staged for startup: the only route to options that cannot change once the instance runs. - config->SetOptionByName(name, Value(setting)); - auto option = DBConfig::GetOptionByName(name); - staged_settings[option ? Identifier(option->name) : name] = setting; - return; - } - // Force GLOBAL scope: the internal context has no LOCAL settings of its own, and instance-scoped settings only - // make sense as GLOBAL anyway. - PhysicalSet::SetVariable(*internal_connection->context, name, SetScope::GLOBAL, Value(setting)); -} - -unique_ptr CV2Instance::GetOption(std::string_view name) { - if (!IsStarted()) { - return CV2Option::FromName(CV2OptionSource(*config, staged_settings), name); - } - return CV2Option::FromName(CV2OptionSource(*internal_connection->context), name); -} - -idx_t CV2Instance::GetOptionCount() { - if (!IsStarted()) { - return CV2Option::Count(CV2OptionSource(*config, staged_settings)); - } - return CV2Option::Count(CV2OptionSource(*internal_connection->context)); -} - -unique_ptr CV2Instance::GetOptionByIndex(idx_t index) { - if (!IsStarted()) { - return CV2Option::FromIndex(CV2OptionSource(*config, staged_settings), index); - } - return CV2Option::FromIndex(CV2OptionSource(*internal_connection->context), index); + CheckAttached(instance, name); + DatabaseManager::Get(instance).SetDefaultDatabase(name); } } // namespace capiv2 @@ -199,12 +106,34 @@ using namespace duckdb::capiv2; DUCKDB_V2_ERROR duckdb_v2_instance_create(duckdb_v2_environment_handle env, duckdb_v2_instance_handle *out_instance, duckdb_v2_error_info_handle *err) { + return duckdb_v2_instance_create_with_options(env, nullptr, nullptr, 0, out_instance, err); +} + +DUCKDB_V2_ERROR duckdb_v2_instance_create_with_options(duckdb_v2_environment_handle env, + const duckdb_v2_str *option_names, + const duckdb_v2_str *option_values, idx_t option_count, + duckdb_v2_instance_handle *out_instance, + duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(env); DUCKDB_CHECK_ARG(out_instance); *out_instance = nullptr; + if (option_count > 0 && !option_names) { + return NullArgumentError(err, __func__, "option_names"); + } + if (option_count > 0 && !option_values) { + return NullArgumentError(err, __func__, "option_values"); + } return WithErrorHandler(err, [&]() { + duckdb::DBConfig startup_config; + for (idx_t i = 0; i < option_count; i++) { + if (IsNullArgument(&option_names[i]) || IsNullArgument(&option_values[i])) { + throw duckdb::InvalidInputException("malformed startup option at index %llu", i); + } + startup_config.SetOptionByName(duckdb::Identifier(duckdb::string(Convert(&option_names[i]))), + duckdb::Value(duckdb::string(Convert(&option_values[i])))); + } auto *env_wrapper = Convert(env); - auto wrapper = duckdb::make_uniq(*env_wrapper); + auto wrapper = duckdb::make_uniq(*env_wrapper, startup_config); env_wrapper->instance_count.fetch_add(1, std::memory_order_release); *out_instance = Convert(wrapper.release()); }); @@ -225,8 +154,8 @@ DUCKDB_V2_ERROR duckdb_v2_instance_destroy(duckdb_v2_instance_handle *instance) }); } -DUCKDB_V2_ERROR duckdb_v2_instance_attach(duckdb_v2_instance_handle instance, const duckdb_v2_str *path, - const duckdb_v2_identifier_t *name, duckdb_v2_attach_options_handle options, +DUCKDB_V2_ERROR duckdb_v2_instance_attach(duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, + const duckdb_v2_str *path, duckdb_v2_attach_options_handle options, bool make_default, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(instance); DUCKDB_CHECK_ARG(path); @@ -235,7 +164,6 @@ DUCKDB_V2_ERROR duckdb_v2_instance_attach(duckdb_v2_instance_handle instance, co } return WithErrorHandler(err, [&]() { auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); duckdb::Identifier attach_name = name ? duckdb::Identifier(ConvertIdentifierName(name)) : duckdb::Identifier(); wrapper.Attach(duckdb::string(Convert(path)), attach_name, Convert(options), make_default); }); @@ -277,76 +205,23 @@ DUCKDB_V2_ERROR duckdb_v2_attach_options_destroy(duckdb_v2_attach_options_handle }); } -DUCKDB_V2_ERROR duckdb_v2_instance_detach(duckdb_v2_instance_handle instance, const duckdb_v2_str *path, +DUCKDB_V2_ERROR duckdb_v2_instance_detach(duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(instance); - DUCKDB_CHECK_ARG(path); - return WithErrorHandler(err, [&]() { - auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - wrapper.Detach(duckdb::string(Convert(path))); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_instance_set_default(duckdb_v2_instance_handle instance, const duckdb_v2_str *path, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(instance); - DUCKDB_CHECK_ARG(path); - return WithErrorHandler(err, [&]() { - auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - wrapper.SetDefault(duckdb::string(Convert(path))); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_instance_set_option(duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, - const duckdb_v2_str *setting, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(instance); DUCKDB_CHECK_ARG(name); - DUCKDB_CHECK_ARG(setting); return WithErrorHandler(err, [&]() { auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - wrapper.SetOption(duckdb::Identifier(ConvertIdentifierName(name)), duckdb::string(Convert(setting))); + wrapper.Detach(duckdb::Identifier(ConvertIdentifierName(name))); }); } -DUCKDB_V2_ERROR duckdb_v2_instance_get_option_by_name(duckdb_v2_instance_handle instance, - const duckdb_v2_identifier_t *name, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_instance_set_default(duckdb_v2_instance_handle instance, const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(instance); DUCKDB_CHECK_ARG(name); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; - return WithErrorHandler(err, [&]() { - auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - *out_option = Convert(wrapper.GetOption(ConvertIdentifierName(name)).release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_instance_get_option_count(duckdb_v2_instance_handle instance, idx_t *out_count, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(instance); - DUCKDB_CHECK_ARG(out_count); - return WithErrorHandler(err, [&]() { - auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - *out_count = wrapper.GetOptionCount(); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_instance_get_option_by_index(duckdb_v2_instance_handle instance, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(instance); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; return WithErrorHandler(err, [&]() { auto &wrapper = *Convert(instance); - duckdb::lock_guard guard(wrapper.lock); - *out_option = Convert(wrapper.GetOptionByIndex(index).release()); + wrapper.SetDefault(duckdb::Identifier(ConvertIdentifierName(name))); }); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_log_sink.cpp b/src/duckdb/src/main/capi/v2/capi_v2_log_sink.cpp new file mode 100644 index 000000000..760324d21 --- /dev/null +++ b/src/duckdb/src/main/capi/v2/capi_v2_log_sink.cpp @@ -0,0 +1,243 @@ +#include "duckdb/main/capi_v2/capi_v2_internal.hpp" + +#include "duckdb/logging/log_manager.hpp" +#include "duckdb/logging/log_storage.hpp" + +namespace duckdb::capiv2 { + +//! The context of a log entry, beyond what the write callback receives directly +class CV2LogSinkWriteInfo { +public: + explicit CV2LogSinkWriteInfo(const LoggingContext &context) : context(context) { + } + + const LoggingContext &context; +}; + +static auto Convert(duckdb_v2_log_sink_write_info_handle info) -> CV2LogSinkWriteInfo * { + return reinterpret_cast(info); +} +static auto Convert(CV2LogSinkWriteInfo *info) -> duckdb_v2_log_sink_write_info_handle { + return reinterpret_cast(info); +} + +//! Carries nothing yet +class CV2LogSinkFlushInfo {}; + +static auto Convert(CV2LogSinkFlushInfo *info) -> duckdb_v2_log_sink_flush_info_handle { + return reinterpret_cast(info); +} + +//! A log sink that hands every entry to the caller's write callback. Errors the callbacks report are dropped: +//! logging happens inside arbitrary operations, which a sink must not fail. +class CV2CallbackLogSink final : public LogStorage { +public: + CV2CallbackLogSink(string name, duckdb_v2_log_sink_write_callback_fn write_cb, + duckdb_v2_log_sink_flush_callback_fn flush_cb, shared_ptr user_data) + : name(std::move(name)), write_cb(write_cb), flush_cb(flush_cb), user_data(std::move(user_data)) { + } + + const string GetStorageName() override { + return name; + } + + void WriteLogEntry(timestamp_t timestamp, LogLevel level, const string &log_type, const string &log_message, + const RegisteredLoggingContext &context) override { + CV2LogSinkWriteInfo info(context.context); + duckdb_v2_str type {log_type.data(), log_type.size()}; + duckdb_v2_str message {log_message.data(), log_message.size()}; + CV2ErrorInfo err = {}; + auto err_ptr = Convert(&err); + // the values of the level and scope enums match duckdb::LogLevel and duckdb::LogContextScope + write_cb(Convert(&info), GetUserData(), timestamp.value, static_cast(level), + static_cast(context.context.scope), &type, &message, &err_ptr); + } + + void WriteLogEntries(DataChunk &chunk, const RegisteredLoggingContext &context) override { + } + + void FlushAll() override { + if (!flush_cb) { + return; + } + CV2LogSinkFlushInfo info; + CV2ErrorInfo err = {}; + auto err_ptr = Convert(&err); + flush_cb(Convert(&info), GetUserData(), &err_ptr); + } + + void Flush(LoggingTargetTable table) override { + } + + bool IsEnabled(LoggingTargetTable table) override { + return true; + } + +private: + void *GetUserData() const { + return user_data ? user_data->GetData() : nullptr; + } + + const string name; + duckdb_v2_log_sink_write_callback_fn write_cb; + duckdb_v2_log_sink_flush_callback_fn flush_cb; + shared_ptr user_data; +}; + +class CV2LogSink { +public: + explicit CV2LogSink(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and registers the sink on `target`. + void Register(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "log sink"); + if (name.empty()) { + throw InvalidInputException("Log sink name cannot be empty."); + } + if (!write_cb) { + throw InvalidInputException("A write callback must be set for the log sink."); + } + // the log manager looks registered sinks up by their lowercased name + auto sink_name = StringUtil::Lower(name); + if (sink_name == LogConfig::IN_MEMORY_STORAGE_NAME || sink_name == LogConfig::STDOUT_STORAGE_NAME || + sink_name == LogConfig::FILE_STORAGE_NAME) { + throw InvalidInputException("\"%s\" is the name of a log sink DuckDB provides.", name); + } + shared_ptr sink = make_shared_ptr(sink_name, write_cb, flush_cb, user_data); + if (!target.GetLogManager().RegisterLogStorage(sink_name, sink)) { + throw InvalidInputException("A log sink named \"%s\" is already registered.", name); + } + } + +public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; + string name; + duckdb_v2_log_sink_write_callback_fn write_cb = nullptr; + duckdb_v2_log_sink_flush_callback_fn flush_cb = nullptr; + shared_ptr user_data = nullptr; +}; + +static auto Convert(duckdb_v2_log_sink_handle sink) -> CV2LogSink * { + return reinterpret_cast(sink); +} +static auto Convert(CV2LogSink *sink) -> duckdb_v2_log_sink_handle { + return reinterpret_cast(sink); +} + +static void ReadId(const optional_idx &source, idx_t *id, bool *present) { + *present = source.IsValid(); + if (source.IsValid()) { + *id = source.GetIndex(); + } +} + +} // namespace duckdb::capiv2 + +//---------------------------------------------------------------------------------------------------------------------- +// Public Functions +//---------------------------------------------------------------------------------------------------------------------- + +using namespace duckdb::capiv2; + +DUCKDB_V2_ERROR duckdb_v2_log_sink_create(duckdb_v2_factory_handle factory, duckdb_v2_log_sink_handle *sink, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(sink); + *sink = nullptr; + return WithErrorHandler(err, [&]() { + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *sink = Convert(result.release()); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_set_name(duckdb_v2_log_sink_handle sink, const duckdb_v2_identifier_t *name, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(sink); + DUCKDB_CHECK_ARG(name); + return WithErrorHandler(err, [&]() { Convert(sink)->name = duckdb::string(ConvertIdentifierName(name)); }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_set_user_data(duckdb_v2_log_sink_handle sink, duckdb_v2_opaque *data, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(sink); + DUCKDB_CHECK_ARG(data); + return WithErrorHandler(err, [&]() { + Convert(sink)->user_data = duckdb::make_shared_ptr(data->ptr, data->destroy, data->equals); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_set_write_callback(duckdb_v2_log_sink_handle sink, + duckdb_v2_log_sink_write_callback_fn callback, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(sink); + return WithErrorHandler(err, [&]() { Convert(sink)->write_cb = callback; }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_set_flush_callback(duckdb_v2_log_sink_handle sink, + duckdb_v2_log_sink_flush_callback_fn callback, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(sink); + return WithErrorHandler(err, [&]() { Convert(sink)->flush_cb = callback; }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_connection_id(duckdb_v2_log_sink_write_info_handle info, idx_t *id, + bool *present, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(id); + DUCKDB_CHECK_ARG(present); + return WithErrorHandler(err, [&]() { ReadId(Convert(info)->context.connection_id, id, present); }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_transaction_id(duckdb_v2_log_sink_write_info_handle info, idx_t *id, + bool *present, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(id); + DUCKDB_CHECK_ARG(present); + return WithErrorHandler(err, [&]() { ReadId(Convert(info)->context.transaction_id, id, present); }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_query_id(duckdb_v2_log_sink_write_info_handle info, idx_t *id, + bool *present, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(id); + DUCKDB_CHECK_ARG(present); + return WithErrorHandler(err, [&]() { ReadId(Convert(info)->context.query_id, id, present); }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_write_info_get_thread_id(duckdb_v2_log_sink_write_info_handle info, idx_t *id, + bool *present, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(info); + DUCKDB_CHECK_ARG(id); + DUCKDB_CHECK_ARG(present); + return WithErrorHandler(err, [&]() { ReadId(Convert(info)->context.thread_id, id, present); }); +} + +DUCKDB_V2_ERROR duckdb_v2_instance_register_log_sink(duckdb_v2_instance_handle instance, duckdb_v2_log_sink_handle sink, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); + DUCKDB_CHECK_ARG(sink); + return WithErrorHandler(err, [&]() { Convert(sink)->Register(*Convert(instance)->GetDatabase().instance); }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_log_sink(duckdb_v2_extension_handle extension, + duckdb_v2_log_sink_handle sink, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(sink); + return WithErrorHandler(err, + [&]() { Convert(sink)->Register(GetExtensionLoader(extension).GetDatabaseInstance()); }); +} + +DUCKDB_V2_ERROR duckdb_v2_log_sink_destroy(duckdb_v2_log_sink_handle *sink) { + return WithErrorHandler(nullptr, [&]() { + if (!sink) { + return; + } + if (*sink) { + delete Convert(*sink); + *sink = nullptr; + } + }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_logging.cpp b/src/duckdb/src/main/capi/v2/capi_v2_logging.cpp index 723116b8d..35287218f 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_logging.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_logging.cpp @@ -21,7 +21,18 @@ static LogLevel ConvertLogLevel(DUCKDB_V2_LOG_LEVEL level) { case DUCKDB_V2_LOG_LEVEL_FATAL: return LogLevel::LOG_FATAL; default: - throw InvalidInputException("unknown log level passed to duckdb_v2_context_log"); + throw InvalidInputException("'%d' is not a log level", static_cast(level)); + } +} + +static void WriteLog(Logger &logger, DUCKDB_V2_LOG_LEVEL level, const duckdb_v2_str *log_type, + const duckdb_v2_str *message) { + const auto log_level = ConvertLogLevel(level); + // ShouldLog/WriteLog take a C string, so the borrowed view has to be materialized. + const string type(Convert(log_type)); + const auto *type_name = type.empty() ? DefaultLogType::NAME : type.c_str(); + if (logger.ShouldLog(type_name, log_level)) { + logger.WriteLog(type_name, log_level, string(Convert(message))); } } @@ -38,15 +49,23 @@ DUCKDB_V2_ERROR duckdb_v2_context_log(duckdb_v2_context_handle ctx, DUCKDB_V2_LO const duckdb_v2_str *log_type, const duckdb_v2_str *message, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(ctx); + return WithErrorHandler(err, + [&]() { WriteLog(duckdb::Logger::Get(Convert(ctx)->context), level, log_type, message); }); +} + +DUCKDB_V2_ERROR duckdb_v2_instance_log(duckdb_v2_instance_handle instance, DUCKDB_V2_LOG_LEVEL level, + const duckdb_v2_str *log_type, const duckdb_v2_str *message, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); return WithErrorHandler(err, [&]() { - const auto log_level = ConvertLogLevel(level); - // ShouldLog/WriteLog take a C string, so the borrowed view has to be materialized. - const duckdb::string type(Convert(log_type)); - const auto *type_name = type.empty() ? duckdb::DefaultLogType::NAME : type.c_str(); - - auto &logger = duckdb::Logger::Get(*Convert(ctx)); - if (logger.ShouldLog(type_name, log_level)) { - logger.WriteLog(type_name, log_level, duckdb::string(Convert(message))); - } + WriteLog(duckdb::Logger::Get(*Convert(instance)->GetDatabase().instance), level, log_type, message); }); } + +DUCKDB_V2_ERROR duckdb_v2_connection_log(duckdb_v2_connection_handle conn, DUCKDB_V2_LOG_LEVEL level, + const duckdb_v2_str *log_type, const duckdb_v2_str *message, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); + return WithErrorHandler( + err, [&]() { WriteLog(duckdb::Logger::Get(*Convert(conn)->context), level, log_type, message); }); +} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_logical_type.cpp b/src/duckdb/src/main/capi/v2/capi_v2_logical_type.cpp index 23dbb0500..78def864a 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_logical_type.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_logical_type.cpp @@ -1,6 +1,7 @@ #include "duckdb/main/capi_v2/capi_v2_internal.hpp" #include "duckdb/catalog/catalog_entry/type_catalog_entry.hpp" +#include "duckdb/catalog/default/default_types.hpp" #include "duckdb/catalog/catalog_entry_retriever.hpp" #include "duckdb/common/enum_util.hpp" #include "duckdb/common/logical_type_info.hpp" @@ -118,6 +119,35 @@ LogicalType BindTypeByNameV2(ClientContext &context, const QualifiedName &name, return type_entry.constructors.Bind(context, type_entry.user_type, args); } +// Binds a built-in type by name, without a client: constructors that consult the catalog (collations, coordinate +// systems) skip that check or refuse. +LogicalType BindBuiltinTypeV2(const QualifiedName &name, const vector &args) { + if (name.Path().size() > 1) { + throw InvalidInputException("a qualified type name requires a catalog: use the factory of a connection or a " + "context"); + } + vector> params; + params.reserve(args.size()); + for (auto &arg : args) { + params.emplace_back(arg.GetName(), arg.GetValue()); + } + auto type = DefaultTypeGenerator::TryDefaultBind(name.Name().GetIdentifierName(), params); + if (type.id() == LogicalTypeId::INVALID) { + throw InvalidInputException("type \"%s\" is not a built-in type", name.Name().GetIdentifierName()); + } + return type; +} + +// Binds a type by name in the factory's scope: the client's catalog, or the built-in types only. +LogicalType BindTypeV2(CV2Factory &factory, const QualifiedName &name, const vector &args) { + if (!factory.TryGetClientContext()) { + return BindBuiltinTypeV2(name, args); + } + LogicalType result; + factory.WithTransaction([&](ClientContext &context) { result = BindTypeByNameV2(context, name, args); }); + return result; +} + // The value-parameter view of a bound type: the exact dual of // logical_type_create. Kinds without retained parameters report 0. idx_t TypeParamCount(const LogicalType &type) { @@ -181,6 +211,27 @@ Value TypeParamValue(const LogicalType &type, idx_t index, duckdb_v2_identifier_ } } // anonymous namespace + +auto ResolveCommonType(CV2Factory &factory, const vector &types) -> LogicalType { + D_ASSERT(!types.empty()); + auto context = factory.TryGetClientContext(); + auto result = types[0]; + for (idx_t i = 1; i < types.size(); i++) { + auto &type = types[i]; + if (context) { + result = LogicalType::MaxLogicalType(*context, result, type); + continue; + } + LogicalType max_type; + if (!LogicalType::DefaultTryGetMaxLogicalTypeUnchecked(result, type, max_type)) { + throw NotImplementedException("Cannot combine types %s and %s - an explicit cast is required", + result.ToString(), type.ToString()); + } + result = std::move(max_type); + } + return result; +} + } // namespace duckdb::capiv2 // --------------------------------------------------------------------------- @@ -212,7 +263,7 @@ static duckdb::vector CollectTypeArgsV2(const duckdb_v2_id // A parameterless call instantiates the primitive directly; with parameters // the id resolves to its canonical name and binds through the same path as // create_type_from_name. -static void CreateLogicalTypeFromIdV2(duckdb::ClientContext &context, DUCKDB_V2_LOGICAL_TYPE_ID type_id, +static void CreateLogicalTypeFromIdV2(CV2Factory &factory, DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, const char *fn) { @@ -233,76 +284,49 @@ static void CreateLogicalTypeFromIdV2(duckdb::ClientContext &context, DUCKDB_V2_ } // Bind errors propagate. auto args = CollectTypeArgsV2(param_names, param_values, param_count); - auto bound = - BindTypeByNameV2(context, duckdb::QualifiedName(duckdb::Identifier(duckdb::EnumUtil::ToString(id))), args); + auto bound = BindTypeV2(factory, duckdb::QualifiedName(duckdb::Identifier(duckdb::EnumUtil::ToString(id))), args); *out_type = Convert(std::move(bound)); } -DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_id(duckdb_v2_context_handle ctx, DUCKDB_V2_LOGICAL_TYPE_ID type_id, +DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_id(duckdb_v2_factory_handle factory, + DUCKDB_V2_LOGICAL_TYPE_ID type_id, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err) { - static const char *fn = "duckdb_v2_context_create_type_from_id"; - DUCKDB_CHECK_ARG(ctx); + static const char *fn = "duckdb_v2_factory_create_type_from_id"; + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_type); return WithErrorHandler(err, [&]() { - // A context arrives with the lock held and a transaction active. - CreateLogicalTypeFromIdV2(*Convert(ctx), type_id, param_names, param_values, param_count, out_type, fn); + CreateLogicalTypeFromIdV2(*Convert(factory), type_id, param_names, param_values, param_count, out_type, fn); }); } -DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_id(duckdb_v2_connection_handle conn, - DUCKDB_V2_LOGICAL_TYPE_ID type_id, - const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, idx_t param_count, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - static const char *fn = "duckdb_v2_connection_create_type_from_id"; - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_type); - return WithErrorHandler(err, [&]() { - auto &context = *Convert(conn)->context; - context.RunFunctionInTransaction([&]() { - CreateLogicalTypeFromIdV2(context, type_id, param_names, param_values, param_count, out_type, fn); - }); - }); -} - -static void CreateLogicalTypeFromTextV2(duckdb::ClientContext &context, const duckdb_v2_str *text, +static void CreateLogicalTypeFromTextV2(CV2Factory &factory, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type) { *out_type = nullptr; // Parse and bind errors propagate. auto string = duckdb::string(Convert(text)); - auto parsed = duckdb::TransformStringToLogicalType(string, context); + if (!factory.TryGetClientContext()) { + *out_type = Convert(duckdb::TypeManager::Get(factory.GetDatabase()).ParseLogicalType(string)); + return; + } + duckdb::LogicalType parsed; + factory.WithTransaction( + [&](duckdb::ClientContext &context) { parsed = duckdb::TransformStringToLogicalType(string, context); }); *out_type = Convert(std::move(parsed)); } -DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_text(duckdb_v2_context_handle ctx, const duckdb_v2_str *text, +DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_text(duckdb_v2_factory_handle factory, const duckdb_v2_str *text, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_type); - DUCKDB_CHECK_ARG(text); - return WithErrorHandler(err, [&]() { - // A context arrives with the lock held and a transaction active. - CreateLogicalTypeFromTextV2(*Convert(ctx), text, out_type); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_text(duckdb_v2_connection_handle conn, const duckdb_v2_str *text, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_type); DUCKDB_CHECK_ARG(text); - return WithErrorHandler(err, [&]() { - auto &context = *Convert(conn)->context; - context.RunFunctionInTransaction([&]() { CreateLogicalTypeFromTextV2(context, text, out_type); }); - }); + return WithErrorHandler(err, [&]() { CreateLogicalTypeFromTextV2(*Convert(factory), text, out_type); }); } -static void CreateLogicalTypeFromArgsV2(duckdb::ClientContext &context, duckdb_v2_qname_handle name, +static void CreateLogicalTypeFromArgsV2(CV2Factory &factory, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type) { @@ -312,37 +336,20 @@ static void CreateLogicalTypeFromArgsV2(duckdb::ClientContext &context, duckdb_v *out_type = nullptr; auto args = CollectTypeArgsV2(param_names, param_values, param_count); // Bind errors propagate. - auto bound = BindTypeByNameV2(context, *Convert(name), args); + auto bound = BindTypeV2(factory, *Convert(name), args); *out_type = Convert(std::move(bound)); } -DUCKDB_V2_ERROR duckdb_v2_context_create_type_from_name(duckdb_v2_context_handle ctx, duckdb_v2_qname_handle name, +DUCKDB_V2_ERROR duckdb_v2_factory_create_type_from_name(duckdb_v2_factory_handle factory, duckdb_v2_qname_handle name, const duckdb_v2_identifier_t *param_names, const duckdb_v2_value_handle *param_values, idx_t param_count, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_type); DUCKDB_CHECK_ARG(name); return WithErrorHandler(err, [&]() { - // A context arrives with the lock held and a transaction active. - CreateLogicalTypeFromArgsV2(*Convert(ctx), name, param_names, param_values, param_count, out_type); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_connection_create_type_from_name(duckdb_v2_connection_handle conn, - duckdb_v2_qname_handle name, - const duckdb_v2_identifier_t *param_names, - const duckdb_v2_value_handle *param_values, - idx_t param_count, duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_type); - DUCKDB_CHECK_ARG(name); - return WithErrorHandler(err, [&]() { - auto &context = *Convert(conn)->context; - context.RunFunctionInTransaction( - [&]() { CreateLogicalTypeFromArgsV2(context, name, param_names, param_values, param_count, out_type); }); + CreateLogicalTypeFromArgsV2(*Convert(factory), name, param_names, param_values, param_count, out_type); }); } @@ -471,26 +478,41 @@ duckdb::LogicalType AliasOf(duckdb_v2_logical_type_handle base_type, const duckd } // namespace -DUCKDB_V2_ERROR duckdb_v2_context_create_type_with_alias(duckdb_v2_context_handle ctx, +DUCKDB_V2_ERROR duckdb_v2_factory_create_type_with_alias(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle base_type, const duckdb_v2_identifier_t *alias_name, duckdb_v2_logical_type_handle *out_type, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(base_type); DUCKDB_CHECK_ARG(alias_name); DUCKDB_CHECK_ARG(out_type); return WithErrorHandler(err, [&]() { *out_type = Convert(AliasOf(base_type, alias_name, out_type)); }); } -DUCKDB_V2_ERROR duckdb_v2_connection_create_type_with_alias(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle base_type, - const duckdb_v2_identifier_t *alias_name, - duckdb_v2_logical_type_handle *out_type, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(base_type); - DUCKDB_CHECK_ARG(alias_name); +DUCKDB_V2_ERROR duckdb_v2_factory_create_common_type(duckdb_v2_factory_handle factory, + const duckdb_v2_logical_type_handle *types, idx_t type_count, + duckdb_v2_logical_type_handle *out_type, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_type); - return WithErrorHandler(err, [&]() { *out_type = Convert(AliasOf(base_type, alias_name, out_type)); }); + *out_type = nullptr; + return WithErrorHandler(err, [&]() { + if (type_count == 0 || !types) { + throw duckdb::InvalidInputException("cannot resolve the common type of an empty set"); + } + duckdb::vector input_types; + input_types.reserve(type_count); + for (idx_t i = 0; i < type_count; i++) { + if (!types[i]) { + throw duckdb::InvalidInputException("type %llu is null", i); + } + auto type = Convert(types[i]); + if (type->id() == duckdb::LogicalTypeId::ANY) { + throw duckdb::InvalidInputException("cannot resolve the common type of ANY"); + } + input_types.push_back(*type); + } + *out_type = Convert(ResolveCommonType(*Convert(factory), input_types)); + }); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_option.cpp b/src/duckdb/src/main/capi/v2/capi_v2_option.cpp index 1e59460e0..cf1215c79 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_option.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_option.cpp @@ -21,18 +21,12 @@ DUCKDB_V2_ERROR duckdb_v2_option_get_name(duckdb_v2_option_handle option, duckdb return WithErrorHandler(err, [&]() { *out_name = Convert(Convert(option)->name); }); } -DUCKDB_V2_ERROR duckdb_v2_option_get_setting(duckdb_v2_option_handle option, duckdb_v2_str *out_setting, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(option); - DUCKDB_CHECK_ARG(out_setting); - return WithErrorHandler(err, [&]() { *out_setting = Convert(Convert(option)->setting); }); -} - -DUCKDB_V2_ERROR duckdb_v2_option_get_default_setting(duckdb_v2_option_handle option, duckdb_v2_str *out_default_setting, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_option_get_default_value(duckdb_v2_option_handle option, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(option); - DUCKDB_CHECK_ARG(out_default_setting); - return WithErrorHandler(err, [&]() { *out_default_setting = Convert(Convert(option)->default_setting); }); + DUCKDB_CHECK_ARG(out_value); + *out_value = nullptr; + return WithErrorHandler(err, [&]() { *out_value = Convert(new duckdb::Value(Convert(option)->default_value)); }); } DUCKDB_V2_ERROR duckdb_v2_option_get_description(duckdb_v2_option_handle option, duckdb_v2_str *out_description, @@ -42,12 +36,31 @@ DUCKDB_V2_ERROR duckdb_v2_option_get_description(duckdb_v2_option_handle option, return WithErrorHandler(err, [&]() { *out_description = Convert(Convert(option)->description); }); } -DUCKDB_V2_ERROR duckdb_v2_option_get_target_scope(duckdb_v2_option_handle option, - DUCKDB_V2_OPTION_TARGET_SCOPE *out_target_scope, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_option_supports_scope(duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE scope, + bool *out_supported, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(option); + DUCKDB_CHECK_ARG(out_supported); + return WithErrorHandler(err, [&]() { + const auto &wrapper = *Convert(option); + switch (scope) { + case DUCKDB_V2_SETTING_SCOPE_GLOBAL: + *out_supported = wrapper.supports_global; + break; + case DUCKDB_V2_SETTING_SCOPE_SESSION: + *out_supported = wrapper.supports_session; + break; + default: + *out_supported = wrapper.supports_global || wrapper.supports_session; + break; + } + }); +} + +DUCKDB_V2_ERROR duckdb_v2_option_get_default_scope(duckdb_v2_option_handle option, DUCKDB_V2_SETTING_SCOPE *out_scope, + duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(option); - DUCKDB_CHECK_ARG(out_target_scope); - return WithErrorHandler(err, [&]() { *out_target_scope = Convert(option)->target_scope; }); + DUCKDB_CHECK_ARG(out_scope); + return WithErrorHandler(err, [&]() { *out_scope = Convert(option)->default_scope; }); } DUCKDB_V2_ERROR duckdb_v2_option_get_alias_count(duckdb_v2_option_handle option, idx_t *out_count, @@ -72,37 +85,5 @@ DUCKDB_V2_ERROR duckdb_v2_option_get_alias(duckdb_v2_option_handle option, idx_t } // --------------------------------------------------------------------------- -// Context option getters: the connection's cascade, read from inside DuckDB +// Context options: the connection's cascade, from inside DuckDB // --------------------------------------------------------------------------- - -DUCKDB_V2_ERROR duckdb_v2_context_get_option_by_name(duckdb_v2_context_handle ctx, const duckdb_v2_identifier_t *name, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(name); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; - return WithErrorHandler(err, [&]() { - CV2OptionSource source(*Convert(ctx)); - *out_option = Convert(CV2Option::FromName(source, ConvertIdentifierName(name)).release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_context_get_option_count(duckdb_v2_context_handle ctx, idx_t *out_count, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_count); - return WithErrorHandler(err, [&]() { *out_count = CV2Option::Count(CV2OptionSource(*Convert(ctx))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_context_get_option_by_index(duckdb_v2_context_handle ctx, idx_t index, - duckdb_v2_option_handle *out_option, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_option); - *out_option = nullptr; - return WithErrorHandler(err, [&]() { - CV2OptionSource source(*Convert(ctx)); - *out_option = Convert(CV2Option::FromIndex(source, index).release()); - }); -} diff --git a/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp b/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp index dcc87c0ca..061f6e071 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp @@ -48,14 +48,8 @@ DUCKDB_V2_ERROR duckdb_v2_prepared_statement_create(duckdb_v2_connection_handle DUCKDB_CHECK_ARG(statement); return WithErrorHandler(err, [&]() { auto *connection = Convert(conn); - // Preparing runs the engine's query cleanup, which would cancel a live stream, so - // refuse first. Unlike execute this only checks the slot is free: preparing - // produces no result, so it never claims it. - if (GetBusySlot(*connection->context)->owner.load() != nullptr) { - throw duckdb::ResourceInUseException( - "connection has a live result; drain, destroy, or interrupt it before preparing a statement " - "(or open another connection)"); - } + // The engine refuses a prepare while another statement on the connection is running + ThrowIfGroupRunning(*GetBusySlot(*connection->context)); // Borrowed, not consumed: prepare a copy so the caller keeps the original. auto prepared = connection->context->Prepare(Convert(statement)->Copy()); if (prepared->HasError()) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_qname.cpp b/src/duckdb/src/main/capi/v2/capi_v2_qname.cpp index 7a7c5e234..03392a84d 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_qname.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_qname.cpp @@ -32,7 +32,11 @@ DUCKDB_V2_ERROR duckdb_v2_qname_parse(const duckdb_v2_str *text, duckdb_v2_qname DUCKDB_CHECK_ARG(out_name); *out_name = nullptr; return WithErrorHandler(err, [&]() { - auto parsed = duckdb::QualifiedName::Parse(duckdb::string(ConvertIdentifierName(text))); + duckdb::QualifiedName parsed; + duckdb::string error; + if (!duckdb::QualifiedName::TryParse(duckdb::string(ConvertIdentifierName(text)), parsed, error)) { + throw duckdb::InvalidInputException("%s in duckdb_v2_qname_parse.", error); + } CheckQNameParts(parsed, "duckdb_v2_qname_parse"); *out_name = Convert(new duckdb::QualifiedName(std::move(parsed))); }); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_replacement_scan.cpp b/src/duckdb/src/main/capi/v2/capi_v2_replacement_scan.cpp index 0e892b5a1..7b37ba0d7 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_replacement_scan.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_replacement_scan.cpp @@ -114,7 +114,8 @@ static auto CV2ReplacementScanTrampoline(ClientContext &context, ReplacementScan CV2ErrorInfo err = {}; auto err_ptr = Convert(&err); - scan_data.callback(Convert(&args), Convert(&context), &err_ptr); + CV2CallbackContext callback_context(context); + scan_data.callback(Convert(&args), Convert(&callback_context), &err_ptr); if (err.HasError()) { err.ThrowAsException(); @@ -124,7 +125,12 @@ static auto CV2ReplacementScanTrampoline(ClientContext &context, ReplacementScan class CV2ReplacementScan { public: - void Register() { + explicit CV2ReplacementScan(DatabaseInstance &db) : db(db) { + } + + //! Validates the configuration and builds the scan to register on `target`. + ReplacementScan Build(DatabaseInstance &target) { + CheckRegistrationTarget(db, target, "replacement scan"); if (!info.callback) { throw InvalidInputException("Callback must be set for the replacement scan."); } @@ -136,44 +142,16 @@ class CV2ReplacementScan { auto data = make_uniq(); data->callback = info.callback; data->user_data = info.user_data; - RegisterScan(ReplacementScan(CV2ReplacementScanTrampoline, std::move(data))); + return ReplacementScan(CV2ReplacementScanTrampoline, std::move(data)); } - virtual ~CV2ReplacementScan() = default; - virtual void RegisterScan(ReplacementScan scan) = 0; - public: + //! The database it was created for: the only one it can be registered on. + DatabaseInstance &db; CV2ReplacementScanData info; bool registered = false; }; -class CV2ConnectionReplacementScan : public CV2ReplacementScan { -public: - explicit CV2ConnectionReplacementScan(Connection &connection) : connection(connection) { - } - - void RegisterScan(ReplacementScan scan) override { - // Connection-scoped: this touches only the connection's own state, never the shared database config. - connection.context->config.replacement_scans.push_back(make_shared_ptr(std::move(scan))); - } - -private: - Connection &connection; -}; - -class CV2InstanceReplacementScan : public CV2ReplacementScan { -public: - explicit CV2InstanceReplacementScan(DatabaseInstance &instance) : instance(instance) { - } - - void RegisterScan(ReplacementScan scan) override { - DBConfig::GetConfig(instance).replacement_scans.push_back(std::move(scan)); - } - -private: - DatabaseInstance &instance; -}; - static auto Convert(duckdb_v2_replacement_scan_handle scan) -> CV2ReplacementScan * { return reinterpret_cast(scan); } @@ -189,44 +167,15 @@ static auto Convert(CV2ReplacementScan *scan) -> duckdb_v2_replacement_scan_hand using namespace duckdb::capiv2; -DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_connection(duckdb_v2_connection_handle connection, - duckdb_v2_replacement_scan_handle *out_scan, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(connection); - DUCKDB_CHECK_ARG(out_scan); - *out_scan = nullptr; - return WithErrorHandler(err, [&]() { - auto &conn = *Convert(connection); - auto scan = duckdb::make_uniq(conn); - *out_scan = Convert(scan.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_instance(duckdb_v2_instance_handle instance, - duckdb_v2_replacement_scan_handle *out_scan, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(instance); - DUCKDB_CHECK_ARG(out_scan); - *out_scan = nullptr; - return WithErrorHandler(err, [&]() { - auto &instance_wrapper = *Convert(instance); - duckdb::lock_guard guard(instance_wrapper.lock); - auto &db = *instance_wrapper.GetDatabase().instance; - auto scan = duckdb::make_uniq(db); - *out_scan = Convert(scan.release()); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create_with_extension(duckdb_v2_extension_handle extension, - duckdb_v2_replacement_scan_handle *out_scan, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(extension); - DUCKDB_CHECK_ARG(out_scan); - *out_scan = nullptr; +DUCKDB_V2_ERROR duckdb_v2_replacement_scan_create(duckdb_v2_factory_handle factory, + duckdb_v2_replacement_scan_handle *scan, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(scan); + *scan = nullptr; return WithErrorHandler(err, [&]() { - auto &db = GetExtensionLoader(extension).GetDatabaseInstance(); - auto scan = duckdb::make_uniq(db); - *out_scan = Convert(scan.release()); + auto result = duckdb::make_uniq(Convert(factory)->GetDatabase()); + *scan = Convert(result.release()); }); } @@ -373,10 +322,39 @@ DUCKDB_V2_ERROR duckdb_v2_replacement_scan_set_alias(duckdb_v2_replacement_scan_ [&]() { Convert(info)->out_alias = duckdb::Identifier(ConvertIdentifierName(alias)); }); } -DUCKDB_V2_ERROR duckdb_v2_replacement_scan_register(duckdb_v2_replacement_scan_handle scan, - duckdb_v2_error_info_handle *err) { +DUCKDB_V2_ERROR duckdb_v2_connection_register_replacement_scan(duckdb_v2_connection_handle conn, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(conn); DUCKDB_CHECK_ARG(scan); - return WithErrorHandler(err, [&]() { Convert(scan)->Register(); }); + return WithErrorHandler(err, [&]() { + auto &context = *Convert(conn)->context; + auto built = Convert(scan)->Build(*context.db); + // Connection-scoped: this touches only the client's own state, never the shared database config. + context.config.replacement_scans.push_back(duckdb::make_shared_ptr(std::move(built))); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_instance_register_replacement_scan(duckdb_v2_instance_handle instance, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(instance); + DUCKDB_CHECK_ARG(scan); + return WithErrorHandler(err, [&]() { + auto &db = *Convert(instance)->GetDatabase().instance; + duckdb::DBConfig::GetConfig(db).replacement_scans.push_back(Convert(scan)->Build(db)); + }); +} + +DUCKDB_V2_ERROR duckdb_v2_extension_register_replacement_scan(duckdb_v2_extension_handle extension, + duckdb_v2_replacement_scan_handle scan, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(extension); + DUCKDB_CHECK_ARG(scan); + return WithErrorHandler(err, [&]() { + auto &db = GetExtensionLoader(extension).GetDatabaseInstance(); + duckdb::DBConfig::GetConfig(db).replacement_scans.push_back(Convert(scan)->Build(db)); + }); } DUCKDB_V2_ERROR duckdb_v2_replacement_scan_destroy(duckdb_v2_replacement_scan_handle *scan) { diff --git a/src/duckdb/src/main/capi/v2/capi_v2_result.cpp b/src/duckdb/src/main/capi/v2/capi_v2_result.cpp index 932ef6b2d..b9daeef94 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_result.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_result.cpp @@ -50,9 +50,14 @@ void ReadChangedRows(const ArrowArrayWrapper &unit, idx_t &rows_changed) { // ResultWrapperV2 state machine // --------------------------------------------------------------------------- -void ResultWrapperV2::BeginPending(unique_ptr next_handle, bool is_principal) { +void ResultWrapperV2::BeginPending(unique_ptr next_handle, bool is_principal, StatementType written_type) { next_handle->ThrowIfError(); principal_active = is_principal; + stream_when_ready = StreamsByDefault(written_type, next_handle->GetStatementProperties()); + if (!stream_when_ready) { + // Settled before the caller steps it, so every step stays one bounded unit of work + next_handle->Materialize(); + } if (is_principal) { types = next_handle->GetTypes(); names = next_handle->GetNames(); @@ -80,6 +85,7 @@ void ResultWrapperV2::StartNextFragment() { } idx_t this_index = fragment_index; auto stmt = std::move(fragments[fragment_index++]); + auto written_type = stmt->type; bool is_last = fragment_index == fragments.size(); // Parameters bind only to the first fragment (statement_execute rejects them on // a statement that expands, so that fragment is the user's statement). Later @@ -106,7 +112,7 @@ void ResultWrapperV2::StartNextFragment() { if (has_result) { principal_seen = true; } - BeginPending(std::move(next_handle), is_principal); + BeginPending(std::move(next_handle), is_principal, written_type); } void ResultWrapperV2::RequireMetadata() const { @@ -122,12 +128,12 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::HandleExecutionError(ErrorData err // (e.g. a max_execution_time timeout) must surface as an error carrying the // engine's message, mirroring the eager ClientContext::Query path. bool user_cancelled = error_data.Type() == ExceptionType::INTERRUPT && busy_slot && - busy_slot->cancel_requested.load(std::memory_order_relaxed); + busy_slot->cancel_requests.load() != cancel_requests_at_start; stream.reset(); handle.reset(); fragments.clear(); try { - RollbackIncompleteGroup(); + AbandonIncompleteGroup(); } catch (...) { // Best effort; never mask the original error. } @@ -178,9 +184,7 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptrGetStatementProperties().result_eagerness == ResultEagerness::FORCED) { - // The statement completes before its result is returned: - // its chunks come from the retained handle instead. + if (!stream_when_ready) { handle->Complete(); if (handle->HasError()) { return HandleExecutionError(handle->GetErrorObject()); @@ -224,9 +228,7 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptr unit; try { unit = @@ -405,24 +407,17 @@ auto ExecutePreparedStatementV2(const shared_ptr &context, Prepar identifier_map_t &values, shared_ptr format) -> unique_ptr { auto wrapper = make_uniq(); - // One live result per connection, claimed the way statement_execute claims it and - // before the submission runs, which would otherwise cancel the live stream. - auto busy_slot = GetBusySlot(*context); - void *expected = nullptr; - if (!busy_slot->owner.compare_exchange_strong(expected, wrapper.get())) { - throw ResourceInUseException("connection has a live result; drain, destroy, or interrupt it before starting " - "a new query (or open another connection)"); - } - // On any failure below, the wrapper's destructor releases the slot. - wrapper->busy_slot = std::move(busy_slot); - wrapper->busy_slot->cancel_requested.store(false, std::memory_order_relaxed); + wrapper->busy_slot = GetBusySlot(*context); + wrapper->cancel_requests_at_start = wrapper->busy_slot->cancel_requests.load(); + ThrowIfGroupRunning(*wrapper->busy_slot); wrapper->context = context; wrapper->format = std::move(format); // A prepared statement is always one engine statement: preprocessing, expansion and // the wrapping transaction all happened at prepare time, so this bypasses the fragment // machinery and is always principal. fragment_count is 1 for metadata symmetry only. wrapper->fragment_count = 1; - wrapper->BeginPending(prepared.Submit(values, QueryParameters(wrapper->format)), true); + // The engine refuses the submission while another statement on the connection is running + wrapper->BeginPending(prepared.Submit(values, QueryParameters(wrapper->format)), true, prepared.GetStatementType()); // The engine runs a prepared statement through an internal EXECUTE, whose statement type // would otherwise be what the result reports. Report the type of the statement that was // prepared instead, so a prepared result is indistinguishable from a stateless one. @@ -435,24 +430,12 @@ auto ExecuteStatementV2(const shared_ptr &context, const SQLState idx_t parameter_count, const char *function_name, shared_ptr format) -> unique_ptr { auto wrapper = make_uniq(); - // One live result per connection. The busy slot lives in the context's - // registered-state map (so the connection handle stays a bare Connection *), - // shared with this result. The busy check is a manual return path: no - // ExceptionType maps to RESOURCE_IN_USE, so routing it through - // WithErrorHandler would degrade the code. It must run before PendingQuery, - // which would otherwise silently cancel the live stream. - auto busy_slot = GetBusySlot(*context); - void *expected = nullptr; - if (!busy_slot->owner.compare_exchange_strong(expected, wrapper.get())) { - throw ResourceInUseException("connection has a live result; drain, destroy, or interrupt it before starting " - "a new query (or open another connection)"); - } - // On any failure below, the wrapper's destructor releases the slot. - wrapper->busy_slot = std::move(busy_slot); - // A fresh query starts uncancelled: clear any consumer-cancellation request - // left over from before this result claimed the slot (mirrors the engine - // clearing interrupt_state at query begin). - wrapper->busy_slot->cancel_requested.store(false, std::memory_order_relaxed); + // The busy slot lives in the context's registered-state map, so the connection handle stays a bare Connection * + wrapper->busy_slot = GetBusySlot(*context); + wrapper->cancel_requests_at_start = wrapper->busy_slot->cancel_requests.load(); + ThrowIfGroupRunning(*wrapper->busy_slot); + // Refused before preprocessing, which decides from the active transaction whether to wrap a group in its own + context->VerifyNoOpenResult(); // Borrowed, not consumed: execute a copy so the caller keeps the original. auto stmt = statement.Copy(); @@ -492,9 +475,17 @@ auto ExecuteStatementV2(const shared_ptr &context, const SQLState } wrapper->context = context; wrapper->format = std::move(format); + if (wrapper->fragments.size() > 1) { + // On any failure below, the wrapper's destructor releases the claim + void *expected = nullptr; + if (!wrapper->busy_slot->owner.compare_exchange_strong(expected, wrapper.get())) { + ThrowConnectionInUse(); + } + } // Prepare the first fragment. Lazy streaming execution: nothing // executes until the result is stepped; for non-expanding statements - // (the common case) this also captures the metadata immediately. + // (the common case) this also captures the metadata immediately. The engine refuses the + // submission while another statement on the connection is running. wrapper->StartNextFragment(); return wrapper; } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp b/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp index f2a718dbc..b4cb9f7d1 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp @@ -1,6 +1,18 @@ #include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/parser/expression/star_expression.hpp" +#include "duckdb/parser/query_node/delete_query_node.hpp" +#include "duckdb/parser/query_node/insert_query_node.hpp" +#include "duckdb/parser/query_node/merge_query_node.hpp" +#include "duckdb/parser/query_node/select_node.hpp" +#include "duckdb/parser/query_node/update_query_node.hpp" #include "duckdb/parser/sql_statement.hpp" +#include "duckdb/parser/statement/delete_statement.hpp" +#include "duckdb/parser/statement/insert_statement.hpp" +#include "duckdb/parser/statement/merge_into_statement.hpp" +#include "duckdb/parser/statement/select_statement.hpp" +#include "duckdb/parser/statement/update_statement.hpp" +#include "duckdb/parser/tableref/column_data_ref.hpp" #include #include @@ -36,6 +48,26 @@ auto ParameterNamesInBindingOrder(const duckdb::SQLStatement &statement) return names; } +//! The common table expressions of a statement a collection can be bound to +auto GetCollectionCTEMap(duckdb::SQLStatement &statement) -> CommonTableExpressionMap & { + switch (statement.type) { + case StatementType::SELECT_STATEMENT: + return statement.Cast().node->cte_map; + case StatementType::INSERT_STATEMENT: + return statement.Cast().node->cte_map; + case StatementType::DELETE_STATEMENT: + return statement.Cast().node->cte_map; + case StatementType::UPDATE_STATEMENT: + return statement.Cast().node->cte_map; + case StatementType::MERGE_INTO_STATEMENT: + return statement.Cast().node->cte_map; + default: + throw InvalidInputException("A collection can only be bound to a SELECT, INSERT, UPDATE, DELETE or MERGE INTO " + "statement, not to a %s statement.", + StatementTypeToString(statement.type)); + } +} + } // namespace auto Convert(duckdb_v2_statement_iterator_handle ptr) -> StatementIteratorWrapperV2 * { @@ -168,6 +200,56 @@ DUCKDB_V2_ERROR duckdb_v2_sql_statement_get_parameter_name(duckdb_v2_sql_stateme }); } +DUCKDB_V2_ERROR duckdb_v2_statement_add_collection(duckdb_v2_sql_statement_handle statement, + const duckdb_v2_identifier_t *name, + duckdb_v2_column_data_collection_handle collection, + const duckdb_v2_identifier_t *column_names, idx_t column_count, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(statement); + DUCKDB_CHECK_ARG(name); + DUCKDB_CHECK_ARG(collection); + return WithErrorHandler(err, [&]() { + auto &stmt = *Convert(statement); + auto &cdc = *Convert(collection); + duckdb::Identifier alias(ConvertIdentifierName(name)); + if (alias.empty()) { + throw duckdb::InvalidInputException("The name to bind a collection under cannot be empty."); + } + if (column_count > 0 && !column_names) { + throw duckdb::InvalidInputException("column_names cannot be null when column_count is not 0."); + } + if (column_count > 0 && column_count != cdc.ColumnCount()) { + throw duckdb::InvalidInputException( + "column_count (%llu) does not match the collection's column count (%llu).", + static_cast(column_count), static_cast(cdc.ColumnCount())); + } + duckdb::vector expected_names; + for (idx_t i = 0; i < column_count; i++) { + expected_names.emplace_back(ConvertIdentifierName(column_names[i])); + if (expected_names.back().empty()) { + throw duckdb::InvalidInputException("Column name %llu of the collection cannot be empty.", + static_cast(i)); + } + } + + auto &cte_map = GetCollectionCTEMap(stmt); + if (cte_map.map.find(alias) != cte_map.map.end()) { + throw duckdb::InvalidInputException("The statement already has a common table expression named %s.", + duckdb::SQLIdentifier(alias)); + } + // SELECT * over a borrowed reference to the collection, never materialized + auto ref = duckdb::make_uniq(cdc, std::move(expected_names)); + ref->alias = alias; + auto select = duckdb::make_uniq(); + select->select_list.push_back(duckdb::make_uniq()); + select->from_table = std::move(ref); + auto cte_info = duckdb::make_uniq(); + cte_info->query_node = std::move(select); + cte_info->materialized = duckdb::CTEMaterialize::CTE_MATERIALIZE_NEVER; + cte_map.map.insert(alias, std::move(cte_info)); + }); +} + DUCKDB_V2_ERROR duckdb_v2_sql_statement_destroy(duckdb_v2_sql_statement_handle *statement) { return WithErrorHandler(nullptr, [&]() { if (!statement) { @@ -217,7 +299,7 @@ DUCKDB_V2_ERROR duckdb_v2_statement_bind(duckdb_v2_connection_handle conn, duckd "statement expands into multiple engine statements; bind is not supported, execute it instead"); } // BindStatement, not Prepare: bind phase only (read-only, no optimize / physical - // plan) and must not cancel a live result (Prepare's InitialCleanup would). It + // plan) and allowed while a result is live (Prepare is refused). It // throws on a bind error, routed through WithErrorHandler. auto signature = connection->context->BindStatement(std::move(fragments[0])); // Assemble into locals, publish to the out-params only at the end once nothing diff --git a/src/duckdb/src/main/capi/v2/capi_v2_value.cpp b/src/duckdb/src/main/capi/v2/capi_v2_value.cpp index 681959820..1f746dc96 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_value.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_value.cpp @@ -166,23 +166,6 @@ DUCKDB_V2_ERROR duckdb_v2_value_destroy(duckdb_v2_value_handle *value) { }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_null(duckdb_v2_logical_type_handle type, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(type); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { - // ANY is a signature wildcard; a value carries data, so reject it. - if (Convert(type)->id() == duckdb::LogicalTypeId::ANY) { - throw duckdb::InvalidInputException("duckdb_v2_value_create_null: type cannot be ANY"); - } - // Value(LogicalType) constructs a typed NULL — exactly what we want. - auto value_type = Convert(type); - auto *v = new duckdb::Value(*value_type); - *out_value = Convert(v); - }); -} - DUCKDB_V2_ERROR duckdb_v2_value_is_null(duckdb_v2_value_handle value, bool *out_is_null, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(value); @@ -462,322 +445,141 @@ DUCKDB_V2_ERROR duckdb_v2_value_get_interval(duckdb_v2_value_handle value, duckd // --------------------------------------------------------------------------- // Typed constructors // -// Two forms per type: the context form for a live bind / execution context, -// the connection form for outside one. Both are gated on their handle, which -// is what makes a value's construction scoped to a catalog. +// Gated on the context handle, which is what makes a value's construction +// scoped to a catalog. // --------------------------------------------------------------------------- -DUCKDB_V2_ERROR duckdb_v2_value_create_bool_with_context(duckdb_v2_context_handle ctx, bool in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_bool(duckdb_v2_factory_handle factory, bool in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BOOLEAN(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_bool_with_connection(duckdb_v2_connection_handle conn, bool in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BOOLEAN(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint_with_context(duckdb_v2_context_handle ctx, int8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TINYINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint_with_connection(duckdb_v2_connection_handle conn, int8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_tinyint(duckdb_v2_factory_handle factory, int8_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TINYINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_smallint_with_context(duckdb_v2_context_handle ctx, int16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::SMALLINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_smallint_with_connection(duckdb_v2_connection_handle conn, int16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_smallint(duckdb_v2_factory_handle factory, int16_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::SMALLINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_int_with_context(duckdb_v2_context_handle ctx, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::INTEGER(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_int_with_connection(duckdb_v2_connection_handle conn, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_int(duckdb_v2_factory_handle factory, int32_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::INTEGER(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_bigint_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_bigint(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BIGINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_bigint_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BIGINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::HUGEINT(Convert(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_hugeint(duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::HUGEINT(Convert(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint_with_context(duckdb_v2_context_handle ctx, uint8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UTINYINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint_with_connection(duckdb_v2_connection_handle conn, uint8_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_utinyint(duckdb_v2_factory_handle factory, uint8_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UTINYINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint_with_context(duckdb_v2_context_handle ctx, uint16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::USMALLINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint_with_connection(duckdb_v2_connection_handle conn, uint16_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_usmallint(duckdb_v2_factory_handle factory, uint16_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::USMALLINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_uint_with_context(duckdb_v2_context_handle ctx, uint32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_uint(duckdb_v2_factory_handle factory, uint32_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UINTEGER(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_uint_with_connection(duckdb_v2_connection_handle conn, uint32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UINTEGER(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint_with_context(duckdb_v2_context_handle ctx, uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UBIGINT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint_with_connection(duckdb_v2_connection_handle conn, uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_ubigint(duckdb_v2_factory_handle factory, uint64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UBIGINT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_uhugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UHUGEINT(Convert(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_uhugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_uhugeint(duckdb_v2_factory_handle factory, const duckdb_v2_uhugeint_t *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UHUGEINT(Convert(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_float_with_context(duckdb_v2_context_handle ctx, float in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_float(duckdb_v2_factory_handle factory, float in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::FLOAT(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_float_with_connection(duckdb_v2_connection_handle conn, float in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::FLOAT(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_double_with_context(duckdb_v2_context_handle ctx, double in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::DOUBLE(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_double_with_connection(duckdb_v2_connection_handle conn, double in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_double(duckdb_v2_factory_handle factory, double in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::DOUBLE(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_varchar_with_context(duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value(Convert(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_varchar_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_varchar(duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value(Convert(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_blob_with_context(duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BLOB_RAW(std::string(Convert(in_value)))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_blob_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_blob(duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::BLOB_RAW(std::string(Convert(in_value)))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_null_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_null(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle type, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value(RequireValueType(type))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_null_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value(RequireValueType(type))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_type_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle in_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TYPE(RequireValueType(in_type))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_type_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle in_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_type(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle in_type, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TYPE(RequireValueType(in_type))); }); @@ -789,212 +591,99 @@ DUCKDB_V2_ERROR duckdb_v2_value_create_type_with_connection(duckdb_v2_connection // The duals of the temporal accessors: same units, no range checking. // --------------------------------------------------------------------------- -DUCKDB_V2_ERROR duckdb_v2_value_create_date_with_context(duckdb_v2_context_handle ctx, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::DATE(duckdb::date_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_date_with_connection(duckdb_v2_connection_handle conn, int32_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_date(duckdb_v2_factory_handle factory, int32_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::DATE(duckdb::date_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_time_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_time(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIME(duckdb::dtime_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_time_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIME(duckdb::dtime_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIME_NS(duckdb::dtime_ns_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_time_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIME_NS(duckdb::dtime_ns_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz_with_context(duckdb_v2_context_handle ctx, uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMETZ(duckdb::dtime_tz_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz_with_connection(duckdb_v2_connection_handle conn, uint64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_time_tz(duckdb_v2_factory_handle factory, uint64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMETZ(duckdb::dtime_tz_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMP(duckdb::timestamp_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMP(duckdb::timestamp_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler( - err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPSEC(duckdb::timestamp_sec_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_sec(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler( err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPSEC(duckdb::timestamp_sec_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, - [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPMS(duckdb::timestamp_ms_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ms(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPMS(duckdb::timestamp_ms_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, - [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPNS(duckdb::timestamp_ns_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPNS(duckdb::timestamp_ns_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, - [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPTZ(duckdb::timestamp_tz_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_with_connection(duckdb_v2_connection_handle conn, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPTZ(duckdb::timestamp_tz_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns_with_context(duckdb_v2_context_handle ctx, int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler( - err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPTZNS(duckdb::timestamp_tz_ns_t(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns_with_connection(duckdb_v2_connection_handle conn, - int64_t in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_timestamp_tz_ns(duckdb_v2_factory_handle factory, int64_t in_value, + duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler( err, [&]() { *out_value = Emit(duckdb::Value::TIMESTAMPTZNS(duckdb::timestamp_tz_ns_t(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_interval_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_interval_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::INTERVAL(Convert(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_interval_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_interval_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_interval(duckdb_v2_factory_handle factory, const duckdb_v2_interval_t *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::INTERVAL(Convert(in_value))); }); @@ -1007,79 +696,34 @@ DUCKDB_V2_ERROR duckdb_v2_value_create_interval_with_connection(duckdb_v2_connec // plus its width and scale, an internal 128-bit form, or opaque storage bytes. // --------------------------------------------------------------------------- -DUCKDB_V2_ERROR duckdb_v2_value_create_decimal_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, uint8_t width, - uint8_t scale, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(BuildDecimal(in_value, width, scale)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_uuid_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UUID(Convert(in_value))); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_bit_with_context(duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(BuildBit(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_bignum_with_context(duckdb_v2_context_handle ctx, const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(BuildBignum(in_value)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_decimal_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, uint8_t width, - uint8_t scale, duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_decimal(duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, + uint8_t width, uint8_t scale, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(BuildDecimal(in_value, width, scale)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_uuid_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_hugeint_t *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_uuid(duckdb_v2_factory_handle factory, const duckdb_v2_hugeint_t *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(duckdb::Value::UUID(Convert(in_value))); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_bit_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_bit(duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(BuildBit(in_value)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_bignum_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_str *in_value, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_bignum(duckdb_v2_factory_handle factory, const duckdb_v2_str *in_value, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(BuildBignum(in_value)); }); @@ -1110,7 +754,7 @@ duckdb::vector CollectChildren(const duckdb_v2_value_handle *chil // The common type of a set of children, the same rule a list literal follows. // An explicit type wins outright; without one an empty set has nothing to // resolve, which is what makes the empty forms take their type. -duckdb::LogicalType ResolveChildType(duckdb::ClientContext &ctx, duckdb_v2_logical_type_handle declared, +duckdb::LogicalType ResolveChildType(CV2Factory &factory, duckdb_v2_logical_type_handle declared, const duckdb::vector &children, const char *what) { if (declared) { auto declared_type = Convert(declared); @@ -1120,21 +764,22 @@ duckdb::LogicalType ResolveChildType(duckdb::ClientContext &ctx, duckdb_v2_logic throw duckdb::InvalidInputException(std::string("cannot resolve the ") + what + " of an empty set; pass it explicitly"); } - auto type = children[0].type(); - for (idx_t i = 1; i < children.size(); i++) { - type = duckdb::LogicalType::MaxLogicalType(ctx, type, children[i].type()); + duckdb::vector types; + types.reserve(children.size()); + for (auto &child : children) { + types.push_back(child.type()); } - return type; + return ResolveCommonType(factory, types); } -duckdb::Value BuildList(duckdb::ClientContext &ctx, duckdb_v2_logical_type_handle child_type, +duckdb::Value BuildList(CV2Factory &factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count) { auto values = CollectChildren(children, child_count); - auto type = ResolveChildType(ctx, child_type, values, "element type"); + auto type = ResolveChildType(factory, child_type, values, "element type"); return duckdb::Value::LIST(type, std::move(values)); } -duckdb::Value BuildArray(duckdb::ClientContext &ctx, duckdb_v2_logical_type_handle child_type, +duckdb::Value BuildArray(CV2Factory &factory, duckdb_v2_logical_type_handle child_type, const duckdb_v2_value_handle *children, idx_t child_count) { auto values = CollectChildren(children, child_count); // The engine's minimum array size is 1, so there is no empty ARRAY, with or @@ -1142,7 +787,7 @@ duckdb::Value BuildArray(duckdb::ClientContext &ctx, duckdb_v2_logical_type_hand if (values.empty()) { throw duckdb::InvalidInputException("an ARRAY must have at least one element"); } - auto type = ResolveChildType(ctx, child_type, values, "element type"); + auto type = ResolveChildType(factory, child_type, values, "element type"); return duckdb::Value::ARRAY(type, std::move(values)); } @@ -1175,136 +820,66 @@ duckdb::Value BuildTuple(const duckdb_v2_value_handle *children, idx_t field_cou return duckdb::Value::STRUCT(duckdb::LogicalType::TUPLE(std::move(types)), std::move(values)); } -duckdb::Value BuildMap(duckdb::ClientContext &ctx, duckdb_v2_logical_type_handle key_type, +duckdb::Value BuildMap(CV2Factory &factory, duckdb_v2_logical_type_handle key_type, duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, const duckdb_v2_value_handle *values, idx_t entry_count) { auto key_values = CollectChildren(keys, entry_count); auto value_values = CollectChildren(values, entry_count); - auto resolved_key = ResolveChildType(ctx, key_type, key_values, "key type"); - auto resolved_value = ResolveChildType(ctx, value_type, value_values, "value type"); + auto resolved_key = ResolveChildType(factory, key_type, key_values, "key type"); + auto resolved_value = ResolveChildType(factory, value_type, value_values, "value type"); return duckdb::Value::MAP(resolved_key, resolved_value, std::move(key_values), std::move(value_values)); } } // namespace -DUCKDB_V2_ERROR duckdb_v2_value_create_list_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle child_type, - const duckdb_v2_value_handle *children, idx_t child_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, - [&]() { *out_value = Emit(BuildList(*Convert(ctx), child_type, children, child_count)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_list_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle child_type, - const duckdb_v2_value_handle *children, idx_t child_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { - auto &ctx = *Convert(conn)->context; - ctx.RunFunctionInTransaction([&]() { *out_value = Emit(BuildList(ctx, child_type, children, child_count)); }); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_array_with_context(duckdb_v2_context_handle ctx, - duckdb_v2_logical_type_handle child_type, - const duckdb_v2_value_handle *children, idx_t child_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, - [&]() { *out_value = Emit(BuildArray(*Convert(ctx), child_type, children, child_count)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_array_with_connection(duckdb_v2_connection_handle conn, - duckdb_v2_logical_type_handle child_type, - const duckdb_v2_value_handle *children, idx_t child_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_list(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, + const duckdb_v2_value_handle *children, idx_t child_count, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; - return WithErrorHandler(err, [&]() { - auto &ctx = *Convert(conn)->context; - ctx.RunFunctionInTransaction([&]() { *out_value = Emit(BuildArray(ctx, child_type, children, child_count)); }); - }); + return WithErrorHandler( + err, [&]() { *out_value = Emit(BuildList(*Convert(factory), child_type, children, child_count)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_struct_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_identifier_t *names, - const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_create_array(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle child_type, + const duckdb_v2_value_handle *children, idx_t child_count, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(BuildStruct(names, children, field_count)); }); + return WithErrorHandler( + err, [&]() { *out_value = Emit(BuildArray(*Convert(factory), child_type, children, child_count)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_struct_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_identifier_t *names, - const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_struct(duckdb_v2_factory_handle factory, const duckdb_v2_identifier_t *names, + const duckdb_v2_value_handle *children, idx_t field_count, + duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(BuildStruct(names, children, field_count)); }); } -DUCKDB_V2_ERROR duckdb_v2_value_create_tuple_with_context(duckdb_v2_context_handle ctx, - const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler(err, [&]() { *out_value = Emit(BuildTuple(children, field_count)); }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_create_tuple_with_connection(duckdb_v2_connection_handle conn, - const duckdb_v2_value_handle *children, idx_t field_count, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +DUCKDB_V2_ERROR duckdb_v2_value_create_tuple(duckdb_v2_factory_handle factory, const duckdb_v2_value_handle *children, + idx_t field_count, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { *out_value = Emit(BuildTuple(children, field_count)); }); } DUCKDB_V2_ERROR -duckdb_v2_value_create_map_with_context(duckdb_v2_context_handle ctx, duckdb_v2_logical_type_handle key_type, - duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, - const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); - DUCKDB_CHECK_ARG(out_value); - *out_value = nullptr; - return WithErrorHandler( - err, [&]() { *out_value = Emit(BuildMap(*Convert(ctx), key_type, value_type, keys, values, entry_count)); }); -} - -DUCKDB_V2_ERROR -duckdb_v2_value_create_map_with_connection(duckdb_v2_connection_handle conn, duckdb_v2_logical_type_handle key_type, - duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, - const duckdb_v2_value_handle *values, idx_t entry_count, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); +duckdb_v2_value_create_map(duckdb_v2_factory_handle factory, duckdb_v2_logical_type_handle key_type, + duckdb_v2_logical_type_handle value_type, const duckdb_v2_value_handle *keys, + const duckdb_v2_value_handle *values, idx_t entry_count, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(out_value); *out_value = nullptr; return WithErrorHandler(err, [&]() { - auto &ctx = *Convert(conn)->context; - ctx.RunFunctionInTransaction( - [&]() { *out_value = Emit(BuildMap(ctx, key_type, value_type, keys, values, entry_count)); }); + *out_value = Emit(BuildMap(*Convert(factory), key_type, value_type, keys, values, entry_count)); }); } @@ -1355,36 +930,23 @@ DUCKDB_V2_ERROR duckdb_v2_value_get_child(duckdb_v2_value_handle value, idx_t in }); } -static void CastValueV2(duckdb::ClientContext &ctx, duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle target_type, duckdb_v2_value_handle *out_value) { +static void CastValueV2(CV2Factory &factory, duckdb_v2_value_handle value, duckdb_v2_logical_type_handle target_type, + duckdb_v2_value_handle *out_value) { *out_value = nullptr; - // Non-strict, through the context's cast function set (registered - // custom casts included). Cast failures propagate. + // Non-strict, through the client's cast function set (registered custom + // casts included), or the built-in casts only. Cast failures propagate. auto target = Convert(target_type); - auto casted = Convert(value)->CastAs(ctx, *target); + auto context = factory.TryGetClientContext(); + auto casted = context ? Convert(value)->CastAs(*context, *target) : Convert(value)->DefaultCastAs(*target); *out_value = Convert(new duckdb::Value(std::move(casted))); } -DUCKDB_V2_ERROR duckdb_v2_value_cast_with_connection(duckdb_v2_connection_handle conn, duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, - duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(conn); - DUCKDB_CHECK_ARG(value); - DUCKDB_CHECK_ARG(target_type); - DUCKDB_CHECK_ARG(out_value); - return WithErrorHandler(err, [&]() { - auto &ctx = *Convert(conn)->context; - ctx.RunFunctionInTransaction([&]() { CastValueV2(ctx, value, target_type, out_value); }); - }); -} - -DUCKDB_V2_ERROR duckdb_v2_value_cast_with_context(duckdb_v2_context_handle ctx, duckdb_v2_value_handle value, - duckdb_v2_logical_type_handle target_type, - duckdb_v2_value_handle *out_value, duckdb_v2_error_info_handle *err) { - DUCKDB_CHECK_ARG(ctx); +DUCKDB_V2_ERROR duckdb_v2_value_cast(duckdb_v2_factory_handle factory, duckdb_v2_value_handle value, + duckdb_v2_logical_type_handle target_type, duckdb_v2_value_handle *out_value, + duckdb_v2_error_info_handle *err) { + DUCKDB_CHECK_ARG(factory); DUCKDB_CHECK_ARG(value); DUCKDB_CHECK_ARG(target_type); DUCKDB_CHECK_ARG(out_value); - return WithErrorHandler(err, [&]() { CastValueV2(*Convert(ctx), value, target_type, out_value); }); + return WithErrorHandler(err, [&]() { CastValueV2(*Convert(factory), value, target_type, out_value); }); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp b/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp index 7a4c34e9e..f38219eab 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp @@ -9,6 +9,7 @@ #include "duckdb/common/vector/map_vector.hpp" #include "duckdb/common/vector/struct_vector.hpp" #include "duckdb/common/vector/union_vector.hpp" +#include "duckdb/common/vector_operations/vector_operations.hpp" // Out-param zeroing on failure: // - Pointer-bearing out-params (out_view, out_child, out_data) are @@ -75,6 +76,50 @@ DUCKDB_V2_ERROR duckdb_v2_vector_flatten(duckdb_v2_vector_handle vector, duckdb_ return WithErrorHandler(err, [&]() { Convert(vector)->Flatten(); }); } +DUCKDB_V2_ERROR duckdb_v2_vector_cast(duckdb_v2_factory_handle factory, duckdb_v2_vector_handle source, + duckdb_v2_vector_handle target, DUCKDB_V2_CAST_MODE mode, bool *out_converted, + duckdb_v2_error_info_handle *cast_err, duckdb_v2_error_info_handle *err) { + if (cast_err) { + *cast_err = nullptr; + } + DUCKDB_CHECK_ARG(factory); + DUCKDB_CHECK_ARG(source); + DUCKDB_CHECK_ARG(target); + return WithErrorHandler(err, [&]() { + auto *src = Convert(source); + auto *dst = Convert(target); + if (src == dst) { + throw duckdb::InvalidInputException("duckdb_v2_vector_cast: source and target must be different vectors"); + } + if (mode != DUCKDB_V2_CAST_MODE_NORMAL && mode != DUCKDB_V2_CAST_MODE_TRY) { + throw duckdb::InvalidInputException("duckdb_v2_vector_cast: invalid cast mode %d", static_cast(mode)); + } + const auto count = src->size(); + // a try cast collects the error instead of throwing, and NULLs the rows that failed + duckdb::string error; + auto error_ptr = mode == DUCKDB_V2_CAST_MODE_TRY ? &error : nullptr; + auto context = Convert(factory)->TryGetClientContext(); + // the casts expect a fresh result, so stale validity or data in the target cannot leak into it + duckdb::Vector result(dst->GetType(), count); + auto converted = context ? duckdb::VectorOperations::TryCast(*context, *src, result, count, error_ptr) + : duckdb::VectorOperations::DefaultTryCast(*src, result, count, error_ptr); + dst->Reference(result); + if (out_converted) { + *out_converted = converted; + } + if (cast_err && !converted) { + // rendered like any error the API reports, as a conversion error + auto info = duckdb::make_uniq(); + try { + throw duckdb::ConversionException(error); + } catch (...) { + RenderCaughtError(info->code, info->message, info->raw_message); + } + *cast_err = Convert(info.release()); + } + }); +} + DUCKDB_V2_ERROR duckdb_v2_vector_reference(duckdb_v2_vector_handle vector, duckdb_v2_vector_handle source, duckdb_v2_error_info_handle *err) { DUCKDB_CHECK_ARG(vector); diff --git a/src/duckdb/src/main/client_context.cpp b/src/duckdb/src/main/client_context.cpp index 657395edc..5c36b0b47 100644 --- a/src/duckdb/src/main/client_context.cpp +++ b/src/duckdb/src/main/client_context.cpp @@ -219,7 +219,11 @@ void ClientContext::Destroy() { if (transaction.HasActiveTransaction()) { transaction.ResetActiveQuery(); if (!transaction.IsAutoCommit()) { - transaction.Rollback(nullptr); + // a destructor cannot throw: once the database is invalidated, the rollback can fail as well + try { + transaction.Rollback(nullptr); + } catch (...) { // NOLINT + } } } CleanupInternal(*lock); @@ -252,6 +256,31 @@ unique_ptr ClientContext::ErrorResult(ErrorData error, const string &query) { return make_uniq(std::move(error)); } +bool ClientContext::OpenResultHoldsConnection(ClientContextLock &) { + // A query no result holds (one whose begin failed), or whose result a collector built so that nothing can end + // it, is abandoned instead + return active_query && active_query->HasOpenResult() && !active_query->collector_built_result; +} + +template +unique_ptr ClientContext::InitialCleanup(ClientContextLock &lock) { + if (OpenResultHoldsConnection(lock)) { + // The statement never ran, so no invalidation policy applies to the open result's transaction + auto error = ErrorData(ResourceInUseException(OPEN_RESULT_ERROR)); + ProcessError(error, string()); + return make_uniq(std::move(error)); + } + AbandonActiveQuery(lock); + return nullptr; +} + +void ClientContext::VerifyNoOpenResult() { + auto lock = LockContext(); + if (OpenResultHoldsConnection(*lock)) { + throw ResourceInUseException(OPEN_RESULT_ERROR); + } +} + Logger &ClientContext::GetLogger() const { return *logger; } @@ -518,7 +547,6 @@ unique_ptr ClientContext::PrepareInternal(ClientContextLock & prepare->statement = std::move(statement); QueryParameters parameters; - parameters.result_eagerness = ResultEagerness::FORCED; auto result = RunStatementInternal(lock, std::move(prepare), parameters, false); result->ThrowIfError(); auto entry = client_data->prepared_statements.find(Identifier(name)); @@ -541,7 +569,9 @@ unique_ptr ClientContext::Prepare(unique_ptr st // Try to prepare. try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } return PrepareInternal(*lock, std::move(statement)); } catch (std::exception &ex) { return ErrorResult(ErrorData(ex), query); @@ -596,7 +626,9 @@ unique_ptr ClientContext::Prepare(const string &query) { auto lock = LockContext(); // prepare the query try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } // first parse the query auto statements = ParseStatementsInternal(*lock, query); @@ -614,7 +646,7 @@ unique_ptr ClientContext::Prepare(const string &query) { unique_ptr ClientContext::SubmitStatementInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters) { + const QueryParameters ¶meters, Retention retention) { // prepare the query for execution if (!statement->named_param_map.empty() && parameters.statement_args) { PreparedStatement::VerifyParameters(*parameters.statement_args, statement->named_param_map, this); @@ -630,12 +662,12 @@ unique_ptr ClientContext::SubmitStatementInternal(ClientContextLock } // execute the prepared statement CheckIfPreparedStatementIsExecutable(*prepared); - return SubmitPreparedStatementInternal(lock, std::move(prepared), parameters); + return SubmitPreparedStatementInternal(lock, std::move(prepared), parameters, retention); } unique_ptr ClientContext::RunStatementInternal(ClientContextLock &lock, unique_ptr statement, const QueryParameters ¶meters, bool verify) { - auto result = SubmitInternal(lock, std::move(statement), parameters, verify); + auto result = SubmitInternal(lock, std::move(statement), parameters, verify, Retention::RETAINED_AT_SUBMISSION); if (result->HasError()) { return result; } @@ -651,7 +683,7 @@ static bool HasBoundParameterValues(const SQLStatement &statement) { } unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters) { + const QueryParameters ¶meters, Retention retention) { // CONNECT chokepoint: when connected, non-control SQL is rewritten in place and falls through to // the normal pipeline. No recursion — the rewrite goes through SubmitStatementInternal, not back here. if (is_connected) { @@ -702,7 +734,7 @@ unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, bool invalidate_query = true; try { - result = SubmitStatementInternal(lock, std::move(statement), parameters); + result = SubmitStatementInternal(lock, std::move(statement), parameters, retention); } catch (std::exception &ex) { ErrorData error(ex); if (!ErrorInvalidatesTransaction(error.Type())) { @@ -750,15 +782,16 @@ void ClientContext::LogQueryInternal(ClientContextLock &, const string &query) { client_data->log_query_writer->Sync(); } -unique_ptr ClientContext::Query(unique_ptr statement, QueryParameters parameters) { +unique_ptr ClientContext::Query(unique_ptr statement, const QueryParameters ¶meters) { auto lock = LockContext(); - parameters.result_eagerness = ResultEagerness::FORCED; try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } } catch (std::exception &ex) { return ErrorResult(ErrorData(ex), statement->query); } - auto result = SubmitInternal(*lock, std::move(statement), parameters, true); + auto result = SubmitInternal(*lock, std::move(statement), parameters, true, Retention::RETAINED_AT_SUBMISSION); if (result->HasError()) { if (transaction.HasActiveTransaction() && transaction.GetAutoRollback()) { transaction.Rollback(result->GetErrorObject()); @@ -770,9 +803,10 @@ unique_ptr ClientContext::Query(unique_ptr statement, unique_ptr ClientContext::Query(const string &query, QueryParameters query_parameters) { auto lock = LockContext(); - // The lazy path bypasses ParseStatementsInternal → InitialCleanup, so clear leftover query state - // (interrupt flag, etc.) ourselves. - InitialCleanup(*lock); + // The lazy path drives its own parse iterator, so it clears leftover query state (interrupt flag, etc.) itself + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } auto &profiler = QueryProfiler::Get(*this); profiler.StartQuery(query); // ParseIterator's constructor runs UTF-8 validation / Unicode-space strip and can throw — route @@ -835,11 +869,10 @@ unique_ptr ClientContext::Query(const string &query, QueryParameter ErrorData(InvalidInputException("Cannot prepare multiple statements at once!")), query); } if (statement) { - auto parameters = query_parameters; - // Every statement of an eager query completes before the call returns, so none of them - // leaves a producer parked for the consumer's choice - parameters.result_eagerness = ResultEagerness::FORCED; - auto current_result = SubmitInternal(*lock, std::move(statement), parameters); + // Every statement completes before the call returns, so none of them leaves a producer parked for the + // consumer's choice + auto current_result = + SubmitInternal(*lock, std::move(statement), query_parameters, true, Retention::RETAINED_AT_SUBMISSION); auto has_result = current_result->GetStatementProperties().return_type == StatementReturnType::QUERY_RESULT; if (!current_result->HasError()) { current_result = CompleteInternal(*lock, std::move(current_result)); @@ -883,7 +916,9 @@ unique_ptr ClientContext::Query(unique_ptr statement, unique_ptr ClientContext::Submit(const string &query, const QueryParameters ¶meters) { auto lock = LockContext(); try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } auto statements = ParseStatementsInternal(*lock, query); if (statements.empty()) { @@ -904,7 +939,9 @@ unique_ptr ClientContext::Submit(const string &query, const QueryPa unique_ptr ClientContext::Submit(unique_ptr statement, const QueryParameters ¶meters) { auto lock = LockContext(); try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } return SubmitInternal(*lock, std::move(statement), parameters, true); } catch (std::exception &ex) { @@ -937,20 +974,22 @@ unique_ptr ClientContext::RunInternalStatement(unique_ptr(*lock)) { + return refusal; + } } catch (std::exception &ex) { return ErrorResult(ErrorData(ex), statement->query); } - QueryParameters params = parameters; - params.result_eagerness = ResultEagerness::FORCED; - return RunStatementInternal(*lock, std::move(statement), params, false); + return RunStatementInternal(*lock, std::move(statement), parameters, false); } unique_ptr ClientContext::SubmitInternalStatement(unique_ptr statement, const QueryParameters ¶meters) { auto lock = LockContext(); try { - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } } catch (std::exception &ex) { return ErrorResult(ErrorData(ex), statement->query); } @@ -958,7 +997,8 @@ unique_ptr ClientContext::SubmitInternalStatement(unique_ptr ClientContext::SubmitInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, bool verify) { + const QueryParameters ¶meters, bool verify, + Retention retention) { if (verify) { try { StatementVerification(lock, statement, parameters); @@ -967,7 +1007,7 @@ unique_ptr ClientContext::SubmitInternal(ClientContextLock &lock, u return ErrorResult(ErrorData(ex), statement->query); } } - return SubmitStatement(lock, std::move(statement), parameters); + return SubmitStatement(lock, std::move(statement), parameters, retention); } void ClientContext::Interrupt() { @@ -1006,7 +1046,7 @@ void ClientContext::InterruptCheck() const { void ClientContext::CancelTransaction() { auto lock = LockContext(); - InitialCleanup(*lock); + AbandonActiveQuery(*lock); } void ClientContext::EnableProfiling() { @@ -1097,12 +1137,13 @@ void ClientContext::RunTransactionStatementInternal(const TransactionInfo &info) void ClientContext::RunTransactionStatement(const TransactionInfo &info) { auto lock = LockContext(); - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + refusal->ThrowError(); + } if (is_connected) { auto statement = make_uniq(info.Copy()); statement->query = info.ToString(); QueryParameters parameters; - parameters.result_eagerness = ResultEagerness::FORCED; auto result = RunStatementInternal(*lock, std::move(statement), parameters); if (result->HasError()) { if (transaction.HasActiveTransaction() && transaction.GetAutoRollback()) { @@ -1247,7 +1288,9 @@ unordered_set ClientContext::GetTableNames(const string &query, const bo unique_ptr ClientContext::SubmitInternal(ClientContextLock &lock, const shared_ptr &relation, const QueryParameters &query_parameters) { - InitialCleanup(lock); + if (auto refusal = InitialCleanup(lock)) { + return refusal; + } #ifdef DEBUG // run the ToString method of any relation we run, mostly to ensure it doesn't crash @@ -1268,12 +1311,13 @@ unique_ptr ClientContext::Submit(const shared_ptr &relati unique_ptr ClientContext::Execute(const shared_ptr &relation) { auto lock = LockContext(); auto &expected_columns = relation->Columns(); - InitialCleanup(*lock); + if (auto refusal = InitialCleanup(*lock)) { + return refusal; + } auto relation_stmt = make_uniq(relation); QueryParameters parameters; - parameters.result_eagerness = ResultEagerness::FORCED; - auto result = SubmitInternal(*lock, std::move(relation_stmt), parameters); + auto result = SubmitInternal(*lock, std::move(relation_stmt), parameters, true, Retention::RETAINED_AT_SUBMISSION); if (result->HasError()) { return result; } diff --git a/src/duckdb/src/main/client_context_execution.cpp b/src/duckdb/src/main/client_context_execution.cpp index ec55c60bc..8e2af94c1 100644 --- a/src/duckdb/src/main/client_context_execution.cpp +++ b/src/duckdb/src/main/client_context_execution.cpp @@ -1,4 +1,5 @@ #include "duckdb/main/client_context.hpp" +#include "duckdb/common/operator/add.hpp" #include "duckdb/common/chrono.hpp" #include "duckdb/common/error_data.hpp" @@ -50,9 +51,14 @@ void ClientContext::BeginQueryInternal(ClientContextLock &lock, const SQLStateme // Set query deadline if max_execution_time is configured auto max_execution_time = Settings::Get(*this); if (max_execution_time > 0) { - auto now = steady_clock::now(); - auto deadline_tp = now + milliseconds(max_execution_time); - query_deadline = NumericCast(duration_cast(deadline_tp.time_since_epoch()).count()); + auto now_ms = NumericCast(duration_cast(steady_clock::now().time_since_epoch()).count()); + idx_t deadline; + if (TryAddOperator::Operation(now_ms, NumericCast(max_execution_time), deadline)) { + query_deadline = deadline; + } else { + // the deadline is too far in the future to be represented - there is effectively no deadline + query_deadline.SetInvalid(); + } } else { query_deadline.SetInvalid(); } @@ -193,8 +199,10 @@ void BindPreparedStatementParameters(ClientContext &context, PreparedStatementDa statement.Bind(context, owned_values); } -unique_ptr ClientContext::SubmitPreparedStatementInternal( - ClientContextLock &lock, shared_ptr statement_data_p, const QueryParameters ¶meters) { +unique_ptr +ClientContext::SubmitPreparedStatementInternal(ClientContextLock &lock, + shared_ptr statement_data_p, + const QueryParameters ¶meters, Retention retention) { D_ASSERT(active_query); auto &statement_data = *statement_data_p; BindPreparedStatementParameters(*this, statement_data, parameters); @@ -259,8 +267,7 @@ unique_ptr ClientContext::SubmitPreparedStatementInternal( buffer = make_shared_ptr(*this, ResultLifetime::UNDECIDED, std::move(format_context), parameters.format); } - if (parameters.result_eagerness == ResultEagerness::FORCED || - statement_data.properties.result_eagerness == ResultEagerness::FORCED) { + if (retention == Retention::RETAINED_AT_SUBMISSION) { // Settled before execution starts, so no producer ever parks for the decision buffer->Decide(ResultLifetime::RETAINED); } @@ -303,6 +310,7 @@ unique_ptr ClientContext::CompleteDelegatedInternal(ClientContextLo auto produced = executor.GetResult(); if (executor.HasStreamingResultCollector()) { active_query->SetOpenResult(*produced); + active_query->collector_built_result = true; } else { CleanupInternal(lock, produced.get(), false); } @@ -383,8 +391,7 @@ QueryResultState ClientContext::FailQueryInternal(ClientContextLock &lock, BaseQ return QueryResultState::EXECUTION_ERROR; } -void ClientContext::InitialCleanup(ClientContextLock &lock) { - //! Abandon the query of an open result that no call ended, as Close would, and reset the interrupted flag +void ClientContext::AbandonActiveQuery(ClientContextLock &lock) { if (active_query) { AbortInternal(lock); } diff --git a/src/duckdb/src/main/config.cpp b/src/duckdb/src/main/config.cpp index 2d5338f65..66f3ed20b 100644 --- a/src/duckdb/src/main/config.cpp +++ b/src/duckdb/src/main/config.cpp @@ -549,6 +549,24 @@ LogicalType DBConfig::ParseLogicalType(const string &type) { return type_id; } +ExtensionOptionInfo::~ExtensionOptionInfo() { +} + +SetScope ExtensionOption::ResolveScope(const Identifier &name, SetScope scope) const { + if (scope == SetScope::AUTOMATIC) { + scope = default_scope; + } + if (scope == SetScope::GLOBAL && !supports_global) { + throw InvalidInputException( + "Setting \"%s\" cannot be set as a global variable - it can only be set per session", name); + } + if (scope == SetScope::SESSION && !supports_session) { + throw InvalidInputException("Setting \"%s\" cannot be set as a session variable - it can only be set globally", + name); + } + return scope; +} + bool DBConfig::HasExtensionOption(const Identifier &name) const { return user_settings.HasExtensionOption(name); } @@ -560,8 +578,12 @@ bool DBConfig::TryGetExtensionOption(const Identifier &name, ExtensionOption &re void DBConfig::AddExtensionOption(const Identifier &name, string description, LogicalType parameter, const Value &default_value, set_option_callback_t function, SetScope default_scope, bool is_debug, bool is_deprecated) { - ExtensionOption extension_option(std::move(description), std::move(parameter), function, default_value, - default_scope, is_debug, is_deprecated); + AddExtensionOption(name, ExtensionOption(std::move(description), std::move(parameter), function, default_value, + default_scope, is_debug, is_deprecated)); +} + +void DBConfig::AddExtensionOption(const Identifier &name, ExtensionOption extension_option) { + auto default_value = extension_option.default_value; auto setting_index = user_settings.AddExtensionOption(name, std::move(extension_option)); // copy over unrecognized options, if they match the new extension option auto iter = options.unrecognized_options.find(name); @@ -719,10 +741,10 @@ idx_t DBConfig::GetSystemAvailableMemory(FileSystem &fs) { return available_memory; } -idx_t DBConfig::ParseMemoryLimit(const string &arg) { +optional_idx DBConfig::ParseMemoryLimit(const string &arg) { if (arg[0] == '-' || arg == "null" || arg == "none") { - // infinite - return NumericLimits::Maximum(); + // unlimited + return optional_idx(); } idx_t result; @@ -730,15 +752,21 @@ idx_t DBConfig::ParseMemoryLimit(const string &arg) { if (!error.empty()) { if (error == "Memory cannot be negative") { - result = DConstants::INVALID_INDEX; - } else { - throw ParserException(error); + return optional_idx(); } + throw ParserException(error); + } + if (result == DConstants::INVALID_INDEX) { + return optional_idx(); } - return result; } +idx_t DBConfig::ParseMemoryLimitOrMaximum(const string &arg) { + auto limit = ParseMemoryLimit(arg); + return limit.IsValid() ? limit.GetIndex() : NumericLimits::Maximum(); +} + optional_idx DBConfig::ParseMemoryLimitSlurm(const string &arg) { if (arg.empty()) { return optional_idx(); diff --git a/src/duckdb/src/main/connection.cpp b/src/duckdb/src/main/connection.cpp index a41a3fd5b..9ef64e0d3 100644 --- a/src/duckdb/src/main/connection.cpp +++ b/src/duckdb/src/main/connection.cpp @@ -20,8 +20,12 @@ namespace duckdb { -Connection::Connection(DatabaseInstance &database) +Connection::Connection(DatabaseInstance &database) : Connection(database, ConnectionType::USER) { +} + +Connection::Connection(DatabaseInstance &database, ConnectionType connection_type) : context(make_shared_ptr(database.shared_from_this())) { + context->connection_type = connection_type; auto &connection_manager = ConnectionManager::Get(database); connection_manager.AssignConnectionId(*this); connection_manager.AddConnection(*context); @@ -30,6 +34,10 @@ Connection::Connection(DatabaseInstance &database) Connection::Connection(DuckDB &database) : Connection(*database.instance) { } +Connection Connection::CreateInternal(DatabaseInstance &database) { + return Connection(database, ConnectionType::INTERNAL); +} + Connection::Connection(Connection &&other) noexcept { std::swap(context, other.context); std::swap(connection_id, other.connection_id); @@ -151,7 +159,6 @@ unique_ptr Connection::QueryParamsRecursive(const string &query, ve auto named_params = ConvertParamListToMap(values); QueryParameters parameters; parameters.statement_args = named_params; - parameters.result_eagerness = ResultEagerness::FORCED; return context->Query(query, parameters); } diff --git a/src/duckdb/src/main/database.cpp b/src/duckdb/src/main/database.cpp index 525c0d4df..6817c5f23 100644 --- a/src/duckdb/src/main/database.cpp +++ b/src/duckdb/src/main/database.cpp @@ -287,7 +287,7 @@ void DatabaseInstance::LoadExtensionSettings() { // if the extension provided the option, it should no longer be unrecognized. D_ASSERT(config.options.unrecognized_options.find(name) == config.options.unrecognized_options.end()); auto &context = *con.context; - PhysicalSet::SetExtensionVariable(context, extension_option, SetScope::GLOBAL, value); + PhysicalSet::SetExtensionVariable(context, name, extension_option, SetScope::GLOBAL, value); } con.Commit(); diff --git a/src/duckdb/src/main/extension/extension_install_dynamic.cpp b/src/duckdb/src/main/extension/extension_install_dynamic.cpp index 58252fc07..f5192d2b2 100644 --- a/src/duckdb/src/main/extension/extension_install_dynamic.cpp +++ b/src/duckdb/src/main/extension/extension_install_dynamic.cpp @@ -201,9 +201,40 @@ static unique_ptr DirectInstallExtension(DatabaseInstance return make_uniq(info); } +//! Throws if the url of an extension download is not allowed by the file access settings +static void VerifyCanDownloadExtension(DatabaseInstance &db, const string &url) { + if (Settings::Get(db) || ExtensionRepository::IsOfficialRepositoryUrl(url)) { + return; + } + // urls are matched against allowed_directories by prefix without being normalized, so reject dot segments + // (also percent-encoded) that the server could resolve to a location outside of the allowed directory + auto lower_url = StringUtil::Lower(url); + for (auto &segment : StringUtil::Split(lower_url, '/')) { + if (segment == "." || segment == ".." || StringUtil::Contains(segment, "%2e")) { + throw PermissionException("Cannot download extension from \"%s\" - urls with dot segments are not " + "allowed when external access is disabled", + url); + } + } + if (db.config.CanAccessFile(url, FileType::FILE_TYPE_REGULAR)) { + return; + } + // with httpfs loaded http repositories are bumped to https: allow them if the http url is allowed + if (StringUtil::StartsWith(url, "https://") && + db.config.CanAccessFile("http://" + url.substr(8), FileType::FILE_TYPE_REGULAR)) { + return; + } + throw PermissionException("Cannot download extension from \"%s\" - external access is disabled by " + "configuration. Add the repository to allowed_directories to allow it", + url); +} + //! Perform the GET request for an extension against a single url static unique_ptr RequestExtension(DatabaseInstance &db, optional_ptr context, const string &url, const HTTPHeaders &headers) { + // the official repositories are reachable even when external access is disabled, any other url must be allowed + // through allowed_directories or allowed_paths + VerifyCanDownloadExtension(db, url); auto &manager = db.config.GetHTTPTransportManager(); auto session = context ? manager.CreateSession(*context, url) : manager.CreateSession(db, url); auto ¶ms = session.Parameters(); @@ -212,6 +243,8 @@ static unique_ptr RequestExtension(DatabaseInstance &db, optional_ // to avoid lengthy retry on 304 params.follow_location = false; params.keep_alive = false; + // the url was checked above, which also allows the official repositories, so skip the central check + params.skip_external_access_check = true; GetRequestInfo get_request(url, headers, params, nullptr, nullptr); get_request.try_request = true; diff --git a/src/duckdb/src/main/extension/extension_loader.cpp b/src/duckdb/src/main/extension/extension_loader.cpp index e4d21a2fc..44593e888 100644 --- a/src/duckdb/src/main/extension/extension_loader.cpp +++ b/src/duckdb/src/main/extension/extension_loader.cpp @@ -239,10 +239,16 @@ void ExtensionLoader::RegisterFunction(PragmaFunction function) { void ExtensionLoader::RegisterFunction(PragmaFunctionSet function) { D_ASSERT(!function.name.empty()); - CreatePragmaFunctionInfo info(std::move(function)); + RegisterFunction(CreatePragmaFunctionInfo(std::move(function))); +} + +void ExtensionLoader::RegisterFunction(CreatePragmaFunctionInfo info) { + D_ASSERT(!info.functions.name.empty()); info.extension_name = GetRegisteredExtensionName(); - info.SetQualifiedName( - QualifiedName(info.GetQualifiedName().Catalog(), loader_info.extension_schema, info.GetQualifiedName().Name())); + if (info.GetQualifiedName().Schema() == DEFAULT_SCHEMA) { + info.SetQualifiedName(QualifiedName(info.GetQualifiedName().Catalog(), loader_info.extension_schema, + info.GetQualifiedName().Name())); + } auto &system_catalog = Catalog::GetSystemCatalog(db); auto data = CatalogTransaction::GetSystemTransaction(db); system_catalog.CreatePragmaFunction(data, info); diff --git a/src/duckdb/src/main/extension_install_info.cpp b/src/duckdb/src/main/extension_install_info.cpp index 1a6d43fce..62a1a26c7 100644 --- a/src/duckdb/src/main/extension_install_info.cpp +++ b/src/duckdb/src/main/extension_install_info.cpp @@ -1,5 +1,6 @@ #include "duckdb/main/extension_install_info.hpp" #include "duckdb/common/string.hpp" +#include "duckdb/common/string_util.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/common/serializer/buffered_file_reader.hpp" @@ -78,6 +79,29 @@ string ExtensionRepository::TryGetBackupRepositoryUrl(const string &url) { return ""; } +bool ExtensionRepository::IsOfficialRepositoryUrl(const string &url) { + static constexpr const char *OFFICIAL_REPOSITORY_URLS[] = {CORE_REPOSITORY_URL, CORE_NIGHTLY_REPOSITORY_URL, + COMMUNITY_REPOSITORY_URL, CORE_BACKUP_REPOSITORY_URL, + COMMUNITY_BACKUP_REPOSITORY_URL}; + // strip the scheme: the official repositories are served over both http and https + string host_and_path; + if (StringUtil::StartsWith(url, "http://")) { + host_and_path = url.substr(7); + } else if (StringUtil::StartsWith(url, "https://")) { + host_and_path = url.substr(8); + } else { + return false; + } + for (auto repository_url : OFFICIAL_REPOSITORY_URLS) { + string repository_host = string(repository_url).substr(7); + // the host must match exactly, so that e.g. "extensions.duckdb.org.example.com" does not match + if (host_and_path == repository_host || StringUtil::StartsWith(host_and_path, repository_host + "/")) { + return true; + } + } + return false; +} + ExtensionRepository ExtensionRepository::GetDefaultRepository(optional_ptr config) { if (config) { auto custom_extension_repo = Settings::Get(*config); diff --git a/src/duckdb/src/main/external_resource_type_registry.cpp b/src/duckdb/src/main/external_resource_type_registry.cpp index 03c18d94f..c4175f712 100644 --- a/src/duckdb/src/main/external_resource_type_registry.cpp +++ b/src/duckdb/src/main/external_resource_type_registry.cpp @@ -1,6 +1,5 @@ #include "duckdb/main/external_resource_type_registry.hpp" -#include "duckdb/common/error_data.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/main/client_context.hpp" #include "duckdb/main/database.hpp" @@ -20,23 +19,10 @@ ExternalResourceTypeRegistry &ExternalResourceTypeRegistry::Get(ClientContext &c //! name here - the registry is append-only, and a bad entry would squat its name until restart. static void ValidateFunctionName(const ExternalResourceType &type, const string &key, const string &value) { QualifiedName qname; - try { - qname = QualifiedName::Parse(value); - } catch (const std::exception &ex) { - ErrorData error(ex); + string error; + if (!QualifiedName::TryParse(value, qname, error)) { throw InvalidInputException("external resource type \"%s\": '%s' is not a valid function name: %s", type.name, - key, error.RawMessage()); - } - if (qname.Name().empty()) { - throw InvalidInputException("external resource type \"%s\": '%s' is not a valid function name: \"%s\"", - type.name, key, value); - } - for (auto &component : qname.Path()) { - if (component.empty()) { - throw InvalidInputException( - "external resource type \"%s\": '%s' has an empty catalog or schema component: \"%s\"", type.name, key, - value); - } + key, error); } } diff --git a/src/duckdb/src/main/http/http_transport_manager.cpp b/src/duckdb/src/main/http/http_transport_manager.cpp index 4205a688d..ac7658fc5 100644 --- a/src/duckdb/src/main/http/http_transport_manager.cpp +++ b/src/duckdb/src/main/http/http_transport_manager.cpp @@ -204,7 +204,7 @@ HTTPTransportManager::HTTPTransportManager(const shared_ptr &initial_h } auto reuse_policy = initial_http_util->GetTransportReusePolicy(); ValidateReusePolicy(reuse_policy); - providers.push_back(make_uniq(initial_http_util, reuse_policy, 0)); + providers.push_back(make_uniq(initial_http_util, reuse_policy, idx_t(0))); } HTTPTransportManager::~HTTPTransportManager() { diff --git a/src/duckdb/src/main/http/http_util.cpp b/src/duckdb/src/main/http/http_util.cpp index 573e0a070..cc7896ca2 100644 --- a/src/duckdb/src/main/http/http_util.cpp +++ b/src/duckdb/src/main/http/http_util.cpp @@ -151,6 +151,15 @@ unique_ptr HTTPUtil::Request(BaseRequest &request) { } unique_ptr HTTPUtil::Request(BaseRequest &request, unique_ptr &client) { + if (request.params.db && !request.params.skip_external_access_check) { + // HTTP requests access an external resource - check this is allowed by the file access settings. This is the + // central check for requests that do not go through the file system (e.g. the REST catalog calls of iceberg). + auto &config = request.params.db->config; + if (!config.CanAccessFile(request.url, FileType::FILE_TYPE_REGULAR)) { + throw PermissionException("Cannot access \"%s\" - file system operations are disabled by configuration", + request.url); + } + } auto response = SendRequest(request, client); if (!response) { auto method = EnumUtil::ToString(request.type); diff --git a/src/duckdb/src/main/query_profiler.cpp b/src/duckdb/src/main/query_profiler.cpp index da078097c..4325e7b62 100644 --- a/src/duckdb/src/main/query_profiler.cpp +++ b/src/duckdb/src/main/query_profiler.cpp @@ -542,6 +542,9 @@ void OperatorProfiler::Flush(const PhysicalOperator &phys_op) { } void QueryProfiler::Flush(OperatorProfiler &profiler) { + if (!running.load(std::memory_order_relaxed)) { + return; + } lock_guard guard(lock); if (!IsEnabled() || !running) { return; @@ -574,6 +577,9 @@ void QueryProfiler::Flush(OperatorProfiler &profiler) { } void QueryProfiler::SetBlockedTime(const double &blocked_thread_time) { + if (!running.load(std::memory_order_relaxed)) { + return; + } lock_guard guard(lock); if (!IsEnabled() || !running) { return; diff --git a/src/duckdb/src/main/query_result.cpp b/src/duckdb/src/main/query_result.cpp index 81b769959..46f5a5ff9 100644 --- a/src/duckdb/src/main/query_result.cpp +++ b/src/duckdb/src/main/query_result.cpp @@ -208,9 +208,8 @@ bool QueryResult::IsOpen() { QueryResultState QueryResult::Cancelled() { if (!HasError()) { - SetError(ErrorData(ExceptionType::INTERRUPT, - "The execution of the query was cancelled before it could finish, likely caused by " - "executing a different query")); + SetError( + ErrorData(ExceptionType::INTERRUPT, "The execution of the query was cancelled before it could finish")); } return QueryResultState::EXECUTION_ERROR; } diff --git a/src/duckdb/src/main/query_result_stream.cpp b/src/duckdb/src/main/query_result_stream.cpp index 5a5652607..ccc52bb6e 100644 --- a/src/duckdb/src/main/query_result_stream.cpp +++ b/src/duckdb/src/main/query_result_stream.cpp @@ -7,6 +7,14 @@ namespace duckdb { +bool StreamsByDefault(StatementType written_type, const StatementProperties &properties, bool reads_to_the_end) { + if (written_type != StatementType::SELECT_STATEMENT || + properties.return_type != StatementReturnType::QUERY_RESULT) { + return false; + } + return reads_to_the_end || properties.IsReadOnly(); +} + ResultStreamBase::ResultStreamBase(unique_ptr result, const char *expected_format) : handle(std::move(result)) { if (!handle) { @@ -16,11 +24,6 @@ ResultStreamBase::ResultStreamBase(unique_ptr result, const char *e throw InvalidInputException("Attempting to open a stream on an unsuccessful query result\nError: %s", handle->GetError()); } - if (handle->GetStatementProperties().result_eagerness == ResultEagerness::FORCED) { - throw InvalidInputException( - "Attempting to open a stream on a %s statement, which completes before its result is returned", - StatementTypeToString(handle->GetStatementType())); - } if (!handle->HasBufferedData()) { throw InvalidInputException("Attempting to open a stream on a query result that has no streaming buffer"); } diff --git a/src/duckdb/src/main/settings/custom_settings.cpp b/src/duckdb/src/main/settings/custom_settings.cpp index 64cfd0920..cf9449843 100644 --- a/src/duckdb/src/main/settings/custom_settings.cpp +++ b/src/duckdb/src/main/settings/custom_settings.cpp @@ -78,7 +78,7 @@ static DatabaseInstance &GetDB(DatabaseInstance *db) { template static idx_t ParseMemoryLimitOrPercentage(const string &input, BASE &&get_base) { if (input.empty() || input.back() != '%') { - return DBConfig::ParseMemoryLimit(input); + return DBConfig::ParseMemoryLimitOrMaximum(input); } double percentage; if (!TryDoubleCast(input.c_str(), input.size() - 1, percentage, false) || percentage < 0 || percentage > 100) { @@ -118,6 +118,9 @@ bool AccessModeSetting::OnGlobalSet(DatabaseInstance *db, DBConfig &config, cons // Allocator Background Threads //===----------------------------------------------------------------------===// void AllocatorBackgroundThreadsSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllocatorBackgroundThreadsSetting::Name); + } Allocator::SetBackgroundThreads(input.GetValue()); } @@ -126,7 +129,7 @@ void AllocatorBackgroundThreadsSetting::OnSet(SettingCallbackInfo &info, Value & //===----------------------------------------------------------------------===// void AllocatorBulkDeallocationFlushThresholdSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { - config.options.allocator_bulk_deallocation_flush_threshold = DBConfig::ParseMemoryLimit(input.ToString()); + config.options.allocator_bulk_deallocation_flush_threshold = DBConfig::ParseMemoryLimitOrMaximum(input.ToString()); if (db) { BufferManager::GetBufferManager(*db).GetBufferPool().SetAllocatorBulkDeallocationFlushThreshold( config.options.allocator_bulk_deallocation_flush_threshold); @@ -175,6 +178,9 @@ void AllocatorFlushThresholdSetting::OnSet(SettingCallbackInfo &info, Value &inp // Allow Community Extensions //===----------------------------------------------------------------------===// void AllowCommunityExtensionsSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowCommunityExtensionsSetting::Name); + } if (info.db && input.GetValue()) { throw InvalidInputException("Cannot change allow_community_extensions setting while database is running"); } @@ -197,6 +203,9 @@ static int ExtensionRepositoryAccessLevel(ExtensionRepositoryAccess access) { } void AllowExtensionRepositoriesSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowExtensionRepositoriesSetting::Name); + } // validate the value auto new_access = ExtensionRepositoryManager::ParseAccess(StringValue::Get(input)); if (!info.db) { @@ -233,6 +242,9 @@ void ExtensionRepositoryDirectorySetting::OnSet(SettingCallbackInfo &info, Value // Allow Persistent Secrets //===----------------------------------------------------------------------===// void AllowPersistentSecretsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowPersistentSecretsSetting::Name); + } auto value = input.DefaultCastAs(LogicalType::BOOLEAN); config.secret_manager->SetEnablePersistentSecrets(value.GetValue()); } @@ -250,6 +262,9 @@ Value AllowPersistentSecretsSetting::GetSetting(const ClientContext &context) { // Allow Unredacted Secrets //===----------------------------------------------------------------------===// void AllowUnredactedSecretsSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowUnredactedSecretsSetting::Name); + } if ((info.db || info.context) && input.GetValue()) { throw InvalidInputException("Cannot change allow_unredacted_secrets setting while database is running"); } @@ -268,6 +283,9 @@ void DisableDatabaseInvalidationSetting::OnSet(SettingCallbackInfo &info, Value // Allow Unsigned Extensions //===----------------------------------------------------------------------===// void AllowUnsignedExtensionsSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowUnsignedExtensionsSetting::Name); + } if (info.db && input.GetValue()) { throw InvalidInputException("Cannot change allow_unsigned_extensions setting while database is running"); } @@ -277,6 +295,9 @@ void AllowUnsignedExtensionsSetting::OnSet(SettingCallbackInfo &info, Value &inp // Allowed Configs //===----------------------------------------------------------------------===// void AllowedConfigsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowedConfigsSetting::Name); + } config.options.allowed_configs.clear(); auto &list = ListValue::GetChildren(input); for (auto &val : list) { @@ -301,6 +322,9 @@ Value AllowedConfigsSetting::GetSetting(const ClientContext &context) { // Allowed Directories //===----------------------------------------------------------------------===// void AllowedDirectoriesSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowedDirectoriesSetting::Name); + } if (!Settings::Get(config)) { throw InvalidInputException("Cannot change allowed_directories when enable_external_access is disabled"); } @@ -334,6 +358,9 @@ Value AllowedDirectoriesSetting::GetSetting(const ClientContext &context) { // Allowed Paths //===----------------------------------------------------------------------===//void void AllowedPathsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", AllowedPathsSetting::Name); + } if (!Settings::Get(config)) { throw InvalidInputException("Cannot change allowed_paths when enable_external_access is disabled"); } @@ -392,7 +419,7 @@ Value BlockAllocatorMemorySetting::GetSetting(const ClientContext &context) { // Checkpoint Threshold //===----------------------------------------------------------------------===// void CheckpointThresholdSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { - idx_t new_limit = DBConfig::ParseMemoryLimit(input.ToString()); + idx_t new_limit = DBConfig::ParseMemoryLimitOrMaximum(input.ToString()); config.options.checkpoint_wal_size = new_limit; } @@ -440,6 +467,9 @@ Value DebugVerificationModeSetting::GetSetting(const ClientContext &context) { // Default Block Size //===----------------------------------------------------------------------===// void DefaultBlockSizeSetting::OnSet(SettingCallbackInfo &, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", DefaultBlockSizeSetting::Name); + } auto block_alloc_size = input.GetValue(); Storage::VerifyBlockAllocSize(block_alloc_size); } @@ -630,6 +660,9 @@ void VacuumRebuildIndexesSetting::OnSet(SettingCallbackInfo &info, Value &input) // Enable External Access //===----------------------------------------------------------------------===// void EnableExternalAccessSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", EnableExternalAccessSetting::Name); + } if (!info.db) { return; } @@ -658,6 +691,9 @@ void EnableExternalAccessSetting::OnSet(SettingCallbackInfo &info, Value &input) // Enable External File Cache //===----------------------------------------------------------------------===// void EnableExternalFileCacheSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", EnableExternalFileCacheSetting::Name); + } if (info.db) { ExternalFileCache::Get(*info.db).SetEnabled(input.GetValue()); } @@ -678,14 +714,23 @@ static void ValidateExternalFileCacheBlockSize(const char *name, const Value &in } void ExternalFileCacheLocalMaxBlockSizeSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", ExternalFileCacheLocalMaxBlockSizeSetting::Name); + } ValidateExternalFileCacheBlockSize(Name, input); } void ExternalFileCacheRemoteMaxBlockSizeSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", ExternalFileCacheRemoteMaxBlockSizeSetting::Name); + } ValidateExternalFileCacheBlockSize(Name, input); } void ExternalFileCacheRemoteMinBlockSizeSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", ExternalFileCacheRemoteMinBlockSizeSetting::Name); + } ValidateExternalFileCacheBlockSize(Name, input); } @@ -696,6 +741,9 @@ Value EnableLogging::GetSetting(const ClientContext &context) { return context.db->GetLogManager().GetConfig().enabled; } void EnableLogging::SetGlobal(DatabaseInstance *db_p, DBConfig &config, const Value ¶meter) { + if (parameter.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", EnableLogging::Name); + } auto &db = GetDB(db_p); db.GetLogManager().SetEnableLogging(parameter.GetValue()); } @@ -789,6 +837,9 @@ Value ForceVariantShredding::GetSetting(const ClientContext &context) { // Extension Directory //===----------------------------------------------------------------------===// void ExtensionDirectoriesSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", ExtensionDirectoriesSetting::Name); + } config.options.extension_directories.clear(); auto &list = ListValue::GetChildren(input); @@ -1112,6 +1163,9 @@ void HTTPProxySetting::ResetGlobal(DatabaseInstance *, DBConfig &config) { // Index Scan Percentage //===----------------------------------------------------------------------===// void IndexScanPercentageSetting::OnSet(SettingCallbackInfo &, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", IndexScanPercentageSetting::Name); + } auto index_scan_percentage = input.GetValue(); if (index_scan_percentage < 0 || index_scan_percentage > 1.0) { throw InvalidInputException("the index scan percentage must be within [0, 1]"); @@ -1122,6 +1176,9 @@ void IndexScanPercentageSetting::OnSet(SettingCallbackInfo &, Value &input) { // Initial Column Segment Size //===----------------------------------------------------------------------===// void InitialColumnSegmentSizeSetting::OnSet(SettingCallbackInfo &, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", InitialColumnSegmentSizeSetting::Name); + } auto initial_column_segment_size = input.GetValue(); if (initial_column_segment_size < DEFAULT_BLOCK_HEADER_STORAGE_SIZE) { throw InvalidInputException( @@ -1183,11 +1240,14 @@ Value MaxMemorySetting::GetSetting(const ClientContext &context) { // Max Temp Directory Size //===----------------------------------------------------------------------===// void MaxTempDirectorySizeSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", MaxTempDirectorySizeSetting::Name); + } if (input == "90% of available disk space") { ResetGlobal(db, config); return; } - auto maximum_swap_space = DBConfig::ParseMemoryLimit(input.ToString()); + auto maximum_swap_space = DBConfig::ParseMemoryLimitOrMaximum(input.ToString()); if (maximum_swap_space == DConstants::INVALID_INDEX) { // We use INVALID_INDEX to indicate that the value is not set by the user // use one lower to indicate 'unlimited' @@ -1236,6 +1296,7 @@ void OperatorMemoryLimitSetting::SetLocal(ClientContext &context, const Value &i if (input.IsNull()) { config.operator_memory_limit.SetInvalid(); } else { + // an unlimited size is the same as no operator memory limit config.operator_memory_limit = DBConfig::ParseMemoryLimit(input.ToString()); } } @@ -1441,6 +1502,9 @@ Value ProfilingCoverageSetting::GetSetting(const ClientContext &context) { // Progress Bar Time //===----------------------------------------------------------------------===// void ProgressBarTimeSetting::SetLocal(ClientContext &context, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", ProgressBarTimeSetting::Name); + } auto &config = ClientConfig::GetConfig(context); ProgressBar::SystemOverrideCheck(config); config.wait_time = input.GetValue(); @@ -1567,7 +1631,7 @@ Value StandardVectorSizeSetting::GetSetting(const ClientContext &) { //===----------------------------------------------------------------------===// void MaxStreamingBufferSizeSetting::SetLocal(ClientContext &context, const Value &input) { auto &config = ClientConfig::GetConfig(context); - config.max_streaming_buffer_size = DBConfig::ParseMemoryLimit(input.ToString()); + config.max_streaming_buffer_size = DBConfig::ParseMemoryLimitOrMaximum(input.ToString()); } void MaxStreamingBufferSizeSetting::ResetLocal(ClientContext &context) { @@ -1630,6 +1694,9 @@ void TempFileEncryptionSetting::OnSet(SettingCallbackInfo &info, Value &input) { // Tracked Metrics //===----------------------------------------------------------------------===// void TrackedMetricsSetting::SetLocal(ClientContext &context, const Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", TrackedMetricsSetting::Name); + } auto &config = ClientConfig::GetConfig(context); config.tracked_metrics.clear(); if (input.type() == LogicalType::LIST(LogicalType::VARCHAR)) { @@ -1759,6 +1826,9 @@ Value HTTPClientPoolCapacitySetting::GetSetting(const ClientContext &context) { //===----------------------------------------------------------------------===// void WarningsAsErrorsSetting::OnSet(SettingCallbackInfo &info, Value &input) { + if (input.IsNull()) { + throw InvalidInputException("%s setting cannot be NULL", WarningsAsErrorsSetting::Name); + } auto &log_manager = LogManager::Get(*info.context); if (input == Value(true) && !log_manager.GetConfig().enabled) { throw Exception( @@ -1774,6 +1844,7 @@ void WriteBufferRowGroupMemoryLimitSetting::SetGlobal(DatabaseInstance *db, DBCo if (input.IsNull() || input.ToString().empty()) { config.options.write_buffer_row_group_memory_limit = optional_idx(); } else { + // an unlimited size means that there is no limit config.options.write_buffer_row_group_memory_limit = DBConfig::ParseMemoryLimit(input.ToString()); } } diff --git a/src/duckdb/src/optimizer/aggregate_function_rewriter.cpp b/src/duckdb/src/optimizer/aggregate_function_rewriter.cpp index 9a2a6985a..93b24c9b3 100644 --- a/src/duckdb/src/optimizer/aggregate_function_rewriter.cpp +++ b/src/duckdb/src/optimizer/aggregate_function_rewriter.cpp @@ -14,6 +14,7 @@ #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" +#include "duckdb/planner/expression_nullability.hpp" #include "duckdb/planner/operator/logical_aggregate.hpp" #include "duckdb/planner/operator/logical_projection.hpp" @@ -45,6 +46,9 @@ class AggregateRewriteRule { public: virtual bool ShouldSkip(const LogicalAggregate &aggr) const = 0; + virtual bool ShouldRewrite(const LogicalAggregate &aggr, Expression &expr) { + return true; + } virtual unique_ptr Rewrite(unique_ptr &expr, vector> &bindings, vector> &additional_expressions) = 0; virtual unique_ptr @@ -66,7 +70,8 @@ static unique_ptr GetSmallIntegerTypesMatcher() { //! AVG(x) -> SUM(x) / COUNT(x) class AvgRewriteRule : public AggregateRewriteRule { public: - explicit AvgRewriteRule(Optimizer &optimizer) : AggregateRewriteRule(optimizer) { + AvgRewriteRule(Optimizer &optimizer, LogicalOperator &root) + : AggregateRewriteRule(optimizer), nullability(optimizer.context, root) { auto op = make_uniq(); op->function = make_uniq("avg"); op->type = make_uniq(); @@ -75,20 +80,68 @@ class AvgRewriteRule : public AggregateRewriteRule { child_matcher->type = make_uniq(); op->matchers.push_back(std::move(child_matcher)); matcher = std::move(op); + + auto count = make_uniq(); + count->function = make_uniq(identifier_set_t {"count", "count_star"}); + count->policy = SetMatcher::Policy::SOME; + count_matcher = std::move(count); } public: bool ShouldSkip(const LogicalAggregate &aggr) const override { - // AVG -> SUM/COUNT is correct under any grouping (both ignore NULLs - // identically), so ROLLUP/CUBE/GROUPING SETS are safe to rewrite. - // Decomposing here is also the prerequisite for partial-aggregate - // pushdown to fire on AVG queries. The remaining guard is for - // grouping_functions: their per-row grouping-set bitmasks reference - // the original AVG column directly, and the rewrite has no way to - // translate those references. return !aggr.grouping_functions.empty(); } + bool ShouldRewrite(const LogicalAggregate &aggr, Expression &expr) override { + if (aggr.groups.empty()) { + return true; + } + if (optimizer.OptimizerDisabled(OptimizerType::COMMON_AGGREGATE)) { + return false; + } + auto &child = *expr.Cast().GetChildren()[0]; + FunctionBinder binder(optimizer.context); + vector> sum_args; + sum_args.push_back(child.Copy()); + auto sum = binder.BindAggregateFunction( + GetBuiltinAggregateFunction(optimizer.context, "sum", {child.GetReturnType()}), std::move(sum_args)); + vector> count_args; + count_args.push_back(child.Copy()); + auto count = binder.BindAggregateFunction( + GetBuiltinAggregateFunction(optimizer.context, "count", {child.GetReturnType()}), std::move(count_args)); + + D_ASSERT(aggr.children.size() == 1); + auto is_not_null = [&](const Expression &input) { + if (input.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { + return !input.Cast().GetValue().IsNull(); + } + return nullability.IsNotNull(*aggr.children[0], input); + }; + const auto can_share_count = + !optimizer.OptimizerDisabled(OptimizerType::STATISTICS_PROPAGATION) && is_not_null(child); + for (auto &other : aggr.expressions) { + // Identical AVGs already share their native state. + if (expr.Equals(*other)) { + continue; + } + if (sum->Equals(*other) || count->Equals(*other)) { + return true; + } + if (!can_share_count || other->IsVolatile()) { + continue; + } + vector> bindings; + if (!count_matcher->Match(*other, bindings) && !matcher->Match(*other, bindings)) { + continue; + } + auto &children = other->Cast().GetChildren(); + if (children.empty() || (children.size() == 1 && is_not_null(*children[0]))) { + return true; + } + } + return false; + } + unique_ptr Rewrite(unique_ptr &expr, vector> &bindings, vector> &additional_expressions) override { FunctionBinder function_binder(optimizer.context); @@ -111,6 +164,10 @@ class AvgRewriteRule : public AggregateRewriteRule { // SUM(x) / COUNT(x): additional_expressions[0] is the COUNT(x) ref return optimizer.BindScalarFunction("/", std::move(aggr_ref), std::move(additional_expressions[0])); } + +private: + NotNullExpressionAnalyzer nullability; + unique_ptr count_matcher; }; //! SUM(x + C) -> SUM(x) + C * COUNT(x) @@ -346,6 +403,9 @@ class AggregateFunctionRewriterInternal : public LogicalOperatorVisitor { if (!rule.matcher->Match(*expr, bindings)) { continue; } + if (!rule.ShouldRewrite(aggr, *expr)) { + continue; + } RewriteInfo rewrite_info; auto count_arg = rule.Rewrite(expr, bindings, rewrite_info.additional_expressions); @@ -434,9 +494,6 @@ class AggregateFunctionRewriterInternal : public LogicalOperatorVisitor { }; AggregateFunctionRewriter::AggregateFunctionRewriter(Optimizer &optimizer) : optimizer(optimizer) { - rules.push_back(make_uniq(optimizer)); - rules.push_back(make_uniq(optimizer)); - rules.push_back(make_uniq(optimizer)); } AggregateFunctionRewriter::~AggregateFunctionRewriter() { @@ -444,6 +501,10 @@ AggregateFunctionRewriter::~AggregateFunctionRewriter() { void AggregateFunctionRewriter::Optimize(unique_ptr &op) { RewriteAggregateCallbacks(optimizer, op); + vector> rules; + rules.push_back(make_uniq(optimizer, *op)); + rules.push_back(make_uniq(optimizer)); + rules.push_back(make_uniq(optimizer)); // Run each rule as an independent pass so that transformations by an earlier rule affect later rules for (auto &rule : rules) { AggregateFunctionRewriterInternal rewriter(optimizer, *rule); diff --git a/src/duckdb/src/optimizer/build_probe_side_optimizer.cpp b/src/duckdb/src/optimizer/build_probe_side_optimizer.cpp index 8a3f1b6da..cc6a55166 100644 --- a/src/duckdb/src/optimizer/build_probe_side_optimizer.cpp +++ b/src/duckdb/src/optimizer/build_probe_side_optimizer.cpp @@ -218,7 +218,7 @@ double BuildProbeSideOptimizer::GetBuildSize(vector types, const id // This is a not a smooth cost function, so instead we do the average, which is ~3 * sizeof(ht_entry_t) row_width += 3 * sizeof(ht_entry_t); - return static_cast(row_width * cardinality); + return MaxValue(static_cast(row_width) * static_cast(cardinality), sizeof(ht_entry_t)); } idx_t BuildProbeSideOptimizer::ChildHasJoins(LogicalOperator &op) { diff --git a/src/duckdb/src/optimizer/column_binding_replacer.cpp b/src/duckdb/src/optimizer/column_binding_replacer.cpp index 87657ada9..bab6b3010 100644 --- a/src/duckdb/src/optimizer/column_binding_replacer.cpp +++ b/src/duckdb/src/optimizer/column_binding_replacer.cpp @@ -138,7 +138,10 @@ void ColumnBindingReplacer::VisitOperator(LogicalOperator &op) { if (stop_operator && stop_operator.get() == &op) { return; } - VisitOperatorChildren(op); + // Binding replacement keeps child layouts intact, so projection maps are left untouched + for (auto &child : op.children) { + VisitOperator(*child); + } VisitOperatorBindings(op); } diff --git a/src/duckdb/src/optimizer/compressed_materialization.cpp b/src/duckdb/src/optimizer/compressed_materialization.cpp index d8074437f..4f3c71ac6 100644 --- a/src/duckdb/src/optimizer/compressed_materialization.cpp +++ b/src/duckdb/src/optimizer/compressed_materialization.cpp @@ -356,8 +356,22 @@ unique_ptr CMHelper::CreateIntegralFunctionCompress(unique_p // CompressedMaterialization //===--------------------------------------------------------------------===// CompressedMaterialization::CompressedMaterialization(Optimizer &optimizer_p, LogicalOperator &root_p, + const vector>> &ancestors_p, statistics_map_t &statistics_map_p) - : optimizer(optimizer_p), context(optimizer.context), root(&root_p), statistics_map(statistics_map_p) { + : optimizer(optimizer_p), context(optimizer.context), root(&root_p), ancestors(ancestors_p), + statistics_map(statistics_map_p) { +} + +void CompressedMaterialization::RebindAncestors(ColumnBindingReplacer &replacer) { + // Table indexes are unique to a single operator, so references to the replaced bindings + // can only occur in the operators between the compressed operator and the root + for (auto &ancestor : ancestors) { + auto &ancestor_op = ancestor.get(); + if (replacer.stop_operator && replacer.stop_operator.get() == ancestor_op.get()) { + continue; + } + replacer.VisitOperatorBindings(*ancestor_op); + } } void CompressedMaterialization::GetReferencedBindings(const Expression &root_expr, @@ -433,7 +447,7 @@ void CompressedMaterialization::Compress(unique_ptr &op) { return; } - root->ResolveOperatorTypes(); + op->ResolveOperatorTypes(); switch (op->type) { case LogicalOperatorType::LOGICAL_AGGREGATE_AND_GROUP_BY: @@ -541,7 +555,7 @@ void CompressedMaterialization::CreateCompressProjection(unique_ptr CompressedMaterialization::GetCompressExpression(const ColumnBinding &binding, diff --git a/src/duckdb/src/optimizer/compressed_materialization/compress_aggregate.cpp b/src/duckdb/src/optimizer/compressed_materialization/compress_aggregate.cpp index 6671dbda8..abecfeef4 100644 --- a/src/duckdb/src/optimizer/compressed_materialization/compress_aggregate.cpp +++ b/src/duckdb/src/optimizer/compressed_materialization/compress_aggregate.cpp @@ -91,6 +91,21 @@ void CompressedMaterialization::CompressAggregate(unique_ptr &o // Create info for compression CompressedMaterializationInfo info(*op, {0}, referenced_bindings); + // Columns that the aggregate does not read cannot be referenced above it, so leave them uncompressed + column_binding_set_t used_bindings; + for (const auto &group : groups) { + GetReferencedBindings(*group, used_bindings); + } + for (const auto &expr : aggregate.expressions) { + GetReferencedBindings(*expr, used_bindings); + } + auto &child_info = info.child_info[0]; + for (idx_t binding_idx = 0; binding_idx < child_info.bindings_before.size(); binding_idx++) { + if (used_bindings.find(child_info.bindings_before[binding_idx]) == used_bindings.end()) { + child_info.can_compress[binding_idx] = false; + } + } + // Create binding mapping const auto bindings_out = aggregate.GetColumnBindings(); const auto &types = aggregate.types; diff --git a/src/duckdb/src/optimizer/cte_join_filter_collector.cpp b/src/duckdb/src/optimizer/cte_join_filter_collector.cpp index c5f0ce9cd..75810abe3 100644 --- a/src/duckdb/src/optimizer/cte_join_filter_collector.cpp +++ b/src/duckdb/src/optimizer/cte_join_filter_collector.cpp @@ -1,4 +1,5 @@ #include "duckdb/optimizer/cte_join_filter_collector.hpp" +#include "duckdb/common/numeric_utils.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/planner/expression/expression_barrier.hpp" @@ -165,7 +166,7 @@ void CTEJoinFilterCollector::PushFilter(LogicalOperator &op, const LogicalCTERef auto child_bindings = child->GetColumnBindings(); auto child_keys = target_keys; for (auto &key : child_keys) { - auto position = std::find(bindings.begin(), bindings.end(), key) - bindings.begin(); + auto position = NumericCast(std::find(bindings.begin(), bindings.end(), key) - bindings.begin()); key = child_bindings[position]; } PushFilter(*child, source, source_keys, std::move(child_keys), comparisons); diff --git a/src/duckdb/src/optimizer/filter_combiner.cpp b/src/duckdb/src/optimizer/filter_combiner.cpp index 336c20454..696bad84d 100644 --- a/src/duckdb/src/optimizer/filter_combiner.cpp +++ b/src/duckdb/src/optimizer/filter_combiner.cpp @@ -501,6 +501,10 @@ FilterPushdownResult FilterCombiner::TryPushdownPrefixFilter(TableFilterSet &tab } auto &column_ref = func.GetChildren()[0]->Cast(); auto &constant_value_expr = func.GetChildren()[1]->Cast(); + if (constant_value_expr.GetValue().IsNull()) { + // prefix(col, NULL) is NULL and filters out every row - leave the filter in place, do not push it down + return FilterPushdownResult::NO_PUSHDOWN; + } auto prefix_string = StringValue::Get(constant_value_expr.GetValue()); if (prefix_string.empty()) { // empty prefix - skip @@ -620,6 +624,20 @@ FilterPushdownResult FilterCombiner::TryPushdownLikeFilter(TableFilterSet &table return FilterPushdownResult::PUSHED_DOWN_PARTIALLY; } +static bool TryPushdownDenseInFilter(TableFilterSet &table_filters, const BoundColumnRefExpression &column, + vector &values) { + if (values.empty() || !FilterCombiner::IsDenseRange(values)) { + return false; + } + // IsDenseRange sorts the values and only accepts consecutive integers. + auto lower_bound = CreateComparisonExpression(column, ExpressionType::COMPARE_GREATERTHANOREQUALTO, values.front()); + auto upper_bound = + CreateComparisonExpression(column, ExpressionType::COMPARE_LESSTHANOREQUALTO, std::move(values.back())); + table_filters.PushFilter(column.Binding().column_index, make_uniq(std::move(lower_bound))); + table_filters.PushFilter(column.Binding().column_index, make_uniq(std::move(upper_bound))); + return true; +} + FilterPushdownResult FilterCombiner::TryPushdownInFilter(TableFilterSet &table_filters, const vector &column_ids, Expression &expr) { if (expr.GetExpressionType() != ExpressionType::COMPARE_IN) { @@ -671,15 +689,7 @@ FilterPushdownResult FilterCombiner::TryPushdownInFilter(TableFilterSet &table_f D_ASSERT(!const_value_expr.GetValue().IsNull()); in_list.push_back(const_value_expr.GetValue()); } - if (type.IsIntegral() && IsDenseRange(in_list)) { - // dense range! turn this into x >= min AND x <= max - // IsDenseRange sorts in_list, so the front element is the min and the back element is the max - auto lower_bound = CreateComparisonExpression( - *func.GetChildren()[0], ExpressionType::COMPARE_GREATERTHANOREQUALTO, std::move(in_list.front())); - auto upper_bound = CreateComparisonExpression(*func.GetChildren()[0], ExpressionType::COMPARE_LESSTHANOREQUALTO, - std::move(in_list.back())); - table_filters.PushFilter(proj_index, make_uniq(std::move(lower_bound))); - table_filters.PushFilter(proj_index, make_uniq(std::move(upper_bound))); + if (TryPushdownDenseInFilter(table_filters, column_ref, in_list)) { return FilterPushdownResult::PUSHED_DOWN_FULLY; } // if this is not a dense range we can push an optional filter for zone-map pruning @@ -704,6 +714,9 @@ FilterPushdownResult FilterCombiner::TryPushdownOrClause(TableFilterSet &table_f } ProjectionIndex proj_id; LogicalType col_type = LogicalType::INVALID; + optional_ptr equality_column; + vector equality_values; + bool only_equalities = true; for (idx_t i = 0; i < conj.GetChildren().size(); i++) { auto &child = conj.GetChildren()[i]; if (!BoundComparisonExpression::IsComparison(*child)) { @@ -736,6 +749,11 @@ FilterPushdownResult FilterCombiner::TryPushdownOrClause(TableFilterSet &table_f } auto comparison_type = invert ? FlipComparisonExpression(comp.GetExpressionType()) : comp.GetExpressionType(); + only_equalities &= comparison_type == ExpressionType::COMPARE_EQUAL; + if (comparison_type == ExpressionType::COMPARE_EQUAL && !const_val->GetValue().IsNull()) { + equality_column = column_ref; + equality_values.push_back(const_val->GetValue()); + } if (const_val->GetValue().IsNull()) { switch (comparison_type) { case ExpressionType::COMPARE_DISTINCT_FROM: { @@ -760,6 +778,10 @@ FilterPushdownResult FilterCombiner::TryPushdownOrClause(TableFilterSet &table_f CreateComparisonExpression(*column_ref, comparison_type, const_val->GetValue())); } } + if (only_equalities && equality_column && + TryPushdownDenseInFilter(table_filters, *equality_column, equality_values)) { + return FilterPushdownResult::PUSHED_DOWN_FULLY; + } table_filters.PushFilter(proj_id, CreateOptionalExpressionFilter(std::move(conj_filter), col_type)); return FilterPushdownResult::PUSHED_DOWN_PARTIALLY; } diff --git a/src/duckdb/src/optimizer/join_filter_pushdown_optimizer.cpp b/src/duckdb/src/optimizer/join_filter_pushdown_optimizer.cpp index 2f633093b..b9c150533 100644 --- a/src/duckdb/src/optimizer/join_filter_pushdown_optimizer.cpp +++ b/src/duckdb/src/optimizer/join_filter_pushdown_optimizer.cpp @@ -368,6 +368,9 @@ void JoinFilterPushdownOptimizer::GenerateJoinFilters(LogicalComparisonJoin &joi JoinFilterPushdownFilter get_filter; get_filter.dynamic_filters = get.dynamic_filters; get_filter.columns = std::move(target.columns); + if (get.has_estimated_cardinality) { + get_filter.estimated_scan_cardinality = get.estimated_cardinality; + } pushdown_info->probe_info.push_back(std::move(get_filter)); } diff --git a/src/duckdb/src/optimizer/join_order/cardinality_estimator.cpp b/src/duckdb/src/optimizer/join_order/cardinality_estimator.cpp index 4d48fd3d2..9c28fd7b5 100644 --- a/src/duckdb/src/optimizer/join_order/cardinality_estimator.cpp +++ b/src/duckdb/src/optimizer/join_order/cardinality_estimator.cpp @@ -487,6 +487,15 @@ LeftJoinDenomInfo CardinalityEstimator::CalculateLeftJoinDenomInfo(Subgraph2Deno auto &inner_numerator = set_manager.Union(*left.numerator_relations, *right.numerator_relations); auto inner_denom = CalculateInnerJoinDenom(GetEffectiveDenom(left.denom) * GetEffectiveDenom(right.denom), filter); + auto composite_pair = predicate.GetCompositeJoinPair(); + if (composite_pair) { + auto cap = GetJoinPairCap(*composite_pair); + CompositeJoinPairStats stats; + stats.RegisterDistinctCount(filter.GetDistinctCount()); + if (stats.CanApplyCap(cap) && filter.GetDistinctCount() > 0 && cap > filter.GetDistinctCount()) { + inner_denom = inner_denom / filter.GetDistinctCount() * cap; + } + } if (inner_denom <= 0) { return LeftJoinDenomInfo(preserved_numerator, preserved_denom); } diff --git a/src/duckdb/src/optimizer/join_order/join_predicate.cpp b/src/duckdb/src/optimizer/join_order/join_predicate.cpp index 71c1ed2e1..f832c7b6c 100644 --- a/src/duckdb/src/optimizer/join_order/join_predicate.cpp +++ b/src/duckdb/src/optimizer/join_order/join_predicate.cpp @@ -164,6 +164,14 @@ optional_idx JoinPredicate::GetEqualityClassIndex() const { return equality_class_index; } +void JoinPredicate::SetCompositeJoinPair(JoinRelationSet &pair) { + composite_join_pair = pair; +} + +optional_ptr JoinPredicate::GetCompositeJoinPair() const { + return composite_join_pair; +} + JoinEqualityPredicateEdge::JoinEqualityPredicateEdge(JoinPredicate &predicate, RelationIndex left_relation, RelationIndex right_relation, ColumnBinding left_binding, ColumnBinding right_binding) @@ -171,6 +179,46 @@ JoinEqualityPredicateEdge::JoinEqualityPredicateEdge(JoinPredicate &predicate, R right_binding(right_binding) { } +idx_t JoinEqualityGroups::GetOrCreateGroup(const ColumnBinding &binding) { + auto entry = binding_to_group.emplace(binding, parents.size()); + if (entry.second) { + parents.push_back(entry.first->second); + } + return entry.first->second; +} + +idx_t JoinEqualityGroups::FindRoot(idx_t group) { + while (parents[group] != group) { + parents[group] = parents[parents[group]]; + group = parents[group]; + } + return group; +} + +void JoinEqualityGroups::AddEquality(const ColumnBinding &left, const ColumnBinding &right) { + auto left_root = FindRoot(GetOrCreateGroup(left)); + auto right_root = FindRoot(GetOrCreateGroup(right)); + if (left_root != right_root) { + parents[MaxValue(left_root, right_root)] = MinValue(left_root, right_root); + } +} + +idx_t JoinEqualityGroups::GetGroup(const ColumnBinding &binding) { + auto entry = binding_to_group.find(binding); + D_ASSERT(entry != binding_to_group.end()); + return FindRoot(entry->second); +} + +void RelationPairEqualitySummary::AddEquality(idx_t equality_class_index, ColumnBinding first_binding, + ColumnBinding second_binding) { + if (std::find(direct_equality_class_indices.begin(), direct_equality_class_indices.end(), equality_class_index) == + direct_equality_class_indices.end()) { + direct_equality_class_indices.push_back(equality_class_index); + } + first_relation_bindings.insert(first_binding); + second_relation_bindings.insert(second_binding); +} + bool RelationPairEqualitySummary::HasDirectCompositeEquality() const { return direct_equality_class_indices.size() >= 2 && first_relation_bindings.size() >= 2 && second_relation_bindings.size() >= 2; @@ -215,18 +263,9 @@ void JoinPredicateModel::AddEqualityClass(JoinEqualityClass equality_class) { equality_classes.push_back(std::move(equality_class)); } -bool JoinPredicateModel::ContainsClassIndex(const vector &class_indices, idx_t equality_class_index) { - return std::find(class_indices.begin(), class_indices.end(), equality_class_index) != class_indices.end(); -} - void JoinPredicateModel::AddDirectEqualityPairClass(JoinRelationSet &pair, idx_t equality_class_index, ColumnBinding first_binding, ColumnBinding second_binding) { - auto &summary = equality_pairs[pair]; - if (!ContainsClassIndex(summary.direct_equality_class_indices, equality_class_index)) { - summary.direct_equality_class_indices.push_back(equality_class_index); - } - summary.first_relation_bindings.insert(first_binding); - summary.second_relation_bindings.insert(second_binding); + equality_pairs[pair].AddEquality(equality_class_index, first_binding, second_binding); } const vector> &JoinPredicateModel::GetPredicates() const { diff --git a/src/duckdb/src/optimizer/join_order/join_relation_set.cpp b/src/duckdb/src/optimizer/join_order/join_relation_set.cpp index 10e319dee..7794c423f 100644 --- a/src/duckdb/src/optimizer/join_order/join_relation_set.cpp +++ b/src/duckdb/src/optimizer/join_order/join_relation_set.cpp @@ -9,8 +9,18 @@ namespace duckdb { using JoinRelationTreeNode = JoinRelationSetManager::JoinRelationTreeNode; +static constexpr idx_t BITMASK_WORD_SIZE = sizeof(idx_t) * 8; + JoinRelationSet::JoinRelationSet(unsafe_unique_array relations, idx_t count) : relations(std::move(relations)), count(count) { + if (count != 0) { + // the relations are sorted, so the last one determines the number of words + bitmask.resize(this->relations[count - 1].index / BITMASK_WORD_SIZE + 1, 0); + for (idx_t i = 0; i < count; i++) { + auto index = this->relations[i].index; + bitmask[index / BITMASK_WORD_SIZE] |= idx_t(1) << (index % BITMASK_WORD_SIZE); + } + } } // LCOV_EXCL_START @@ -30,33 +40,23 @@ bool JoinRelationSet::Empty() const { //! Returns true if sub is a subset of super bool JoinRelationSet::IsSubset(const JoinRelationSet &super, const JoinRelationSet &sub) { D_ASSERT(sub.count > 0); - if (sub.count > super.count) { + if (sub.count > super.count || sub.bitmask.size() > super.bitmask.size()) { return false; } - idx_t j = 0; - for (idx_t i = 0; i < super.count; i++) { - if (sub.relations[j] == super.relations[i]) { - j++; - if (j == sub.count) { - return true; - } + for (idx_t i = 0; i < sub.bitmask.size(); i++) { + if (sub.bitmask[i] & ~super.bitmask[i]) { + return false; } } - return false; + return true; } bool JoinRelationSet::Intersects(const JoinRelationSet &left, const JoinRelationSet &right) { - idx_t left_idx = 0; - idx_t right_idx = 0; - while (left_idx < left.count && right_idx < right.count) { - if (left.relations[left_idx] == right.relations[right_idx]) { + const auto words = MinValue(left.bitmask.size(), right.bitmask.size()); + for (idx_t i = 0; i < words; i++) { + if (left.bitmask[i] & right.bitmask[i]) { return true; } - if (left.relations[left_idx] < right.relations[right_idx]) { - left_idx++; - } else { - right_idx++; - } } return false; } diff --git a/src/duckdb/src/optimizer/join_order/plan_enumerator.cpp b/src/duckdb/src/optimizer/join_order/plan_enumerator.cpp index f88abebee..5c4a415fa 100644 --- a/src/duckdb/src/optimizer/join_order/plan_enumerator.cpp +++ b/src/duckdb/src/optimizer/join_order/plan_enumerator.cpp @@ -413,45 +413,51 @@ bool PlanEnumerator::SolveJoinOrderApproximately() { for (idx_t i = 0; i < query_graph_manager.relation_manager.NumRelations(); i++) { join_relations.push_back(query_graph_manager.set_manager.GetJoinRelation(RelationIndex(i))); } - while (join_relations.size() > 1) { - // now in every step of the algorithm, we greedily pick the join between the to-be-joined relations that has the - // smallest cost. This is O(r^2) per step, and every step will reduce the total amount of relations to-be-joined - // by 1, so the total cost is O(r^3) in the amount of relations - // long is needed to prevent clang-tidy complaints. (idx_t) cannot be added to an iterator position because it - // is unsigned. - idx_t best_left = 0, best_right = 0; - bool found_connection = false; - double best_cost = NumericLimits::Maximum(); + + // in every step of the algorithm, we greedily pick the join between the to-be-joined relations that has the + // smallest cost. Pair costs are deterministic while the query graph is unchanged, so we evaluate each pair once + // and keep the viable candidates in a list, instead of re-emitting every pair in every step + struct GreedyCandidate { + optional_ptr left; + optional_ptr right; + double cost; + }; + vector candidates; + auto EmitCandidate = [&](JoinRelationSet &left, JoinRelationSet &right) { + auto &connection = GetConnections(left, right); + if (connection.empty()) { + return; + } + auto node = EmitPair(left, right, connection); + if (node) { + candidates.push_back({&left, &right, node->cost}); + } + }; + auto InitializeCandidates = [&]() { + candidates.clear(); for (idx_t i = 0; i < join_relations.size(); i++) { - auto left = join_relations[i]; for (idx_t j = i + 1; j < join_relations.size(); j++) { - auto right = join_relations[j]; - // check if we can connect these two relations - auto &connection = GetConnections(left, right); - if (!connection.empty()) { - // we can check the cost of this connection - auto node = EmitPair(left, right, connection); - if (!node) { - continue; - } + EmitCandidate(join_relations[i], join_relations[j]); + } + } + }; + InitializeCandidates(); - // update the DP tree in case a plan created by the DP algorithm uses the node - // that was potentially just updated by EmitPair. You will get a use-after-free - // error if future plans rely on the old node that was just replaced. - // if node in FullPath, then updateDP tree. - - if (!found_connection || node->cost < best_cost) { - // best pair found so far - found_connection = true; - best_cost = node->cost; - best_left = i; - best_right = j; - } - } + while (join_relations.size() > 1) { + optional_ptr best_left; + optional_ptr best_right; + double best_cost = NumericLimits::Maximum(); + for (auto &candidate : candidates) { + if (!best_left || candidate.cost < best_cost) { + best_cost = candidate.cost; + best_left = candidate.left; + best_right = candidate.right; } } - if (!found_connection) { + if (!best_left) { if (ActivateRequiredCrossProducts()) { + // the query graph gained edges, which invalidates the evaluated candidates + InitializeCandidates(); continue; } // Optimizer-introduced cross products are valid only inside an inner-join companion set (Section 6.2). @@ -466,41 +472,50 @@ bool PlanEnumerator::SolveJoinOrderApproximately() { auto right_plan = plans.find(right); D_ASSERT(left_plan != plans.end() && right_plan != plans.end()); auto cost = left_plan->second->cost + right_plan->second->cost; - if (!found_connection || cost < best_cost) { - found_connection = true; + if (!best_left || cost < best_cost) { best_cost = cost; - best_left = i; - best_right = j; + best_left = left; + best_right = right; } } } - if (!found_connection) { + if (!best_left) { return false; } - auto &left = join_relations[best_left].get(); - auto &right = join_relations[best_right].get(); // create a cross product edge (i.e. edge with empty filter) between these two sets in the query graph - query_graph_manager.CreateQueryGraphCrossProduct(left, right); + query_graph_manager.CreateQueryGraphCrossProduct(*best_left, *best_right); + // the new edge only connects this pair, which is merged below, so the other candidates remain valid connection_cache.clear(); // now emit the pair and continue with the algorithm - auto &connections = GetConnections(left, right); + auto &connections = GetConnections(*best_left, *best_right); D_ASSERT(!connections.empty()); - auto cross_product = EmitPair(left, right, connections); + auto cross_product = EmitPair(*best_left, *best_right, connections); if (!cross_product) { return false; } } - // now update the to-be-checked pairs - // remove left and right, and add the combination - - // important to erase the biggest element first - // if we erase the smallest element first the index of the biggest element changes - auto &new_set = query_graph_manager.set_manager.Union(join_relations.at(best_left).get(), - join_relations.at(best_right).get()); - D_ASSERT(best_right > best_left); - join_relations.erase(join_relations.begin() + (int64_t)best_right); - join_relations.erase(join_relations.begin() + (int64_t)best_left); + // now update the to-be-checked pairs: remove left and right, and add the combination + auto &new_set = query_graph_manager.set_manager.Union(*best_left, *best_right); + for (idx_t i = join_relations.size(); i > 0; i--) { + auto &relation = join_relations[i - 1].get(); + if (&relation == best_left.get() || &relation == best_right.get()) { + join_relations.erase(join_relations.begin() + NumericCast(i - 1)); + } + } + idx_t kept_candidates = 0; + for (idx_t candidate_idx = 0; candidate_idx < candidates.size(); candidate_idx++) { + auto &candidate = candidates[candidate_idx]; + if (candidate.left == best_left || candidate.left == best_right || candidate.right == best_left || + candidate.right == best_right) { + continue; + } + candidates[kept_candidates++] = candidate; + } + candidates.resize(kept_candidates); + for (auto &relation : join_relations) { + EmitCandidate(relation, new_set); + } join_relations.push_back(new_set); } return true; diff --git a/src/duckdb/src/optimizer/join_order/query_graph_manager.cpp b/src/duckdb/src/optimizer/join_order/query_graph_manager.cpp index 149d4b127..7f91cf700 100644 --- a/src/duckdb/src/optimizer/join_order/query_graph_manager.cpp +++ b/src/duckdb/src/optimizer/join_order/query_graph_manager.cpp @@ -26,27 +26,7 @@ QueryGraphManager::QueryGraphManager(ClientContext &context) : context(context), void QueryGraphManager::BuildPredicateModel() { predicate_model.Clear(); - column_binding_map_t binding_to_component; - vector parents; - - auto get_component = [&](const ColumnBinding &binding) -> idx_t { - auto entry = binding_to_component.find(binding); - if (entry != binding_to_component.end()) { - return entry->second; - } - auto component = parents.size(); - parents.push_back(component); - binding_to_component[binding] = component; - return component; - }; - - auto find_root = [&](idx_t component) -> idx_t { - while (parents[component] != component) { - parents[component] = parents[parents[component]]; - component = parents[component]; - } - return component; - }; + JoinEqualityGroups equality_groups; // First union all normalized equality predicates. Do not assign edge ids in this pass, // because a later predicate can merge two previously separate components. @@ -66,11 +46,7 @@ void QueryGraphManager::BuildPredicateModel() { continue; } - auto left_root = find_root(get_component(predicate.GetEqualityBinding(true))); - auto right_root = find_root(get_component(predicate.GetEqualityBinding(false))); - if (left_root != right_root) { - parents[MaxValue(left_root, right_root)] = MinValue(left_root, right_root); - } + equality_groups.AddEquality(predicate.GetEqualityBinding(true), predicate.GetEqualityBinding(false)); } // Then assign stable final ids to every equality edge using the final roots. @@ -81,7 +57,7 @@ void QueryGraphManager::BuildPredicateModel() { if (!predicate.CanBuildEqualityClosure()) { continue; } - auto root = find_root(binding_to_component[predicate.GetEqualityBinding(true)]); + auto root = equality_groups.GetGroup(predicate.GetEqualityBinding(true)); auto entry = root_to_equivalence_id.find(root); if (entry == root_to_equivalence_id.end()) { entry = root_to_equivalence_id.insert(make_pair(root, root_to_equivalence_id.size())).first; @@ -125,6 +101,79 @@ void QueryGraphManager::BuildPredicateModel() { predicate_model.AddDirectEqualityPairClass(pair, equality_class.index, first_binding, second_binding); } } + BuildLeftEqualityPairs(); +} + +void QueryGraphManager::BuildLeftEqualityPairs() { + for (auto &op : join_operators) { + if (op->type != JoinOrderOperatorType::LEFT) { + continue; + } + JoinEqualityGroups groups; + auto add_equality = [&](ExpressionType type, const Expression &left, const Expression &right) { + if (type != ExpressionType::COMPARE_EQUAL && type != ExpressionType::COMPARE_NOT_DISTINCT_FROM) { + return; + } + ColumnBinding left_binding; + ColumnBinding right_binding; + GetEquivalenceBinding(left, left_binding); + GetEquivalenceBinding(right, right_binding); + if (left_binding.table_index.IsValid() && right_binding.table_index.IsValid()) { + groups.AddEquality(left_binding, right_binding); + } + }; + vector> residuals; + for (auto &condition : op->conditions) { + if (condition.IsComparison()) { + add_equality(condition.GetComparisonType(), condition.GetLHS(), condition.GetRHS()); + } else { + residuals.push_back(condition.GetJoinExpression()); + } + } + // Same-side ON equalities can connect keys even though they are not costing predicates. + while (!residuals.empty()) { + auto &expression = residuals.back().get(); + residuals.pop_back(); + if (expression.GetExpressionType() == ExpressionType::CONJUNCTION_AND) { + for (auto &child : expression.Cast().GetChildren()) { + residuals.push_back(*child); + } + } else if (BoundComparisonExpression::IsComparison(expression)) { + auto &comparison = expression.Cast(); + add_equality(expression.GetExpressionType(), BoundComparisonExpression::Left(comparison), + BoundComparisonExpression::Right(comparison)); + } + } + reference_map_t summaries; + vector, reference>> pairs; + for (auto predicate_index : op->costing_predicate_indices) { + auto &predicate = predicate_model.GetPredicates()[predicate_index].get(); + auto type = predicate.GetComparisonType(); + if (!predicate.CanBuildSelectivityDomain() || !predicate.HasValidEqualityBindings() || + (type != ExpressionType::COMPARE_EQUAL && type != ExpressionType::COMPARE_NOT_DISTINCT_FROM)) { + continue; + } + auto first_binding = predicate.GetEqualityBinding(true); + auto second_binding = predicate.GetEqualityBinding(false); + auto first_relation = JoinOrderUtil::GetBindingRelation(first_binding); + auto second_relation = JoinOrderUtil::GetBindingRelation(second_binding); + if (first_relation == second_relation) { + continue; + } + auto &pair = set_manager.Union(set_manager.GetJoinRelation(first_relation), + set_manager.GetJoinRelation(second_relation)); + if (pair.relations[0] != first_relation) { + std::swap(first_binding, second_binding); + } + summaries[pair].AddEquality(groups.GetGroup(first_binding), first_binding, second_binding); + pairs.emplace_back(predicate, pair); + } + for (auto &entry : pairs) { + if (summaries.at(entry.second).HasDirectCompositeEquality()) { + entry.first.get().SetCompositeJoinPair(entry.second); + } + } + } } void QueryGraphManager::BuildInnerCompanionSets() { diff --git a/src/duckdb/src/optimizer/optimizer.cpp b/src/duckdb/src/optimizer/optimizer.cpp index 30d8d8464..a068c5fba 100644 --- a/src/duckdb/src/optimizer/optimizer.cpp +++ b/src/duckdb/src/optimizer/optimizer.cpp @@ -386,7 +386,7 @@ void Optimizer::RunBuiltInOptimizers() { // rewrites UNNESTs in DelimJoins by moving them to the projection RunOptimizer(OptimizerType::UNNEST_REWRITER, [&]() { - UnnestRewriter unnest_rewriter; + UnnestRewriter unnest_rewriter(context); plan = unnest_rewriter.Optimize(std::move(plan)); }); diff --git a/src/duckdb/src/optimizer/pullup/pullup_projection.cpp b/src/duckdb/src/optimizer/pullup/pullup_projection.cpp index c69e5535d..c9c940e3f 100644 --- a/src/duckdb/src/optimizer/pullup/pullup_projection.cpp +++ b/src/duckdb/src/optimizer/pullup/pullup_projection.cpp @@ -80,6 +80,13 @@ unique_ptr FilterPullup::PullupProjection(unique_ptrchildren[0] = Rewrite(std::move(op->children[0])); if (!filters_expr_pullup.empty()) { auto &proj = op->Cast(); + for (auto &expr : filters_expr_pullup) { + if (expr->CanThrow()) { + // Keep fallible predicates and their guards below the projection. + RevertFilterPullup(proj, filters_expr_pullup); + return op; + } + } // INTERSECT, EXCEPT, and DISTINCT if (!can_add_column) { // special treatment for operators that cannot add columns, e.g., INTERSECT, EXCEPT, and DISTINCT diff --git a/src/duckdb/src/optimizer/remove_unused_columns.cpp b/src/duckdb/src/optimizer/remove_unused_columns.cpp index 4af9f46b1..8c9e597c5 100644 --- a/src/duckdb/src/optimizer/remove_unused_columns.cpp +++ b/src/duckdb/src/optimizer/remove_unused_columns.cpp @@ -1661,6 +1661,15 @@ bool BaseColumnPruner::HandleExtractRecursive(unique_ptr &expr_p, } } +//! A cast pushed into the scan of a shredded VARIANT is applied to the shredded value instead of the VARIANT. For a +//! JSON target the two differ: a VARIANT serializes to JSON, a shredded VARCHAR would be parsed as JSON. +static bool ExtractCastCanBePushedDown(const BoundColumnRefExpression &colref, const Expression &cast) { + if (colref.GetReturnType().id() != LogicalTypeId::VARIANT) { + return true; + } + return !cast.GetReturnType().IsJSONType(); +} + bool BaseColumnPruner::HandleExtractExpression(unique_ptr *expression, optional_ptr> cast_expression) { optional_ptr colref; @@ -1671,6 +1680,10 @@ bool BaseColumnPruner::HandleExtractExpression(unique_ptr *expressio if (!HandleExtractRecursive(*expression, colref, path_ref, expressions)) { return false; } + if (cast_expression && !ExtractCastCanBePushedDown(*colref, **cast_expression)) { + // the cast stays on top of the pushed-down extract + cast_expression = nullptr; + } if (cast_expression) { auto &top_level = expressions.back(); top_level.cast = cast_expression; @@ -1812,6 +1825,13 @@ static bool TryGetCastChild(unique_ptr &expr, optional_ptr VARIANT -> U equals T -> U. + // Invertibility is insufficient: VARIANT uses stricter string-to-number parsing, + // and zoned timestamp serialization can differ between the two paths. + return false; + } child = BoundCastExpression::ChildMutable(cast); return true; diff --git a/src/duckdb/src/optimizer/statistics/expression/propagate_conjunction.cpp b/src/duckdb/src/optimizer/statistics/expression/propagate_conjunction.cpp index 8e56fee73..debc0f07b 100644 --- a/src/duckdb/src/optimizer/statistics/expression/propagate_conjunction.cpp +++ b/src/duckdb/src/optimizer/statistics/expression/propagate_conjunction.cpp @@ -2,11 +2,68 @@ #include "duckdb/optimizer/statistics_propagator.hpp" #include "duckdb/planner/expression/bound_conjunction_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" +#include "duckdb/planner/expression/bound_function_expression.hpp" +#include "duckdb/planner/expression/expression_barrier.hpp" +#include "duckdb/planner/expression_nullability.hpp" #include "duckdb/optimizer/expression_rewriter.hpp" #include "duckdb/execution/expression_executor.hpp" namespace duckdb { +void StatisticsPropagator::SimplifyConstantOrNull(vector> &expressions) { + if (expressions.size() < 2) { + return; + } + bool has_candidate = false; + for (auto &expr : expressions) { + has_candidate |= ExpressionIsConstantOrNull(*expr, Value::BOOLEAN(true)); + } + if (!has_candidate) { + return; + } + for (auto &expr : expressions) { + // Removing a NULL check can expose more rows to the remaining predicates. + if (expr->CanThrow() || expr->IsVolatile() || ExpressionBarrier::Contains(*expr)) { + return; + } + } + for (idx_t expr_idx = 0; expr_idx < expressions.size(); expr_idx++) { + auto &expr = *expressions[expr_idx]; + if (!ExpressionIsConstantOrNull(expr, Value::BOOLEAN(true))) { + continue; + } + auto &children = expr.Cast().GetChildren(); + D_ASSERT(children.size() >= 2); + bool redundant = true; + for (idx_t child_idx = 1; child_idx < children.size(); child_idx++) { + auto &child = *children[child_idx]; + if (child.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { + if (child.Cast().GetValue().IsNull()) { + redundant = false; + break; + } + continue; + } + bool rejects_null = false; + for (idx_t other_idx = 0; other_idx < expressions.size(); other_idx++) { + if (other_idx != expr_idx && FilterRejectsNull(*expressions[other_idx], child)) { + rejects_null = true; + break; + } + } + if (!rejects_null) { + redundant = false; + break; + } + } + if (redundant) { + expressions.erase_at(expr_idx); + expr_idx--; + removed_expressions = true; + } + } +} + unique_ptr StatisticsPropagator::PropagateExpression(BoundConjunctionExpression &expr, unique_ptr &expr_ptr) { auto is_and = expr.GetExpressionType() == ExpressionType::CONJUNCTION_AND; @@ -54,6 +111,9 @@ unique_ptr StatisticsPropagator::PropagateExpression(BoundConjun expr_ptr = make_uniq(Value::BOOLEAN(constant_value)); return PropagateExpression(expr_ptr); } + if (is_and) { + SimplifyConstantOrNull(expr.GetChildrenMutable()); + } if (expr.GetChildrenMutable().empty()) { // if there are no children left, replace the conjunction with TRUE (for AND) or FALSE (for OR) expr_ptr = make_uniq(Value::BOOLEAN(is_and)); diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_aggregate.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_aggregate.cpp index 23b10cbf4..4de83ed3c 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_aggregate.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_aggregate.cpp @@ -533,9 +533,14 @@ unique_ptr StatisticsPropagator::PropagateStatistics(LogicalAggr } aggr.distinct_validity = distinct_validity; + const auto is_ungrouped = aggr.groups.empty(); // after we propagate statistics - try to directly execute aggregates using statistics TryExecuteAggregates(aggr, node_ptr); + if (is_ungrouped) { + // Ungrouped aggregate always returns exactly one row, even over empty input + return make_uniq(1U, 1U); + } // the max cardinality of an aggregate is the max cardinality of the input (i.e. when every row is a unique // group) return std::move(node_stats); diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_filter.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_filter.cpp index 8a39bf26f..6e5b05758 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_filter.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_filter.cpp @@ -338,7 +338,7 @@ unique_ptr StatisticsPropagator::PropagateStatistics(LogicalFilt if (filter.projection_map.empty()) { node_ptr = std::move(filter.children[0]); } - break; + return std::move(node_stats); } } else if (prune_result == FilterPropagateResult::FILTER_ALWAYS_FALSE || prune_result == FilterPropagateResult::FILTER_FALSE_OR_NULL) { @@ -347,6 +347,7 @@ unique_ptr StatisticsPropagator::PropagateStatistics(LogicalFilt return make_uniq(0U, 0U); } } + SimplifyConstantOrNull(filter.expressions); // the max cardinality of a filter is the cardinality of the input (i.e. no tuples get filtered) return std::move(node_stats); } diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp index d68863d5c..8d3eaa6b3 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp @@ -13,6 +13,21 @@ namespace duckdb { +static unique_ptr PromoteJoinSide(unique_ptr child, + vector projection_map) { + if (projection_map.empty()) { + return child; + } + auto filter = make_uniq(); + filter->projection_map = std::move(projection_map); + filter->children.push_back(std::move(child)); + if (filter->children[0]->has_estimated_cardinality) { + filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); + } + filter->ResolveOperatorTypes(); + return std::move(filter); +} + bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: @@ -21,15 +36,15 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< // semi or inner join on false; entire node can be pruned ReplaceWithEmptyResult(node_ptr); return true; + + // If the filter is always false or Null, the join output is exactly one of its sides: + // promote that side together with its projection map so the output layout stays unchanged. case JoinType::RIGHT_ANTI: - case JoinType::ANTI: { - if (join.join_type == JoinType::RIGHT_ANTI) { - std::swap(join.children[0], join.children[1]); - } - // If the filter is always false or Null, just return the left child. - node_ptr = std::move(join.children[0]); + node_ptr = PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); + return true; + case JoinType::ANTI: + node_ptr = PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); return true; - } case JoinType::LEFT: // anti/left outer join: replace right side with empty node ReplaceWithEmptyResult(join.children[1]); @@ -348,6 +363,10 @@ unique_ptr StatisticsPropagator::PropagateStatistics(LogicalPosi void StatisticsPropagator::CreateFilterFromJoinStats(unique_ptr &child, unique_ptr &expr, const BaseStatistics &stats_before, const BaseStatistics &stats_after) { + // Defer join-derived filters until after join ordering. + if (mode != StatisticsPropagationMode::FULL) { + return; + } // Only do this for integral colref's that have stats if (expr->GetExpressionType() != ExpressionType::BOUND_COLUMN_REF || !expr->GetReturnType().IsIntegral() || !NumericStats::HasMinMax(stats_before) || !NumericStats::HasMinMax(stats_after)) { diff --git a/src/duckdb/src/optimizer/statistics_propagator.cpp b/src/duckdb/src/optimizer/statistics_propagator.cpp index 7b77d0e0d..fc928ae1f 100644 --- a/src/duckdb/src/optimizer/statistics_propagator.cpp +++ b/src/duckdb/src/optimizer/statistics_propagator.cpp @@ -46,6 +46,7 @@ unique_ptr StatisticsPropagator::PropagateChildren(LogicalOperat unique_ptr StatisticsPropagator::PropagateStatistics(LogicalOperator &node, unique_ptr &node_ptr) { + propagation_path.emplace_back(node_ptr); unique_ptr result; switch (node.type) { case LogicalOperatorType::LOGICAL_AGGREGATE_AND_GROUP_BY: @@ -106,10 +107,11 @@ unique_ptr StatisticsPropagator::PropagateStatistics(LogicalOper if (mode == StatisticsPropagationMode::FULL && !optimizer.OptimizerDisabled(OptimizerType::COMPRESSED_MATERIALIZATION)) { // compress data based on statistics for materializing operators - CompressedMaterialization compressed_materialization(optimizer, *root, statistics_map); + CompressedMaterialization compressed_materialization(optimizer, *root, propagation_path, statistics_map); compressed_materialization.Compress(node_ptr); } + propagation_path.pop_back(); return result; } diff --git a/src/duckdb/src/optimizer/topn_window_elimination.cpp b/src/duckdb/src/optimizer/topn_window_elimination.cpp index 6415ba01f..7808bc49e 100644 --- a/src/duckdb/src/optimizer/topn_window_elimination.cpp +++ b/src/duckdb/src/optimizer/topn_window_elimination.cpp @@ -275,6 +275,8 @@ unique_ptr TopNWindowElimination::OptimizeInternal(unique_ptr new_bindings; diff --git a/src/duckdb/src/optimizer/unnest_rewriter.cpp b/src/duckdb/src/optimizer/unnest_rewriter.cpp index bd31d874d..827158c14 100644 --- a/src/duckdb/src/optimizer/unnest_rewriter.cpp +++ b/src/duckdb/src/optimizer/unnest_rewriter.cpp @@ -1,5 +1,7 @@ #include "duckdb/optimizer/unnest_rewriter.hpp" +#include "duckdb/planner/expression/bound_cast_expression.hpp" + #include "duckdb/common/assert.hpp" #include "duckdb/common/enums/expression_type.hpp" #include "duckdb/common/helper.hpp" @@ -217,7 +219,8 @@ void UnnestRewriter::FindCandidates(unique_ptr &root, unique_pt } auto &bind_col = proj.expressions[col_bind.column_index]->Cast(); auto unnest_expr = make_uniq(unnest_get->types[i]); - unnest_expr->ChildMutable() = proj.expressions[col_bind.column_index]->Copy(); + unnest_expr->ChildMutable() = + BoundCastExpression::AddArrayCastToList(context, proj.expressions[col_bind.column_index]->Copy()); bind_col.BindingMutable() = ColumnBinding(unnest_get_index, bind_col.Binding().column_index); auto unnest_proj_idx = ColumnBinding::PushExpression(unnest->expressions, std::move(unnest_expr)); ColumnBinding new_column_ref(bind_col.Binding().table_index, unnest_proj_idx); @@ -236,8 +239,9 @@ void UnnestRewriter::FindCandidates(unique_ptr &root, unique_pt } } -static bool ConvertCTETableInOutUnnest(unique_ptr &root, unique_ptr &op, - TableIndex input_cte_index, bool require_input_cte_ref = true) { +static bool ConvertCTETableInOutUnnest(ClientContext &context, unique_ptr &root, + unique_ptr &op, TableIndex input_cte_index, + bool require_input_cte_ref = true) { if (op->type != LogicalOperatorType::LOGICAL_GET) { return false; } @@ -284,7 +288,8 @@ static bool ConvertCTETableInOutUnnest(unique_ptr &root, unique } auto &bind_col = proj.expressions[col_bind.column_index]->Cast(); auto unnest_expr = make_uniq(unnest_get->types[i]); - unnest_expr->ChildMutable() = proj.expressions[col_bind.column_index]->Copy(); + unnest_expr->ChildMutable() = + BoundCastExpression::AddArrayCastToList(context, proj.expressions[col_bind.column_index]->Copy()); bind_col.BindingMutable() = ColumnBinding(unnest_get_index, bind_col.Binding().column_index); auto unnest_proj_idx = ColumnBinding::PushExpression(unnest->expressions, std::move(unnest_expr)); ColumnBinding new_column_ref(bind_col.Binding().table_index, unnest_proj_idx); @@ -416,7 +421,7 @@ bool UnnestRewriter::RewriteInlineCTEDedupCandidate(unique_ptr idx_t other_side = 1 - domain_side.GetIndex(); auto domain_ref_bindings = join.children[domain_side.GetIndex()]->GetColumnBindings(); if (join.children[other_side]->type == LogicalOperatorType::LOGICAL_GET && - !ConvertCTETableInOutUnnest(root, join.children[other_side], domain_cte.table_index, false)) { + !ConvertCTETableInOutUnnest(context, root, join.children[other_side], domain_cte.table_index, false)) { return false; } @@ -580,7 +585,7 @@ bool UnnestRewriter::RewriteCTECandidate(unique_ptr &root, uniq idx_t other_side = 1 - domain_side.GetIndex(); if (join.children[other_side]->type == LogicalOperatorType::LOGICAL_GET && - !ConvertCTETableInOutUnnest(root, join.children[other_side], dedup_cte.table_index)) { + !ConvertCTETableInOutUnnest(context, root, join.children[other_side], dedup_cte.table_index)) { return false; } diff --git a/src/duckdb/src/parallel/executor.cpp b/src/duckdb/src/parallel/executor.cpp index 14b8e78b8..8beb6d811 100644 --- a/src/duckdb/src/parallel/executor.cpp +++ b/src/duckdb/src/parallel/executor.cpp @@ -605,12 +605,11 @@ void Executor::ThrowException() { } void Executor::Flush(ThreadContext &thread_context) { - auto global_profiler = profiler; - if (global_profiler) { - global_profiler->Flush(thread_context.profiler); + if (profiler) { + profiler->Flush(thread_context.profiler); auto blocked_time = blocked_thread_time.load(); - global_profiler->SetBlockedTime(double(blocked_time) / 1000.0 / 1000.0); + profiler->SetBlockedTime(double(blocked_time) / 1000.0 / 1000.0); } } diff --git a/src/duckdb/src/parallel/pipeline_broadcast_exchange.cpp b/src/duckdb/src/parallel/pipeline_broadcast_exchange.cpp index 9ab93544c..84db381bd 100644 --- a/src/duckdb/src/parallel/pipeline_broadcast_exchange.cpp +++ b/src/duckdb/src/parallel/pipeline_broadcast_exchange.cpp @@ -120,7 +120,7 @@ struct PipelineBroadcastExchange::BroadcastSpool { collection.InitializeAppend(append_state); } - void Append(DataChunk &chunk, idx_t batch_index) { + void Append(DataChunk &chunk, idx_t batch_index) DUCKDB_EXCLUDES(lock) { annotated_lock_guard guard(lock); D_ASSERT(chunk.size() > 0); auto row_offset = collection.Count(); @@ -130,18 +130,18 @@ struct PipelineBroadcastExchange::BroadcastSpool { append_generation++; } - void Read(idx_t position, DataChunk &result, BroadcastSpoolReader &reader) { - annotated_lock_guard guard(lock); - if (position < base_position || position >= next_position) { - throw InternalException("Attempted to read retired pipeline broadcast spool chunk"); + void Read(idx_t position, const ChunkEntry &entry, DataChunk &result, BroadcastSpoolReader &reader, + bool producer_finished) DUCKDB_EXCLUDES(lock) { + // Completion is observed under the exchange mutex; collection metadata and append_generation are then + // immutable. + annotated_unique_lock guard(lock, std::defer_lock); + if (!producer_finished) { + guard.lock(); } - auto chunk_idx = position - base_position; - D_ASSERT(chunk_idx < chunks.size()); - auto &entry = chunks[chunk_idx]; result.Reset(); auto row_idx = entry.row_offset; - auto offset = SeekLocked(position, entry, row_idx, reader); + auto offset = Seek(position, entry, row_idx, reader); auto available = reader.scan_chunk.size() - offset; if (available >= entry.row_count) { if (offset == 0 && entry.row_count == reader.scan_chunk.size()) { @@ -155,7 +155,7 @@ struct PipelineBroadcastExchange::BroadcastSpool { idx_t rows_read = 0; while (rows_read < entry.row_count) { row_idx = entry.row_offset + rows_read; - offset = SeekLocked(position, entry, row_idx, reader); + offset = Seek(position, entry, row_idx, reader); auto append_count = MinValue(reader.scan_chunk.size() - offset, entry.row_count - rows_read); auto sel = SelectionVector::Incremental(offset, append_count); result.Append(reader.scan_chunk, sel, append_count); @@ -163,7 +163,7 @@ struct PipelineBroadcastExchange::BroadcastSpool { } } - idx_t RetireBefore(idx_t position) { + idx_t RetireBefore(idx_t position) DUCKDB_EXCLUDES(lock) { annotated_lock_guard guard(lock); idx_t retired_chunks = 0; while (!chunks.empty() && base_position < position) { @@ -174,41 +174,35 @@ struct PipelineBroadcastExchange::BroadcastSpool { return retired_chunks; } - bool HasPosition(idx_t position) { - annotated_lock_guard guard(lock); - return position >= base_position && position < next_position; - } - - idx_t BatchIndex(idx_t position) { + ChunkEntry GetChunkEntry(idx_t position) DUCKDB_EXCLUDES(lock) { annotated_lock_guard guard(lock); if (position < base_position || position >= next_position) { throw InternalException("Attempted to inspect retired pipeline broadcast spool chunk"); } - return chunks[position - base_position].batch_index; + return chunks[position - base_position]; } - void InitializeReader(BroadcastSpoolReader &reader) { + void InitializeReader(BroadcastSpoolReader &reader) DUCKDB_EXCLUDES(lock) { annotated_lock_guard guard(lock); - ResetReaderLocked(reader); + ResetReader(reader); collection.InitializeScanChunk(reader.scan_state, reader.scan_chunk); } - void ResetReaderLocked(BroadcastSpoolReader &reader) { + void ResetReader(BroadcastSpoolReader &reader) { collection.InitializeScan(reader.scan_state, ColumnDataScanProperties::ALLOW_ZERO_COPY); reader.observed_generation = append_generation; } - idx_t SeekLocked(idx_t position, const ChunkEntry &entry, idx_t row_idx, BroadcastSpoolReader &reader) { + idx_t Seek(idx_t position, const ChunkEntry &entry, idx_t row_idx, BroadcastSpoolReader &reader) { if (reader.observed_generation != append_generation && !reader.Contains(row_idx)) { - ResetReaderLocked(reader); + ResetReader(reader); } if (!collection.Seek(row_idx, reader.scan_state, reader.scan_chunk)) { throw InternalException( - "Failed to read pipeline broadcast spool chunk (position=%llu, base=%llu, next=%llu, row=%llu, " + "Failed to read pipeline broadcast spool chunk (position=%llu, row=%llu, " "row_offset=%llu, row_count=%llu, current=%llu, next_row=%llu, generation=%llu, observed=%llu)", - position, base_position, next_position, row_idx, entry.row_offset, entry.row_count, - reader.scan_state.current_row_index, reader.scan_state.next_row_index, append_generation, - reader.observed_generation); + position, row_idx, entry.row_offset, entry.row_count, reader.scan_state.current_row_index, + reader.scan_state.next_row_index, append_generation, reader.observed_generation); } D_ASSERT(reader.scan_state.current_row_index <= row_idx); auto offset = row_idx - reader.scan_state.current_row_index; @@ -218,10 +212,10 @@ struct PipelineBroadcastExchange::BroadcastSpool { annotated_mutex lock; ColumnDataCollection collection; - ColumnDataAppendState append_state; - deque chunks; - idx_t base_position; - idx_t next_position; + ColumnDataAppendState append_state DUCKDB_GUARDED_BY(lock); + deque chunks DUCKDB_GUARDED_BY(lock); + idx_t base_position DUCKDB_GUARDED_BY(lock); + idx_t next_position DUCKDB_GUARDED_BY(lock); idx_t append_generation = 0; }; @@ -240,6 +234,7 @@ struct PipelineBroadcastExchange::AppendReservation { struct PipelineBroadcastExchange::SpoolReadReservation { shared_ptr spool; shared_ptr reader; + BroadcastSpool::ChunkEntry entry; idx_t position = 0; optional_idx batch_sequence; @@ -489,16 +484,14 @@ struct PipelineBroadcastExchange::BufferState { optional_idx &batch_index, SpoolReadReservation &spool_read) const { D_ASSERT(position < next_position); if (shared_spool) { - if (!shared_spool->HasPosition(position)) { - throw InternalException("Pipeline broadcast shared spool chunk was retired before it was read"); - } + spool_read.entry = shared_spool->GetChunkEntry(position); if (!reader) { reader = make_shared_ptr(*shared_spool); } spool_read.spool = shared_spool; spool_read.reader = reader; spool_read.position = position; - auto stored_batch_index = shared_spool->BatchIndex(position); + auto stored_batch_index = spool_read.entry.batch_index; if (stored_batch_index != DConstants::INVALID_INDEX) { batch_index = stored_batch_index; } @@ -1386,6 +1379,7 @@ void PipelineBroadcastExchange::Finish() { } buffer->FinishBatches(); if (producer_state == ProducerState::ACTIVE) { + D_ASSERT(append_reservation_state == AppendReservationState::IDLE); producer_state = ProducerState::FINISHED; } TryReleaseBufferedStorageLocked(); @@ -1436,6 +1430,7 @@ SourceResultType PipelineBroadcastExchange::Scan(idx_t consumer_idx, DataChunk & vector log_entries; shared_ptr next_chunk; SpoolReadReservation spool_read; + bool producer_finished; SourceResultType result; batch_index = optional_idx(); batch_index_state = SourceBatchIndexState::UNCHANGED; @@ -1443,11 +1438,12 @@ SourceResultType PipelineBroadcastExchange::Scan(idx_t consumer_idx, DataChunk & annotated_lock_guard guard(lock); result = ReserveScanLocked(consumer_idx, interrupt_state, scan_state, next_chunk, batch_index, batch_index_state, spool_read, readers, writers, log_entries); + producer_finished = producer_state == ProducerState::FINISHED; } if (spool_read.IsSet()) { try { - spool_read.spool->Read(spool_read.position, chunk, *spool_read.reader); + spool_read.spool->Read(spool_read.position, spool_read.entry, chunk, *spool_read.reader, producer_finished); } catch (...) { vector failed_readers; vector failed_writers; diff --git a/src/duckdb/src/parallel/scan_read_ahead.cpp b/src/duckdb/src/parallel/scan_read_ahead.cpp index 4ad044a57..61bd67e25 100644 --- a/src/duckdb/src/parallel/scan_read_ahead.cpp +++ b/src/duckdb/src/parallel/scan_read_ahead.cpp @@ -251,10 +251,13 @@ void ScanReadAhead::SetDone() { } bool ScanReadAhead::TryReserveSlot() { - const bool over_budget = pending_io_bytes.load() >= backlog_budget.load(); + // take the minimum job charge up front so concurrent producers cannot all pass the budget check together + const bool over_budget = pending_io_bytes.fetch_add(MINIMUM_JOB_IO_CHARGE) >= backlog_budget.load(); + // over budget one job stays queued, claimed jobs hold the budget until their task resumes const idx_t depth = over_budget ? 1 : read_ahead_depth; if (active_jobs.fetch_add(1) >= depth) { --active_jobs; + pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; return false; } ++active_producers; @@ -262,8 +265,6 @@ bool ScanReadAhead::TryReserveSlot() { } void ScanReadAhead::PushJob(unique_ptr job, vector> io_tasks) { - // beyond its scheduled I/O a job carries scan-state overhead (row-group sized decode buffers) - static constexpr idx_t MINIMUM_JOB_IO_CHARGE = 16ULL * 1024 * 1024; auto completion = make_shared_ptr(executor); job->io_completion = completion; // wrap all reads before scheduling any, a wrapped task settles the completion even when scheduling throws @@ -274,14 +275,13 @@ void ScanReadAhead::PushJob(unique_ptr job, vector(*executor, std::move(task), completion)); } job->io_bytes = MaxValue(job->io_bytes, MINIMUM_JOB_IO_CHARGE); - pending_io_bytes += job->io_bytes; + // the minimum charge was taken by TryReserveSlot, charge the remainder + pending_io_bytes += job->io_bytes - MINIMUM_JOB_IO_CHARGE; // schedule the reads detached on the async pool right away, logged under their own label if (!read_tasks.empty()) { DUCKDB_LOG(context, AsyncTaskScheduleLogType, "READ_AHEAD", read_tasks.size()); } - for (auto &task : read_tasks) { - executor->ScheduleTask(std::move(task)); - } + executor->ScheduleTasks(std::move(read_tasks)); lock_guard guard(lock); if (memory_governor) { memory_governor->UpdateReservation(pending_io_bytes.load()); diff --git a/src/duckdb/src/parallel/task_executor.cpp b/src/duckdb/src/parallel/task_executor.cpp index e580bbbb6..c5be0f60f 100644 --- a/src/duckdb/src/parallel/task_executor.cpp +++ b/src/duckdb/src/parallel/task_executor.cpp @@ -56,8 +56,12 @@ class TaskExecutorTask : public Task { } //! Retire a task that never made it into the queue, without touching the executor's accounting - void Retire() { - RunGuarded([&]() { task->Cancel(); }, "Unknown exception while cancelling a task"); + void Retire() noexcept { + try { + RunGuarded([&]() { task->Cancel(); }, "Unknown exception while cancelling a task"); + } catch (...) { + // Preserve the submission exception if reporting a cancellation error fails. + } } private: @@ -140,7 +144,16 @@ bool TaskExecutor::IsCancelled() const { void TaskExecutor::ScheduleTask(unique_ptr task) { // wrap before taking ownership of a slot, so that a failure to allocate the wrapper needs no rollback - auto scheduled_task = make_shared_ptr(*this, std::move(task)); + shared_ptr scheduled_task; + try { + scheduled_task = make_shared_ptr(*this, std::move(task)); + } catch (...) { + if (task) { + TaskExecutorTask unscheduled_task(*this, std::move(task)); + unscheduled_task.Retire(); + } + throw; + } { const annotated_lock_guard lock(token->producer_lock); ++total_tasks; @@ -160,6 +173,55 @@ void TaskExecutor::ScheduleTask(unique_ptr task) { throw; } } + +void TaskExecutor::ScheduleTasks(vector> tasks) { + if (tasks.empty()) { + return; + } + if (tasks.size() == 1) { + ScheduleTask(std::move(tasks.front())); + return; + } + vector> scheduled_tasks; + bool accounted = false; + try { +#ifdef DUCKDB_NO_THREADS + // The no-threads bulk queue can publish a prefix before an allocation fails. + for (auto &task : tasks) { + ScheduleTask(std::move(task)); + } +#else + scheduled_tasks.reserve(tasks.size()); + for (auto &task : tasks) { + scheduled_tasks.push_back(make_shared_ptr(*this, std::move(task))); + } + { + const annotated_lock_guard lock(token->producer_lock); + total_tasks += tasks.size(); + } + accounted = true; + // The threaded bulk queue publishes all tasks or none, moving ownership on success. + scheduler.ScheduleTasks(*token, scheduled_tasks, type); +#endif + } catch (...) { + if (accounted) { + const annotated_lock_guard lock(token->producer_lock); + total_tasks -= tasks.size(); + token->producer_cv.notify_one(); + } + for (auto &task : scheduled_tasks) { + static_cast(*task).Retire(); + } + for (auto &task : tasks) { + if (task) { + TaskExecutorTask unscheduled_task(*this, std::move(task)); + unscheduled_task.Retire(); + } + } + throw; + } +} + void TaskExecutor::FinishTask() { const annotated_lock_guard lk(token->producer_lock); ++completed_tasks; diff --git a/src/duckdb/src/parallel/task_notifier.cpp b/src/duckdb/src/parallel/task_notifier.cpp index 012afb548..4b1eb34ed 100644 --- a/src/duckdb/src/parallel/task_notifier.cpp +++ b/src/duckdb/src/parallel/task_notifier.cpp @@ -6,17 +6,13 @@ namespace duckdb { TaskNotifier::TaskNotifier(optional_ptr context_p) : context(context_p) { if (context) { - for (auto &state : context->registered_state->States()) { - state->OnTaskStart(*context); - } + context->registered_state->ForEachState([&](ClientContextState &state) { state.OnTaskStart(*context); }); } } TaskNotifier::~TaskNotifier() { if (context) { - for (auto &state : context->registered_state->States()) { - state->OnTaskStop(*context); - } + context->registered_state->ForEachState([&](ClientContextState &state) { state.OnTaskStop(*context); }); } } diff --git a/src/duckdb/src/parallel/task_scheduler.cpp b/src/duckdb/src/parallel/task_scheduler.cpp index 6adc36a03..a4106a020 100644 --- a/src/duckdb/src/parallel/task_scheduler.cpp +++ b/src/duckdb/src/parallel/task_scheduler.cpp @@ -104,26 +104,22 @@ void TaskScheduler::ScheduleTasks(ProducerToken &producer, vector &task) { +bool TaskScheduler::GetTaskInternal(ConsumerToken &consumer, shared_ptr &task) { for (auto &queue : queues) { - if (queue->Dequeue(task)) { + if (queue->Dequeue(consumer, task)) { return true; } } return false; } -bool TaskScheduler::GetTaskInternal(shared_ptr &task, TaskSchedulerType pool_type) { - return GetQueue(pool_type).Dequeue(task); -} - void TaskScheduler::ExecuteForever(atomic *marker) { ExecuteForever(marker, TaskSchedulerType::REGULAR); } -bool TaskScheduler::TryDequeueAndProcessTask(const DBConfig &config, TaskSchedulerQueue &queue, +bool TaskScheduler::TryDequeueAndProcessTask(const DBConfig &config, TaskSchedulerQueue &queue, ConsumerToken &consumer, shared_ptr &task) { - if (queue.Dequeue(task)) { + if (queue.Dequeue(consumer, task)) { auto process_mode = TaskExecutionMode::PROCESS_ALL; if (Settings::Get(config)) { process_mode = TaskExecutionMode::PROCESS_PARTIAL; @@ -164,6 +160,7 @@ void TaskScheduler::ExecuteForever(atomic *marker, const TaskSchedulerType const auto &block_allocator = BlockAllocator::Get(db); const auto &config = DBConfig::GetConfig(db); auto &pool = GetPool(pool_type); + ConsumerToken consumer(queues); shared_ptr task; // loop until the marker is set to false @@ -194,12 +191,12 @@ void TaskScheduler::ExecuteForever(atomic *marker, const TaskSchedulerType if (pool_type == TaskSchedulerType::REGULAR) { // Regular thread pool picks up tasks from all pools for (auto &queue : queues) { - if (TryDequeueAndProcessTask(config, *queue, task)) { + if (TryDequeueAndProcessTask(config, *queue, consumer, task)) { break; } } } else { - TryDequeueAndProcessTask(config, GetQueue(pool_type), task); + TryDequeueAndProcessTask(config, GetQueue(pool_type), consumer, task); } } // this thread will exit, flush all of its outstanding allocations @@ -215,11 +212,12 @@ void TaskScheduler::ExecuteForever(atomic *marker, const TaskSchedulerType idx_t TaskScheduler::ExecuteTasks(atomic *marker, idx_t max_tasks) { #ifndef DUCKDB_NO_THREADS + ConsumerToken consumer(queues); idx_t completed_tasks = 0; // loop until the marker is set to false while (*marker && completed_tasks < max_tasks) { shared_ptr task; - if (!GetTaskInternal(task)) { + if (!GetTaskInternal(consumer, task)) { return completed_tasks; } auto execute_result = task->Execute(TaskExecutionMode::PROCESS_ALL); @@ -246,10 +244,11 @@ idx_t TaskScheduler::ExecuteTasks(atomic *marker, idx_t max_tasks) { void TaskScheduler::ExecuteTasks(idx_t max_tasks) { #ifndef DUCKDB_NO_THREADS + ConsumerToken consumer(queues); shared_ptr task; for (idx_t i = 0; i < max_tasks; i++) { GetPool(TaskSchedulerType::REGULAR).Wait(TASK_TIMEOUT_USECS); - if (!GetTaskInternal(task)) { + if (!GetTaskInternal(consumer, task)) { return; } try { diff --git a/src/duckdb/src/parallel/task_scheduler_queue.cpp b/src/duckdb/src/parallel/task_scheduler_queue.cpp index 36839f328..ef1d15112 100644 --- a/src/duckdb/src/parallel/task_scheduler_queue.cpp +++ b/src/duckdb/src/parallel/task_scheduler_queue.cpp @@ -13,7 +13,11 @@ TaskSchedulerType TaskSchedulerQueue::GetPoolType() { } #ifndef DUCKDB_NO_THREADS -typedef duckdb_moodycamel::ConcurrentQueue> concurrent_queue_t; +struct TaskQueueTraits : public duckdb_moodycamel::ConcurrentQueueDefaultTraits { + //! Consumers move on to the next producer after this many tasks (default: 256) + static constexpr uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 16; +}; +typedef duckdb_moodycamel::ConcurrentQueue, TaskQueueTraits> concurrent_queue_t; struct ConcurrentQueueWrapper { concurrent_queue_t q; @@ -28,6 +32,15 @@ struct QueueProducerToken { duckdb_moodycamel::ProducerToken token; }; +struct QueueConsumerToken { +public: + explicit QueueConsumerToken(TaskSchedulerQueue &queue) : token(queue.GetQueue().q) { + } + +public: + duckdb_moodycamel::ConsumerToken token; +}; + TaskSchedulerQueue::TaskSchedulerQueue(TaskSchedulerType pool_type_p) : pool_type(pool_type_p), queue(make_uniq()) { } @@ -70,8 +83,8 @@ bool TaskSchedulerQueue::DequeueFromProducerLocked(ProducerToken &token, shared_ return true; } -bool TaskSchedulerQueue::Dequeue(shared_ptr &task) { - if (!queue->q.try_dequeue(task)) { +bool TaskSchedulerQueue::Dequeue(ConsumerToken &token, shared_ptr &task) { + if (!queue->q.try_dequeue(token.GetQueueConsumerToken(pool_type).token, task)) { return false; } --tasks_in_queue; @@ -112,6 +125,12 @@ struct QueueProducerToken { TaskSchedulerQueue *queue; }; +struct QueueConsumerToken { +public: + explicit QueueConsumerToken(TaskSchedulerQueue &) { + } +}; + TaskSchedulerQueue::TaskSchedulerQueue(TaskSchedulerType pool_type_p) : pool_type(pool_type_p) { } @@ -152,7 +171,7 @@ bool TaskSchedulerQueue::DequeueFromProducerLocked(ProducerToken &token, shared_ return true; } -bool TaskSchedulerQueue::Dequeue(shared_ptr &task) { +bool TaskSchedulerQueue::Dequeue(ConsumerToken &token, shared_ptr &task) { throw InternalException("Global dequeue not supported for no threads queue"); } @@ -206,4 +225,17 @@ QueueProducerToken &ProducerToken::GetQueueProducerToken(TaskSchedulerType pool_ return *tokens[static_cast(pool_type)]; } +ConsumerToken::ConsumerToken(array, TASK_SCHEDULER_TYPE_COUNT> &queues) { + for (uint8_t i = 0; i < TASK_SCHEDULER_TYPE_COUNT; i++) { + tokens[i] = make_uniq(*queues[i]); + } +} + +ConsumerToken::~ConsumerToken() { +} + +QueueConsumerToken &ConsumerToken::GetQueueConsumerToken(TaskSchedulerType pool_type) { + return *tokens[static_cast(pool_type)]; +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/expression/cast_expression.cpp b/src/duckdb/src/parser/expression/cast_expression.cpp index f0a54714d..fb93183c7 100644 --- a/src/duckdb/src/parser/expression/cast_expression.cpp +++ b/src/duckdb/src/parser/expression/cast_expression.cpp @@ -56,9 +56,15 @@ unique_ptr CastExpression::Deserialize(Deserializer &deseriali deserializer.ReadPropertyWithExplicitDefault>(203, "type_expr", nullptr); if (type_expression) { + if (type_expression->GetExpressionClass() != ExpressionClass::TYPE) { + throw SerializationException("Failed to deserialize CAST expression: the target is not a type expression"); + } result->cast_type = unique_ptr_cast(std::move(type_expression)); } else { // written by a version that stored the target as a LogicalType + if (cast_type.id() == LogicalTypeId::INVALID) { + throw SerializationException("Failed to deserialize CAST expression: missing target type"); + } result->cast_type = TypeExpression::FromLogicalType(cast_type); } return std::move(result); diff --git a/src/duckdb/src/parser/expression/function_expression.cpp b/src/duckdb/src/parser/expression/function_expression.cpp index 8ae9eafd8..bd15e9955 100644 --- a/src/duckdb/src/parser/expression/function_expression.cpp +++ b/src/duckdb/src/parser/expression/function_expression.cpp @@ -82,9 +82,9 @@ string FunctionExpression::ToString() const { } result += StringUtil::Join(arguments, arguments.size(), ", ", [&](const FunctionArgument &child) { return child.ToString(); }); - // ordered aggregate + // ordered aggregate - WITHIN GROUP is only accepted for mode, other aggregates take the ORDER BY as an argument if (order_bys && !order_bys->orders.empty()) { - if (arguments.empty()) { + if (arguments.empty() && qualified_name.Name() == Identifier("mode")) { result += ") WITHIN GROUP ("; } result += " ORDER BY "; diff --git a/src/duckdb/src/parser/expression/type_expression.cpp b/src/duckdb/src/parser/expression/type_expression.cpp index 21ce59632..4d88b98bc 100644 --- a/src/duckdb/src/parser/expression/type_expression.cpp +++ b/src/duckdb/src/parser/expression/type_expression.cpp @@ -48,89 +48,103 @@ TypeExpression::TypeExpression(const string &type_name, vectorToString() + "[]"; - } - if (result.empty() && type_name == "ARRAY" && params.size() == 2) { - auto &type_param = params[0]; - auto &size_param = params[1]; - return type_param->ToString() + "[" + size_param->ToString() + "]"; - } - // So does STRUCT, MAP and UNION - if (result.empty() && type_name == "STRUCT") { - if (params.empty()) { - return "STRUCT"; - } - string struct_result = "STRUCT("; - for (idx_t i = 0; i < params.size(); i++) { - struct_result += SQLIdentifier(params[i]->GetAlias()) + " " + params[i]->ToString(); - if (i < params.size() - 1) { - struct_result += ", "; - } - } - struct_result += ")"; - return struct_result; - } - if (result.empty() && type_name == "UNION") { - if (params.empty()) { - return "UNION"; - } - string union_result = "UNION("; - for (idx_t i = 0; i < params.size(); i++) { - union_result += SQLIdentifier(params[i]->GetAlias()) + " " + params[i]->ToString(); - if (i < params.size() - 1) { - union_result += ", "; - } - } - union_result += ")"; - return union_result; - } +struct StackItem { + optional_ptr node; + string text; +}; - if (result.empty() && type_name == "MAP" && params.size() == 2) { - return "MAP(" + params[0]->ToString() + ", " + params[1]->ToString() + ")"; +static void PushChild(vector &stack, const ParsedExpression &child) { + if (child.GetExpressionClass() == ExpressionClass::TYPE) { + stack.push_back(StackItem {&child.Cast(), ""}); + } else { + stack.push_back(StackItem {nullptr, child.ToString()}); } +} - if (result.empty() && type_name == "VARCHAR" && !params.empty()) { - if (params.back()->HasAlias() && params.back()->GetAlias() == "collation") { - // Special case for VARCHAR with collation - auto &collate_expr = params.back()->Cast(); - return StringUtil::Format("VARCHAR COLLATE %s", - SQLIdentifier(collate_expr.GetLiteral().ToValue().ToString())); - } - } +string TypeExpression::ToString() const { + string output; + vector stack; + stack.push_back(StackItem {this, ""}); - if (result.empty() && type_name == "INTERVAL" && !params.empty()) { - // We ignore interval types parameters. - return "INTERVAL"; - } + while (!stack.empty()) { + auto item = std::move(stack.back()); + stack.pop_back(); - auto type_id = TransformStringToLogicalTypeId(type_name.GetIdentifierName()); - if (type_id != LogicalTypeId::UNBOUND && type_id != LogicalTypeId::SQLNULL) { - // Built-in type name - result += type_name.GetIdentifierName(); - } else { - result += SQLIdentifier(type_name); - } + if (!item.node) { + output += item.text; + } else { + auto &node = *item.node; + auto &type_name = node.qualified_name.Name(); + auto qual = node.qualified_name.QualificationToString(); + auto ¶ms = node.children; - if (!params.empty()) { - result += "("; - for (idx_t i = 0; i < params.size(); i++) { - result += params[i]->ToString(); - if (i < params.size() - 1) { - result += ", "; + // LIST and ARRAY have special syntax + if (qual.empty() && type_name == "LIST" && params.size() == 1) { + stack.push_back(StackItem {nullptr, "[]"}); + PushChild(stack, *params[0]); + } else if (qual.empty() && type_name == "ARRAY" && params.size() == 2) { + stack.push_back(StackItem {nullptr, "]"}); + PushChild(stack, *params[1]); + stack.push_back(StackItem {nullptr, "["}); + PushChild(stack, *params[0]); + } else if (qual.empty() && (type_name == "STRUCT" || type_name == "UNION")) { + // STRUCT and UNION share the NAME(alias type, ...) syntax + string head = type_name == "STRUCT" ? "STRUCT" : "UNION"; + if (params.empty()) { + stack.push_back(StackItem {nullptr, head}); + continue; + } + stack.push_back(StackItem {nullptr, ")"}); + for (idx_t i = params.size(); i > 0; i--) { + auto &curr_param = *params[i - 1]; + PushChild(stack, curr_param); + stack.push_back(StackItem {nullptr, SQLIdentifier(curr_param.GetAlias()) + " "}); + if (i > 1) { + stack.push_back(StackItem {nullptr, ", "}); + } + } + stack.push_back(StackItem {nullptr, head + "("}); + } else if (qual.empty() && type_name == "MAP" && params.size() == 2) { + stack.push_back(StackItem {nullptr, ")"}); + PushChild(stack, *params[1]); + stack.push_back(StackItem {nullptr, ", "}); + PushChild(stack, *params[0]); + stack.push_back(StackItem {nullptr, "MAP("}); + } else if (qual.empty() && type_name == "VARCHAR" && !params.empty() && params.back()->HasAlias() && + params.back()->GetAlias() == "collation") { + // Special case for VARCHAR with collation + auto &collate_expr = params.back()->Cast(); + stack.push_back(StackItem { + nullptr, StringUtil::Format("VARCHAR COLLATE %s", + SQLIdentifier(collate_expr.GetLiteral().ToValue().ToString()))}); + } else if (qual.empty() && type_name == "INTERVAL" && !params.empty()) { + // We ignore interval types parameters. + stack.push_back(StackItem {nullptr, "INTERVAL"}); + } else { + string result = qual; + auto type_id = TransformStringToLogicalTypeId(type_name.GetIdentifierName()); + if (type_id != LogicalTypeId::UNBOUND && type_id != LogicalTypeId::SQLNULL) { + // Built-in type name + result += type_name.GetIdentifierName(); + } else { + result += SQLIdentifier(type_name); + } + if (params.empty()) { + stack.push_back(StackItem {nullptr, result}); + continue; + } + stack.push_back(StackItem {nullptr, ")"}); + for (idx_t i = params.size(); i > 0; i--) { + PushChild(stack, *params[i - 1]); + if (i > 1) { + stack.push_back(StackItem {nullptr, ", "}); + } + } + stack.push_back(StackItem {nullptr, result + "("}); } } - result += ")"; } - return result; + return output; } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/parser/literal.cpp b/src/duckdb/src/parser/literal.cpp index 0e553627b..6f68c67c1 100644 --- a/src/duckdb/src/parser/literal.cpp +++ b/src/duckdb/src/parser/literal.cpp @@ -145,6 +145,11 @@ static Value NumberToValue(const string &val) { str_val.GetSize() - num_underscores < Decimal::MAX_WIDTH_DECIMAL + decimal_offset) { // figure out the width/scale based on the decimal position auto width = NumericCast(str_val.GetSize() - 1 - num_underscores); + // a well-formed decimal has a single '.', so its position never sits past the width; a second (e.g. trailing) + // dot pushes decimal_position beyond width and would underflow the scale computation below + if (decimal_position.GetIndex() > NumericCast(width) + num_integer_underscores) { + throw ParserException("Could not convert string \"%s\" to a decimal value", val); + } auto scale = NumericCast(width - decimal_position.GetIndex() + num_integer_underscores); if (val[0] == '-') { width--; diff --git a/src/duckdb/src/parser/parsed_data/alter_table_function_info.cpp b/src/duckdb/src/parser/parsed_data/alter_table_function_info.cpp index e0ed3608e..5b89fa358 100644 --- a/src/duckdb/src/parser/parsed_data/alter_table_function_info.cpp +++ b/src/duckdb/src/parser/parsed_data/alter_table_function_info.cpp @@ -19,9 +19,10 @@ CatalogType AlterTableFunctionInfo::GetCatalogType() const { //===--------------------------------------------------------------------===// // AddTableFunctionOverloadInfo //===--------------------------------------------------------------------===// -AddTableFunctionOverloadInfo::AddTableFunctionOverloadInfo(const AlterEntryData &data, TableFunctionSet new_overloads_p) +AddTableFunctionOverloadInfo::AddTableFunctionOverloadInfo(const AlterEntryData &data, TableFunctionSet new_overloads_p, + vector new_descriptions_p) : AlterTableFunctionInfo(AlterTableFunctionType::ADD_FUNCTION_OVERLOADS, data), - new_overloads(std::move(new_overloads_p)) { + new_overloads(std::move(new_overloads_p)), new_descriptions(std::move(new_descriptions_p)) { this->allow_internal = true; } @@ -29,7 +30,8 @@ AddTableFunctionOverloadInfo::~AddTableFunctionOverloadInfo() { } unique_ptr AddTableFunctionOverloadInfo::Copy() const { - return make_uniq_base(GetAlterEntryData(), new_overloads); + return make_uniq_base(GetAlterEntryData(), new_overloads, + new_descriptions); } string AddTableFunctionOverloadInfo::ToString() const { diff --git a/src/duckdb/src/parser/parsed_data/create_table_function_info.cpp b/src/duckdb/src/parser/parsed_data/create_table_function_info.cpp index c05f8532b..3a44eb103 100644 --- a/src/duckdb/src/parser/parsed_data/create_table_function_info.cpp +++ b/src/duckdb/src/parser/parsed_data/create_table_function_info.cpp @@ -30,7 +30,7 @@ unique_ptr CreateTableFunctionInfo::Copy() const { unique_ptr CreateTableFunctionInfo::GetAlterInfo() const { return make_uniq_base( - AlterEntryData(GetQualifiedName(), OnEntryNotFound::RETURN_NULL), functions); + AlterEntryData(GetQualifiedName(), OnEntryNotFound::RETURN_NULL), functions, descriptions); } } // namespace duckdb diff --git a/src/duckdb/src/parser/parsed_data/extra_drop_info.cpp b/src/duckdb/src/parser/parsed_data/extra_drop_info.cpp index 681842f08..cc0681f40 100644 --- a/src/duckdb/src/parser/parsed_data/extra_drop_info.cpp +++ b/src/duckdb/src/parser/parsed_data/extra_drop_info.cpp @@ -13,7 +13,8 @@ unique_ptr ExtraDropTriggerInfo::Copy() const { return make_uniq(*this); } -ExtraDropSecretInfo::ExtraDropSecretInfo() : ExtraDropInfo(ExtraDropInfoType::SECRET_INFO) { +ExtraDropSecretInfo::ExtraDropSecretInfo() + : ExtraDropInfo(ExtraDropInfoType::SECRET_INFO), persist_mode(SecretPersistType::DEFAULT) { } ExtraDropSecretInfo::ExtraDropSecretInfo(const ExtraDropSecretInfo &info) diff --git a/src/duckdb/src/parser/parsed_expression.cpp b/src/duckdb/src/parser/parsed_expression.cpp index e6ff5f1f4..0d06b8e57 100644 --- a/src/duckdb/src/parser/parsed_expression.cpp +++ b/src/duckdb/src/parser/parsed_expression.cpp @@ -62,4 +62,114 @@ bool ParsedExpression::ListEquals(const vector> &le return ExpressionUtil::ListEquals(left, right); } +static bool IsComparisonType(ExpressionType type) { + switch (type) { + case ExpressionType::COMPARE_EQUAL: + case ExpressionType::COMPARE_NOTEQUAL: + case ExpressionType::COMPARE_LESSTHAN: + case ExpressionType::COMPARE_GREATERTHAN: + case ExpressionType::COMPARE_LESSTHANOREQUALTO: + case ExpressionType::COMPARE_GREATERTHANOREQUALTO: + case ExpressionType::COMPARE_DISTINCT_FROM: + case ExpressionType::COMPARE_NOT_DISTINCT_FROM: + return true; + default: + return false; + } +} + +//! Whether an OperatorExpression of the given type can have the given number of children +static bool IsValidOperator(ExpressionType type, idx_t child_count) { + switch (type) { + case ExpressionType::COMPARE_IN: + case ExpressionType::COMPARE_NOT_IN: + return child_count >= 2; + case ExpressionType::OPERATOR_UNPACK: + case ExpressionType::OPERATOR_TRY: + case ExpressionType::OPERATOR_NOT: + case ExpressionType::OPERATOR_IS_NULL: + case ExpressionType::OPERATOR_IS_NOT_NULL: + return child_count == 1; + case ExpressionType::GROUPING_FUNCTION: + return true; + case ExpressionType::OPERATOR_COALESCE: + return child_count >= 1; + case ExpressionType::ARRAY_EXTRACT: + case ExpressionType::STRUCT_EXTRACT: + return child_count == 2; + case ExpressionType::ARRAY_SLICE: + return child_count == 3 || child_count == 4; + case ExpressionType::ARRAY_CONSTRUCTOR: + return true; + default: + return IsComparisonType(type) && child_count == 2; + } +} + +void ParsedExpression::VerifyDeserialized() { + auto invalid = [&](const string &reason) { + return SerializationException("Failed to deserialize %s expression: %s", + EnumUtil::ToString(GetExpressionClass()), reason); + }; + for (auto &child : ChildrenMutable()) { + if (!child) { + throw invalid("missing child expression"); + } + } + switch (GetExpressionClass()) { + case ExpressionClass::COLUMN_REF: + if (Cast().ColumnNames().empty()) { + throw invalid("no column names"); + } + break; + case ExpressionClass::COMPARISON: + if (!IsComparisonType(GetExpressionType())) { + throw invalid("invalid comparison type " + EnumUtil::ToString(GetExpressionType())); + } + break; + case ExpressionClass::CONJUNCTION: + if (GetExpressionType() != ExpressionType::CONJUNCTION_AND && + GetExpressionType() != ExpressionType::CONJUNCTION_OR) { + throw invalid("invalid conjunction type " + EnumUtil::ToString(GetExpressionType())); + } + if (Cast().GetChildren().empty()) { + throw invalid("no children"); + } + break; + case ExpressionClass::OPERATOR: + if (!IsValidOperator(GetExpressionType(), Cast().GetChildren().size())) { + throw invalid("invalid operator type " + EnumUtil::ToString(GetExpressionType()) + " for " + + to_string(Cast().GetChildren().size()) + " children"); + } + break; + case ExpressionClass::CASE: + if (Cast().CaseChecks().empty()) { + throw invalid("no WHEN clauses"); + } + break; + case ExpressionClass::FUNCTION: + if (!Cast().OrderBy()) { + throw invalid("missing ORDER BY modifier"); + } + break; + case ExpressionClass::SUBQUERY: { + auto &subquery = Cast(); + if (!subquery.Subquery() || !subquery.Subquery()->node) { + throw invalid("missing subquery"); + } + if ((subquery.GetSubqueryType() == SubqueryType::ANY) != (subquery.GetChild() != nullptr)) { + throw invalid("a child is required for, and only for, ANY subqueries"); + } + if (subquery.GetSubqueryType() == SubqueryType::INVALID) { + throw invalid("invalid subquery type"); + } + break; + } + case ExpressionClass::LAMBDA_REF: + throw invalid("lambda references cannot be deserialized"); + default: + break; + } +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/parsed_expression_iterator.cpp b/src/duckdb/src/parser/parsed_expression_iterator.cpp index 71adf9882..2f5887a02 100644 --- a/src/duckdb/src/parser/parsed_expression_iterator.cpp +++ b/src/duckdb/src/parser/parsed_expression_iterator.cpp @@ -113,9 +113,15 @@ void ParsedExpressionIterator::EnumerateTableRefChildren( expr_callback(tf_ref.function); break; } + case TableReferenceType::SHOW_REF: { + auto &show_ref = ref.Cast(); + if (show_ref.query) { + EnumerateQueryNodeChildren(*show_ref.query, expr_callback, ref_callback); + } + break; + } case TableReferenceType::BASE_TABLE: case TableReferenceType::EMPTY_FROM: - case TableReferenceType::SHOW_REF: case TableReferenceType::COLUMN_DATA: case TableReferenceType::DELIM_GET: // these TableRefs do not need to be unfolded @@ -156,7 +162,9 @@ void ParsedExpressionIterator::EnumerateQueryNodeChildren( expr_callback(sel_node.qualify); } - EnumerateTableRefChildren(*sel_node.from_table.get(), expr_callback, ref_callback); + if (sel_node.from_table) { + EnumerateTableRefChildren(*sel_node.from_table, expr_callback, ref_callback); + } break; } case QueryNodeType::SET_OPERATION_NODE: { diff --git a/src/duckdb/src/parser/peg/compiled_grammar.cpp b/src/duckdb/src/parser/peg/compiled_grammar.cpp index a497c1756..623048f7e 100644 --- a/src/duckdb/src/parser/peg/compiled_grammar.cpp +++ b/src/duckdb/src/parser/peg/compiled_grammar.cpp @@ -131,7 +131,8 @@ shared_ptr CompiledGrammar::Create(const vector(rule.name, rule.transform_process, rule.collapsible)); + rules.emplace(rule.name, make_uniq(rule.name, rule.transform_process, rule.collapsible, + rule.expression_depth, rule.suppress_suggestions)); } MatcherAllocator allocator; diff --git a/src/duckdb/src/parser/peg/dialect_extension.cpp b/src/duckdb/src/parser/peg/dialect_extension.cpp index 6d866c3d4..76756d34a 100644 --- a/src/duckdb/src/parser/peg/dialect_extension.cpp +++ b/src/duckdb/src/parser/peg/dialect_extension.cpp @@ -40,7 +40,8 @@ shared_ptr DialectExtension::GetCompiledGrammar(const ClientCon compiled_rules_map_t rules; for (auto &entry : parsed_grammar.rules) { auto &rule = *entry.second; - rules.emplace(rule.name, make_uniq(rule.name, rule.transform_process, rule.collapsible)); + rules.emplace(rule.name, make_uniq(rule.name, rule.transform_process, rule.collapsible, + rule.expression_depth, rule.suppress_suggestions)); } auto terminal_rule_overrides = parsed_grammar.BuildTerminalRuleOverrides(*keyword_helper); CreateMatcherFactoryInput matcher_factory_input {allocator, parsed_grammar, rules, *keyword_helper, diff --git a/src/duckdb/src/parser/peg/grammar_change.cpp b/src/duckdb/src/parser/peg/grammar_change.cpp index 9bc7bc94a..40b2e4361 100644 --- a/src/duckdb/src/parser/peg/grammar_change.cpp +++ b/src/duckdb/src/parser/peg/grammar_change.cpp @@ -5,15 +5,18 @@ namespace duckdb { GrammarChange GrammarChange::Create(GrammarChangeType type, string rule_name, string definition, grammar_transform_process_function_t transform_process, grammar_cursor_function_t find_cursor, - terminal_rule_matcher_factory_t matcher_factory, bool collapsible) { + terminal_rule_matcher_factory_t matcher_factory, bool collapsible, + bool suppress_suggestions) { return GrammarChange(type, std::move(rule_name), std::move(definition), std::move(transform_process), - std::move(find_cursor), std::move(matcher_factory), collapsible); + std::move(find_cursor), std::move(matcher_factory), collapsible, suppress_suggestions); } GrammarChange GrammarChange::AddRule(const string &rule_definition, - grammar_transform_process_function_t transform_process) { + grammar_transform_process_function_t transform_process, + bool suppress_suggestions) { auto rule = ParsedGrammar::ParseSingleRule(rule_definition); - return Create(GrammarChangeType::ADD_RULE, std::move(rule.name), rule_definition, std::move(transform_process)); + return Create(GrammarChangeType::ADD_RULE, std::move(rule.name), rule_definition, std::move(transform_process), + nullptr, nullptr, false, suppress_suggestions); } GrammarChange GrammarChange::AddChoice(const string &rule_name, const string &choice, @@ -43,9 +46,9 @@ GrammarChange GrammarChange::ReplaceRule(const string &rule_definition, GrammarChange GrammarChange::SetTransformProcess(const string &rule_name, grammar_transform_process_function_t transform_process, - bool collapsible) { + bool collapsible, bool suppress_suggestions) { return Create(GrammarChangeType::SET_TRANSFORM, rule_name, string(), std::move(transform_process), nullptr, nullptr, - collapsible); + collapsible, suppress_suggestions); } GrammarChange GrammarChange::AddTerminalRuleOverride(const string &rule_name, @@ -57,7 +60,7 @@ GrammarChange GrammarChange::AddTerminalRuleOverride(const string &rule_name, void GrammarChange::Apply(ParsedGrammar &grammar) const { switch (type) { case GrammarChangeType::ADD_RULE: - grammar.AddRule(definition, transform_process); + grammar.AddRule(definition, transform_process, suppress_suggestions); break; case GrammarChangeType::ADD_CHOICE: grammar.AddChoice(rule_name, definition, find_cursor); @@ -75,7 +78,7 @@ void GrammarChange::Apply(ParsedGrammar &grammar) const { grammar.ReplaceRule(definition, transform_process); break; case GrammarChangeType::SET_TRANSFORM: - grammar.SetTransformProcess(rule_name, transform_process, collapsible); + grammar.SetTransformProcess(rule_name, transform_process, collapsible, suppress_suggestions); break; case GrammarChangeType::ADD_TERMINAL_RULE_OVERRIDE: grammar.AddTerminalRuleOverride(rule_name, matcher_factory); diff --git a/src/duckdb/src/parser/peg/matcher_factory.cpp b/src/duckdb/src/parser/peg/matcher_factory.cpp index f9f5487d7..77e1ca472 100644 --- a/src/duckdb/src/parser/peg/matcher_factory.cpp +++ b/src/duckdb/src/parser/peg/matcher_factory.cpp @@ -169,7 +169,7 @@ Matcher &MatcherFactory::CreateMatcher(string_t rule_name, vector().suppress_suggestions = true; } return matcher; @@ -233,8 +233,6 @@ Matcher &MatcherFactory::CreateRootMatcher(const string &root_rule) { AddRuleOverride(entry.first.c_str(), std::move(entry.second)); } - // suppress suggestions for catch-all rules that would pollute statement-level autocomplete - SuppressSuggestions("ExpressionStatement"); // Using SHOW to describe a table/query is deprecated - parse these forms but do not autocomplete them after SHOW // (only setting names and the special SHOW forms are offered). DESCRIBE/SUMMARIZE still complete tables and // queries. diff --git a/src/duckdb/src/parser/peg/matcher_stack.cpp b/src/duckdb/src/parser/peg/matcher_stack.cpp index 42a35db62..16653c385 100644 --- a/src/duckdb/src/parser/peg/matcher_stack.cpp +++ b/src/duckdb/src/parser/peg/matcher_stack.cpp @@ -17,7 +17,13 @@ MatchStack::~MatchStack() { void MatchStack::DestroyTopFrame() { D_ASSERT(!frames.empty()); + auto &frame = frames.back(); + auto &process_allocator = frame.match_state.context.process_allocator; + const auto process_arena_position = frame.process_arena_position; frames.pop_back(); + // the processes of a frame and everything they allocated are dead once the frame is popped - release them so the + // process arena only ever holds the live frames instead of every process the whole statement ever created + process_allocator.Rewind(process_arena_position); } optional PackratMatchState::TryLoadCachedResult(const Matcher &matcher, MatchState &state) { @@ -79,6 +85,7 @@ MatcherResult MatchStack::ExecuteAtomicMatcher(MatchInput input) { void MatchStack::PushFrame(MatchInput input) { input.state.rule = input.matcher.GetRule(); frames.emplace_back(input); + frames.back().process_arena_position = input.state.context.process_allocator.GetPosition(); } void MatchStack::InitializeFrame(MatchStackFrame &frame) { diff --git a/src/duckdb/src/parser/peg/parsed_grammar.cpp b/src/duckdb/src/parser/peg/parsed_grammar.cpp index 3612eaa2e..48628a0dc 100644 --- a/src/duckdb/src/parser/peg/parsed_grammar.cpp +++ b/src/duckdb/src/parser/peg/parsed_grammar.cpp @@ -104,9 +104,11 @@ void ParsedGrammar::AddParsedRule(ParsedGrammarRule rule) { rules.emplace(std::move(name), make_uniq(std::move(rule))); } -void ParsedGrammar::AddRule(const string &rule_definition, grammar_transform_process_function_t transform_process) { +void ParsedGrammar::AddRule(const string &rule_definition, grammar_transform_process_function_t transform_process, + bool suppress_suggestions) { auto rule = ParseSingleRule(rule_definition); rule.transform_process = std::move(transform_process); + rule.suppress_suggestions = suppress_suggestions; AddParsedRule(std::move(rule)); } @@ -218,10 +220,15 @@ void ParsedGrammar::ReplaceRule(const string &rule_definition, grammar_transform } void ParsedGrammar::SetTransformProcess(const string &rule_name, grammar_transform_process_function_t transform_process, - bool collapsible) { + bool collapsible, bool suppress_suggestions) { auto &rule = GetMutableRule(rule_name); rule.transform_process = std::move(transform_process); rule.collapsible = collapsible; + rule.suppress_suggestions = suppress_suggestions; +} + +void ParsedGrammar::SetExpressionDepth(const string &rule_name, ExpressionDepthKind kind) { + GetMutableRule(rule_name).expression_depth = kind; } void ParsedGrammar::AddTerminalRuleOverride(const string &rule_name, terminal_rule_matcher_factory_t matcher_factory) { diff --git a/src/duckdb/src/parser/peg/sql_formatter.cpp b/src/duckdb/src/parser/peg/sql_formatter.cpp index 0e3df4d07..af672832b 100644 --- a/src/duckdb/src/parser/peg/sql_formatter.cpp +++ b/src/duckdb/src/parser/peg/sql_formatter.cpp @@ -36,6 +36,10 @@ class SQLFormatter { static bool IsClauseKeywordLine(const string &trimmed); static bool ShouldUppercase(const string &kw); + // Token helpers + static vector Tokenize(const string &sql); + static bool SkipNonCodeToken(const vector &tokens, idx_t &token_idx, idx_t &pos); + // Token lookahead helpers static string PeekKeyword(const vector &tokens, idx_t i, idx_t offset); static idx_t DetectCompoundClause(const vector &tokens, idx_t i, string &compound_text, @@ -87,7 +91,7 @@ class SQLFormatter { SQLFormatter::SQLFormatter(const FormatterConfig &config) : config(config) { } -string SQLFormatter::Format(const string &sql) { +vector SQLFormatter::Tokenize(const string &sql) { auto &keyword_helper = DuckDBKeywordHelper::Instance(); vector tokens; HighlightTokenizerBehavior behavior(sql, tokens); @@ -100,6 +104,42 @@ string SQLFormatter::Format(const string &sql) { } } + return tokens; +} + +bool SQLFormatter::SkipNonCodeToken(const vector &tokens, idx_t &token_idx, idx_t &pos) { + while (token_idx < tokens.size() && tokens[token_idx].offset + tokens[token_idx].length <= pos) { + token_idx++; + } + if (token_idx == tokens.size()) { + return false; + } + auto &token = tokens[token_idx]; + if (token.offset > pos) { + return false; + } + bool quoted_identifier = + token.type == TokenType::IDENTIFIER && !token.text.empty() && (token.text[0] == '"' || token.text[0] == '`'); + if (token.type != TokenType::STRING_LITERAL && token.type != TokenType::COMMENT && !quoted_identifier) { + return false; + } + auto length = token.length; + // Keep the newline ending a line comment as a formatting boundary. + if (token.type == TokenType::COMMENT && StringUtil::StartsWith(token.text, "--")) { + while (length > 0 && (token.text[length - 1] == '\n' || token.text[length - 1] == '\r')) { + length--; + } + } + if (token.offset + length <= pos) { + return false; + } + pos = token.offset + length - 1; + return true; +} + +string SQLFormatter::Format(const string &sql) { + auto tokens = Tokenize(sql); + if (tokens.empty()) { return sql; } @@ -327,9 +367,14 @@ string SQLFormatter::TrimLeft(const string &line) { } vector SQLFormatter::SplitLines(const string &formatted) { + auto tokens = Tokenize(formatted); + idx_t token_idx = 0; vector lines; idx_t start = 0; for (idx_t k = 0; k <= formatted.size(); k++) { + if (SkipNonCodeToken(tokens, token_idx, k)) { + continue; + } if (k == formatted.size() || formatted[k] == '\n') { lines.push_back(formatted.substr(start, k - start)); start = k + 1; @@ -866,6 +911,9 @@ string SQLFormatter::MergeShortClauses(const string &formatted) const { break; } content_end = j + 1; + if (cline.find('\n') != string::npos) { + break; + } j++; } @@ -1028,19 +1076,15 @@ string SQLFormatter::CollapseFirstCondition(const string &formatted) const { vector SQLFormatter::SplitTopLevelCommas(const string &s) { vector parts; int32_t depth = 0; - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(s); + idx_t token_idx = 0; idx_t start = 0; for (idx_t k = 0; k < s.size(); k++) { const char c = s[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - } else if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; - } else if (c == '(') { + if (SkipNonCodeToken(tokens, token_idx, k)) { + continue; + } + if (c == '(') { depth++; } else if (c == ')') { depth--; @@ -1081,18 +1125,14 @@ vector SQLFormatter::SplitTopLevelCommas(const string &s) { //! Returns string::npos if not found. idx_t SQLFormatter::FindMatchingClose(const string &s, idx_t open_pos) { int32_t depth = 0; - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(s); + idx_t token_idx = 0; for (idx_t k = open_pos; k < s.size(); k++) { const char c = s[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - } else if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; - } else if (c == '(') { + if (SkipNonCodeToken(tokens, token_idx, k)) { + continue; + } + if (c == '(') { depth++; } else if (c == ')') { depth--; @@ -1165,7 +1205,12 @@ string SQLFormatter::ExpandTableDefinition(const string &formatted) const { } idx_t paren_pos = string::npos; + auto tokens = Tokenize(line); + idx_t token_idx = 0; for (idx_t k = indent + prefix_len; k < line.size(); k++) { + if (SkipNonCodeToken(tokens, token_idx, k)) { + continue; + } if (line[k] == '(') { paren_pos = k; break; @@ -1192,7 +1237,12 @@ string SQLFormatter::ExpandTableDefinition(const string &formatted) const { // Case A: keyword on line i, "tablename(cols)" on line i+1. const string &next_line = lines[i + 1]; idx_t next_paren = string::npos; + auto next_tokens = Tokenize(next_line); + idx_t next_token_idx = 0; for (idx_t k = 0; k < next_line.size(); k++) { + if (SkipNonCodeToken(next_tokens, next_token_idx, k)) { + continue; + } if (next_line[k] == '(') { next_paren = k; break; @@ -1278,19 +1328,10 @@ bool SQLFormatter::MatchKeywordAt(const string &s, idx_t pos, const char *keywor //! skipping string literals. Does NOT skip paren-enclosed content //! so that CASE inside function calls (e.g. sum(CASE...)) is found. idx_t SQLFormatter::FindKeywordAny(const string &s, idx_t from_pos, const char *keyword) { - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(s); + idx_t token_idx = 0; for (idx_t k = from_pos; k < s.size(); k++) { - const char c = s[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - continue; - } - if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; + if (SkipNonCodeToken(tokens, token_idx, k)) { continue; } if (MatchKeywordAt(s, k, keyword)) { @@ -1304,21 +1345,11 @@ idx_t SQLFormatter::FindKeywordAny(const string &s, idx_t from_pos, const char * //! CASE...END pairs. Returns string::npos if no matching END is found. idx_t SQLFormatter::FindCaseEnd(const string &s, idx_t case_pos) { int32_t depth = 0; - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(s); + idx_t token_idx = 0; idx_t k = case_pos; while (k < s.size()) { - const char c = s[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - k++; - continue; - } - if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; + if (SkipNonCodeToken(tokens, token_idx, k)) { k++; continue; } @@ -1347,8 +1378,8 @@ vector SQLFormatter::SplitCaseBranches(const string &content) { vector branches; int32_t paren_depth = 0; int32_t case_depth = 0; - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(content); + idx_t token_idx = 0; idx_t start = 0; while (start < content.size() && content[start] == ' ') { @@ -1358,16 +1389,7 @@ vector SQLFormatter::SplitCaseBranches(const string &content) { while (k < content.size()) { const char c = content[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - k++; - continue; - } - if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; + if (SkipNonCodeToken(tokens, token_idx, k)) { k++; continue; } @@ -1528,20 +1550,12 @@ string SQLFormatter::ExpandCaseExpressions(const string &formatted) const { idx_t SQLFormatter::FindLongestParenList(const string &line, idx_t from_pos) { idx_t best_pos = string::npos; idx_t best_len = 0; - bool in_str = false; - char str_char = '\0'; + auto tokens = Tokenize(line); + idx_t token_idx = 0; int32_t depth = 0; for (idx_t k = from_pos; k < line.size(); k++) { char c = line[k]; - if (in_str) { - if (c == str_char) { - in_str = false; - } - continue; - } - if (c == '\'' || c == '"' || c == '`') { - in_str = true; - str_char = c; + if (SkipNonCodeToken(tokens, token_idx, k)) { continue; } if (c == '(' && depth == 0) { diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer.cpp index 329ec74ed..f2330f956 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer.cpp @@ -127,8 +127,29 @@ TransformStackFrame::TransformStackFrame(TransformInput input) TransformStack::TransformStack(PEGTransformer &transformer_p) : transformer(transformer_p) { } +TransformStack::~TransformStack() { + // release the expression depth of frames that remain after an exception + while (!frames.empty()) { + PopFrame(); + } +} + void TransformStack::PushFrame(TransformInput input) { frames.emplace(input); + auto &frame = frames.top(); + auto weight = transformer.ExpressionDepthWeight(frame.rule, frame.parse_result); + if (weight) { + // recorded first: the frame is released (and the weight subtracted) when entering throws + frame.expression_depth_weight = weight; + transformer.EnterExpression(weight); + } +} + +void TransformStack::PopFrame() { + if (frames.top().expression_depth_weight) { + transformer.ExitExpression(frames.top().expression_depth_weight); + } + frames.pop(); } void TransformStack::InitializeFrame(TransformStackFrame &frame) { @@ -165,7 +186,7 @@ arena_ptr TransformStack::Execute(TransformInput input) { continue; } transformer.SetResultLocation(frame.parse_result, *result); - frames.pop(); + PopFrame(); if (frames.empty()) { return result; } @@ -203,6 +224,59 @@ arena_ptr PEGTransformer::TransformInternal(ParseResult &p return stack.Execute(input); } +//! The number of repeated elements directly in a rule's match: the links of an operator chain +static idx_t CountChainLinks(ParseResult &parse_result) { + switch (parse_result.type) { + case ParseResultType::REPEAT: + return parse_result.Cast().GetChildren().size(); + case ParseResultType::OPTIONAL: { + auto &optional = parse_result.Cast(); + return optional.HasResult() ? CountChainLinks(optional.GetResultUnsafe()) : 0; + } + case ParseResultType::LIST: { + idx_t links = 0; + for (auto &child : parse_result.Cast().GetChildren()) { + auto type = child.get().type; + if (type == ParseResultType::REPEAT || type == ParseResultType::OPTIONAL) { + links += CountChainLinks(child.get()); + } + } + return links; + } + default: + return 0; + } +} + +idx_t PEGTransformer::ExpressionDepthWeight(optional_ptr rule, ParseResult &parse_result) { + if (!rule) { + return 0; + } + switch (rule->expression_depth) { + case ExpressionDepthKind::NESTING: + return 1; + case ExpressionDepthKind::CHAIN: + return CountChainLinks(parse_result); + default: + return 0; + } +} + +void PEGTransformer::EnterExpression(idx_t weight) { + // the transformer does not recurse, but binding the resulting expressions does - reject them early + expression_depth += weight; + if (expression_depth > options.max_expression_depth) { + throw ParserException("Max expression depth limit of %lld exceeded. Use \"SET max_expression_depth TO x\" to " + "increase the maximum expression depth.", + options.max_expression_depth); + } +} + +void PEGTransformer::ExitExpression(idx_t weight) { + D_ASSERT(expression_depth >= weight); + expression_depth -= weight; +} + const CompiledGrammarRule &PEGTransformer::GetRule(const string &rule_name) const { auto rule = grammar.GetRule(rule_name); if (!rule) { diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp index f16dd5817..cc687f4b9 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp @@ -157,6 +157,32 @@ PEGTransformerFactory::PEGTransformerFactory(ParsedGrammar &grammar_p) : grammar }, collapsible_rules.count(entry.first) > 0); } + //===--------------------------------------------------------------------===// + // START GENERATED EXPRESSION DEPTH RULES + //===--------------------------------------------------------------------===// + grammar.SetExpressionDepth("SingleExpression", ExpressionDepthKind::NESTING); + grammar.SetExpressionDepth("Type", ExpressionDepthKind::NESTING); + grammar.SetExpressionDepth("LambdaArrowExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("LogicalOrExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("LogicalAndExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("NotExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("IsExpressionContinuation", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("IsDistinctFromExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("ComparisonExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("BetweenInLikeExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("InfixOtherOperatorExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("BitwiseExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("TildeExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("AdditiveExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("MultiplicativeExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("ExponentiationExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("CollateExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("AtTimeZoneExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("PrefixExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("IndirectionList", ExpressionDepthKind::CHAIN); + //===--------------------------------------------------------------------===// + // END GENERATED EXPRESSION DEPTH RULES + //===--------------------------------------------------------------------===// } void PEGTransformerFactory::RegisterDefaultTransforms(ParsedGrammar &grammar) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp index d36f4f87b..9d4023599 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp @@ -224,8 +224,7 @@ string PEGTransformerFactory::TransformDotColLabel(PEGTransformer &transformer, ConstraintColumnDefinition PEGTransformerFactory::TransformColumnDefinition( PEGTransformer &transformer, const Identifier &identifier, const optional &type, - optional generated_column, const bool &has_result, - optional> column_constraint) { + optional generated_column, optional> column_constraint) { auto qualified_name = QualifiedName(identifier); bool has_type = type.has_value(); bool has_generated = generated_column && generated_column->expr != nullptr; @@ -469,6 +468,13 @@ ColumnConstraintEntry PEGTransformerFactory::TransformUniqueConstraint(PEGTransf return entry; } +// Column constraint names are parsed but not stored +ColumnConstraintEntry PEGTransformerFactory::TransformNameableColumnConstraint(PEGTransformer &transformer, + const bool &has_result, + ColumnConstraintEntry child) { + return child; +} + bool PEGTransformerFactory::TransformNullConstraint(PEGTransformer &transformer) { return false; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp index 6265acfc9..42e080ed7 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp @@ -110,60 +110,6 @@ static unique_ptr TransformParserFunctionRewrite(const string return nullptr; } -unique_ptr -PEGTransformerFactory::TransformExpressionStatement(PEGTransformer &transformer, - vector> expression_alias) { - auto expressions = std::move(expression_alias); - auto select_statement = make_uniq(); - auto select_node = make_uniq(); - - bool any_column_ref = false; - for (auto &expr : expressions) { - if (expr->GetExpressionClass() == ExpressionClass::COLUMN_REF) { - any_column_ref = true; - break; - } - } - - if (any_column_ref && expressions.size() > 1) { - throw ParserException("Mix of table names and expressions is not supported. " - "Use SELECT to explicitly specify what you want to query."); - } - - if (any_column_ref) { - // Single COLUMN_REF: treat as table scan - auto &col_expr = expressions[0]->Cast(); - if (col_expr.IsQualified()) { - if (col_expr.ColumnNames().size() >= 4) { - throw ParserException( - "Too many qualifiers encountered. Expected \"catalog.schema.table\" or \"schema.table\""); - } - if (col_expr.ColumnNames().size() == 3) { - auto table_description = TableDescription( - QualifiedName(col_expr.ColumnNames()[0], col_expr.ColumnNames()[1], col_expr.ColumnNames()[2])); - select_node->from_table = make_uniq(table_description); - } else if (col_expr.ColumnNames().size() == 2) { - auto table_description = - TableDescription(QualifiedName({col_expr.ColumnNames()[0]}, col_expr.ColumnNames()[1])); - select_node->from_table = make_uniq(table_description); - } - } else { - auto base_table = make_uniq(); - base_table->SetTable(col_expr.GetColumnName()); - select_node->from_table = std::move(base_table); - } - select_node->select_list.push_back(make_uniq()); - } else { - for (auto &expr : expressions) { - select_node->select_list.push_back(std::move(expr)); - } - select_node->from_table = make_uniq(); - } - - select_statement->node = std::move(select_node); - return std::move(select_statement); -} - unique_ptr PEGTransformerFactory::TransformBaseExpression(PEGTransformer &transformer, unique_ptr single_expression, @@ -723,6 +669,7 @@ PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer if (!not_expression) { return expr; } + auto not_depth_guard = transformer.StackCheck(not_expression->size()); for (idx_t i = 0; i < not_expression->size(); i++) { vector> inner_list_children; inner_list_children.push_back(std::move(expr)); diff --git a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp index 69cba4061..44549a67b 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp @@ -233,12 +233,6 @@ static const TransformFrameOps STRING_LITERAL_VALUE_OPS = { static const TransformFrameOps ANALYZE_KEYWORD_OPS = {"AnalyzeKeyword", &PEGTransformerFactory::InitializeAnalyzeKeywordTrampoline, &PEGTransformerFactory::FinalizeAnalyzeKeywordTrampoline}; -static const TransformFrameOps EXPRESSION_STATEMENT_OPS = { - "ExpressionStatement", &PEGTransformerFactory::InitializeExpressionStatementTrampoline, - &PEGTransformerFactory::FinalizeExpressionStatementTrampoline}; -static const TransformFrameOps EXPRESSION_ALIAS_OPS = {"ExpressionAlias", - &PEGTransformerFactory::InitializeExpressionAliasTrampoline, - &PEGTransformerFactory::FinalizeExpressionAliasTrampoline}; static const TransformFrameOps INDEX_NAME_OPS = {"IndexName", &PEGTransformerFactory::InitializeIndexNameTrampoline, &PEGTransformerFactory::FinalizeIndexNameTrampoline}; static const TransformFrameOps CONSTRAINT_NAME_OPS = {"ConstraintName", @@ -881,6 +875,12 @@ static const TransformFrameOps COLUMN_DEFINITION_OPS = {"ColumnDefinition", static const TransformFrameOps COLUMN_CONSTRAINT_OPS = {"ColumnConstraint", &PEGTransformerFactory::InitializeColumnConstraintTrampoline, &PEGTransformerFactory::FinalizeColumnConstraintTrampoline}; +static const TransformFrameOps NAMEABLE_COLUMN_CONSTRAINT_OPS = { + "NameableColumnConstraint", &PEGTransformerFactory::InitializeNameableColumnConstraintTrampoline, + &PEGTransformerFactory::FinalizeNameableColumnConstraintTrampoline}; +static const TransformFrameOps COLUMN_CONSTRAINT_ELEMENT_OPS = { + "ColumnConstraintElement", &PEGTransformerFactory::InitializeColumnConstraintElementTrampoline, + &PEGTransformerFactory::FinalizeColumnConstraintElementTrampoline}; static const TransformFrameOps NOT_NULL_CONSTRAINT_OPS = {"NotNullConstraint", &PEGTransformerFactory::InitializeNotNullConstraintTrampoline, &PEGTransformerFactory::FinalizeNotNullConstraintTrampoline}; @@ -3133,8 +3133,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CommentValue", &COMMENT_VALUE_OPS}, {"StringLiteralValue", &STRING_LITERAL_VALUE_OPS}, {"AnalyzeKeyword", &ANALYZE_KEYWORD_OPS}, - {"ExpressionStatement", &EXPRESSION_STATEMENT_OPS}, - {"ExpressionAlias", &EXPRESSION_ALIAS_OPS}, {"IndexName", &INDEX_NAME_OPS}, {"ConstraintName", &CONSTRAINT_NAME_OPS}, {"SequenceName", &SEQUENCE_NAME_OPS}, @@ -3365,6 +3363,8 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CreateTableConstraint", &CREATE_TABLE_CONSTRAINT_OPS}, {"ColumnDefinition", &COLUMN_DEFINITION_OPS}, {"ColumnConstraint", &COLUMN_CONSTRAINT_OPS}, + {"NameableColumnConstraint", &NAMEABLE_COLUMN_CONSTRAINT_OPS}, + {"ColumnConstraintElement", &COLUMN_CONSTRAINT_ELEMENT_OPS}, {"NotNullConstraint", &NOT_NULL_CONSTRAINT_OPS}, {"NullConstraint", &NULL_CONSTRAINT_OPS}, {"NotNullColumnConstraint", &NOT_NULL_COLUMN_CONSTRAINT_OPS}, @@ -5515,53 +5515,6 @@ PEGTransformerFactory::FinalizeAnalyzeKeywordTrampoline(PEGTransformer &transfor return transformer.MakeResult(result); } -void PEGTransformerFactory::InitializeExpressionStatementTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = list_items.size(); - process.ReserveChildSlots(1 + dynamic_child_count - 1); - for (idx_t i = list_items.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("ExpressionAlias"), list_items[child_idx].get()}, 0 + child_idx); - } -} - -arena_ptr -PEGTransformerFactory::FinalizeExpressionStatementTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = dynamic_list_items.size(); - vector> expression_alias; - for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - expression_alias.push_back(process.TakeResult>(i)); - } - auto result = TransformExpressionStatement(transformer, std::move(expression_alias)); - return transformer.MakeResult>(std::move(result)); -} - -void PEGTransformerFactory::InitializeExpressionAliasTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); - process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); - } - process.PushChild({*child_rule, choice_result}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeExpressionAliasTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult>(0); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeIndexNameTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { process.ReserveChildSlots(0); @@ -9958,7 +9911,7 @@ PEGTransformerFactory::FinalizeCreateTableConstraintTrampoline(PEGTransformer &t void PEGTransformerFactory::InitializeColumnDefinitionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &repeat_opt = list_pr.GetChild(4).Cast(); + auto &repeat_opt = list_pr.GetChild(3).Cast(); idx_t dynamic_child_count = 0; if (repeat_opt.HasResult()) { auto &repeat_pr = repeat_opt.GetResult().Cast(); @@ -9988,7 +9941,7 @@ PEGTransformerFactory::FinalizeColumnDefinitionTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); idx_t dynamic_child_count = 0; - auto &dynamic_repeat_opt = list_pr.GetChild(4).Cast(); + auto &dynamic_repeat_opt = list_pr.GetChild(3).Cast(); if (dynamic_repeat_opt.HasResult()) { auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); @@ -10003,9 +9956,6 @@ PEGTransformerFactory::FinalizeColumnDefinitionTrampoline(PEGTransformer &transf if (process.child_results[1]) { generated_column = process.TakeResult(1); } - bool has_result {}; - auto &has_result_opt = list_pr.GetChild(3).Cast(); - has_result = has_result_opt.HasResult(); optional> column_constraint {}; if (dynamic_child_count > 0) { vector column_constraint_value; @@ -10014,7 +9964,7 @@ PEGTransformerFactory::FinalizeColumnDefinitionTrampoline(PEGTransformer &transf } column_constraint = std::move(column_constraint_value); } - auto result = TransformColumnDefinition(transformer, identifier, type, std::move(generated_column), has_result, + auto result = TransformColumnDefinition(transformer, identifier, type, std::move(generated_column), std::move(column_constraint)); return transformer.MakeResult(std::move(result)); } @@ -10040,6 +9990,46 @@ PEGTransformerFactory::FinalizeColumnConstraintTrampoline(PEGTransformer &transf return transformer.MakeResult(std::move(result)); } +void PEGTransformerFactory::InitializeNameableColumnConstraintTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("ColumnConstraintElement"), list_pr.GetChild(1)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeNameableColumnConstraintTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + bool has_result {}; + auto &has_result_opt = list_pr.GetChild(0).Cast(); + has_result = has_result_opt.HasResult(); + auto column_constraint_element = process.TakeResult(0); + auto result = TransformNameableColumnConstraint(transformer, has_result, std::move(column_constraint_element)); + return transformer.MakeResult(std::move(result)); +} + +void PEGTransformerFactory::InitializeColumnConstraintElementTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeColumnConstraintElementTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult(0); + return transformer.MakeResult(std::move(result)); +} + void PEGTransformerFactory::InitializeNotNullConstraintTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); diff --git a/src/duckdb/src/parser/qualified_name.cpp b/src/duckdb/src/parser/qualified_name.cpp index 9ce4f609e..7b4a5f19b 100644 --- a/src/duckdb/src/parser/qualified_name.cpp +++ b/src/duckdb/src/parser/qualified_name.cpp @@ -48,27 +48,30 @@ string QualifiedName::ToString(QualifiedNameToStringMode mode) const { //! characters like ()'- and keywords without requiring double quotes. It only requires double quotes around . //! characters and doubled double quotes (which collapse into a single double quote). It's only possible to fully //! double quote a component or not quote it at all. -vector QualifiedName::ParseComponents(const string &input) { - vector result; +static bool TryParseComponents(const string &input, vector &result, string &error) { idx_t idx = 0; while (idx < input.size()) { string entry; if (input[idx] == '"') { if (!StringUtil::TryParseQuotedString(input, idx, entry)) { - throw ParserException("Unterminated quote in qualified name! (input: %s)", input); + error = StringUtil::Format("Unterminated quote in qualified name! (input: %s)", input); + return false; } if (entry.empty()) { - throw ParserException("Zero-length delimited identifier in qualified name! (input: %s)", input); + error = StringUtil::Format("Zero-length delimited identifier in qualified name! (input: %s)", input); + return false; } if (idx < input.size() && input[idx] != '.') { - throw ParserException("Unexpected character after a quoted identifier in a qualified name! (input: %s)", - input); + error = StringUtil::Format( + "Unexpected character after a quoted identifier in a qualified name! (input: %s)", input); + return false; } } else { for (; idx < input.size() && input[idx] != '.'; idx++) { if (input[idx] == '"') { - throw ParserException("Unexpected quote in the middle of a qualified name component! (input: %s)", - input); + error = StringUtil::Format( + "Unexpected quote in the middle of a qualified name component! (input: %s)", input); + return false; } entry += input[idx]; } @@ -78,6 +81,15 @@ vector QualifiedName::ParseComponents(const string &input) { idx++; } } + return true; +} + +vector QualifiedName::ParseComponents(const string &input) { + vector result; + string error; + if (!TryParseComponents(input, result, error)) { + throw ParserException(error); + } return result; } @@ -111,6 +123,25 @@ QualifiedName QualifiedName::Parse(const string &input) { return FromPath(ParseComponents(input)); } +bool QualifiedName::TryParse(const string &input, QualifiedName &result, string &error) { + vector components; + if (!TryParseComponents(input, components, error)) { + return false; + } + if (components.empty()) { + error = StringUtil::Format("Zero-length identifier in qualified name! (input: %s)", input); + return false; + } + for (auto &component : components) { + if (component.empty()) { + error = StringUtil::Format("Zero-length identifier in qualified name! (input: %s)", input); + return false; + } + } + result = FromPath(std::move(components)); + return true; +} + QualifiedColumnName::QualifiedColumnName() { } QualifiedColumnName::QualifiedColumnName(Identifier column_p) : column(std::move(column_p)) { diff --git a/src/duckdb/src/parser/query_node.cpp b/src/duckdb/src/parser/query_node.cpp index dc7c3a0b2..623878f0a 100644 --- a/src/duckdb/src/parser/query_node.cpp +++ b/src/duckdb/src/parser/query_node.cpp @@ -5,6 +5,8 @@ #include "duckdb/parser/query_node/recursive_cte_node.hpp" #include "duckdb/parser/query_node/cte_node.hpp" #include "duckdb/common/limits.hpp" +#include "duckdb/parser/common_table_expression_info.hpp" +#include "duckdb/parser/result_modifier.hpp" namespace duckdb { CommonTableExpressionMap::CommonTableExpressionMap() { @@ -186,4 +188,61 @@ void QueryNode::AddDistinct() { modifiers.push_back(make_uniq()); } +void QueryNode::VerifyDeserialized(const unique_ptr &node) { + if (!node) { + throw SerializationException("Failed to deserialize query node: missing query node"); + } + for (auto &modifier : node->modifiers) { + if (!modifier) { + throw SerializationException("Failed to deserialize query node: missing result modifier"); + } + if (modifier->type == ResultModifierType::ORDER_MODIFIER) { + for (auto &order : modifier->Cast().orders) { + if (!order.expression) { + throw SerializationException("Failed to deserialize query node: missing ORDER BY expression"); + } + } + } else if (modifier->type == ResultModifierType::DISTINCT_MODIFIER) { + for (auto &target : modifier->Cast().distinct_on_targets) { + if (!target) { + throw SerializationException("Failed to deserialize query node: missing DISTINCT ON expression"); + } + } + } + } + for (auto &cte : node->cte_map.map) { + if (!cte.second || !cte.second->query_node) { + throw SerializationException("Failed to deserialize query node: missing CTE query"); + } + } + switch (node->type) { + case QueryNodeType::SELECT_NODE: { + auto &select_node = node->Cast(); + if (select_node.select_list.empty()) { + throw SerializationException("Failed to deserialize query node: empty select list"); + } + for (auto &expr : select_node.select_list) { + if (!expr) { + throw SerializationException("Failed to deserialize query node: missing select list expression"); + } + } + for (auto &expr : select_node.groups.group_expressions) { + if (!expr) { + throw SerializationException("Failed to deserialize query node: missing group expression"); + } + } + break; + } + case QueryNodeType::RECURSIVE_CTE_NODE: { + auto &cte_node = node->Cast(); + if (!cte_node.left || !cte_node.right) { + throw SerializationException("Failed to deserialize query node: missing recursive CTE child"); + } + break; + } + default: + break; + } +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/query_node/set_operation_node.cpp b/src/duckdb/src/parser/query_node/set_operation_node.cpp index a6c3808ee..8ec3f43ad 100644 --- a/src/duckdb/src/parser/query_node/set_operation_node.cpp +++ b/src/duckdb/src/parser/query_node/set_operation_node.cpp @@ -87,6 +87,12 @@ SetOperationNode::SetOperationNode(SetOperationType setop_type, unique_ptr 2 && setop_type != SetOperationType::UNION && setop_type != SetOperationType::UNION_BY_NAME) { + throw SerializationException("SetOperationNode must have exactly two children, except for UNION"); + } + if (setop_type == SetOperationType::NONE) { + throw SerializationException("SetOperationNode must have a set operation type"); + } for (auto &child : children) { if (!child) { throw SerializationException("SetOperationNode children cannot be NULL"); diff --git a/src/duckdb/src/parser/tableref.cpp b/src/duckdb/src/parser/tableref.cpp index 4817d7a01..912dfa179 100644 --- a/src/duckdb/src/parser/tableref.cpp +++ b/src/duckdb/src/parser/tableref.cpp @@ -4,6 +4,8 @@ #include "duckdb/parser/keyword_helper.hpp" #include "duckdb/common/enum_util.hpp" #include "duckdb/common/to_string.hpp" +#include "duckdb/parser/statement/select_statement.hpp" +#include "duckdb/parser/tableref/list.hpp" namespace duckdb { @@ -75,4 +77,57 @@ bool TableRef::Equals(const unique_ptr &left, const unique_ptrEquals(*right); } +void TableRef::VerifyDeserialized() const { + auto invalid = [&](const string &reason) { + return SerializationException("Failed to deserialize %s table reference: %s", EnumUtil::ToString(type), reason); + }; + switch (type) { + case TableReferenceType::JOIN: { + auto &join_ref = Cast(); + if (!join_ref.left || !join_ref.right) { + throw invalid("missing join side"); + } + if (join_ref.ref_type == JoinRefType::REGULAR && !join_ref.condition && join_ref.using_columns.empty()) { + throw invalid("a regular join requires a condition or USING columns"); + } + break; + } + case TableReferenceType::SUBQUERY: { + auto &subquery_ref = Cast(); + if (!subquery_ref.subquery || !subquery_ref.subquery->node) { + throw invalid("missing subquery"); + } + break; + } + case TableReferenceType::PIVOT: { + auto &pivot_ref = Cast(); + if (!pivot_ref.source) { + throw invalid("missing source"); + } + if (pivot_ref.pivots.empty()) { + throw invalid("no PIVOT or UNPIVOT columns"); + } + break; + } + case TableReferenceType::TABLE_FUNCTION: { + auto &function = Cast().function; + if (!function || function->GetExpressionClass() != ExpressionClass::FUNCTION) { + throw invalid("the table function must be a function expression"); + } + break; + } + case TableReferenceType::EXPRESSION_LIST: + for (auto &row : Cast().values) { + for (auto &value : row) { + if (!value) { + throw invalid("missing value"); + } + } + } + break; + default: + break; + } +} + } // namespace duckdb diff --git a/src/duckdb/src/planner/binder.cpp b/src/duckdb/src/planner/binder.cpp index 760557ccc..bedc1860c 100644 --- a/src/duckdb/src/planner/binder.cpp +++ b/src/duckdb/src/planner/binder.cpp @@ -227,6 +227,19 @@ void Binder::AddBoundView(ViewCatalogEntry &view) { bound_views.insert(view); } +void Binder::AddBoundTableMacro(CatalogEntry &table_macro) { + // check if the table macro is already being expanded + auto current = this; + while (current) { + if (current->bound_table_macros.find(table_macro) != current->bound_table_macros.end()) { + throw BinderException("infinite recursion detected: attempting to recursively expand table macro %s", + table_macro.name); + } + current = current->parent.get(); + } + bound_table_macros.insert(table_macro); +} + TableIndex Binder::GenerateTableIndex() { return TableIndex(global_binder_state->bound_tables++); } @@ -600,12 +613,7 @@ BoundStatement Binder::BindReturning(vector> return projection->AddChild(std::move(child_operator)); D_ASSERT(result.types.size() == result.names.size()); result.plan = std::move(projection); - // If an insert/delete/update statement returns data, there are sometimes issues with streaming results - // where the data modification doesn't take place until the streamed result is exhausted. Once a row is - // returned, it should be guaranteed that the row has been inserted. - // see https://github.com/duckdb/duckdb/issues/8310 auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::QUERY_RESULT; return result; } diff --git a/src/duckdb/src/planner/binder/expression/bind_positional_reference_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_positional_reference_expression.cpp index fbb27206c..38f4d9dcf 100644 --- a/src/duckdb/src/planner/binder/expression/bind_positional_reference_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_positional_reference_expression.cpp @@ -7,10 +7,9 @@ namespace duckdb { BindResult ExpressionBinder::BindPositionalReference(unique_ptr &expr, idx_t depth, bool root_expression) { auto &ref = expr->Cast(); - if (depth != 0) { - throw InternalException("Positional reference expression could not be bound"); - } - // replace the positional reference with a column + // Resolve the position against this binder, which owns the positions. The reference can reach this point from an + // inner scope through alias expansion, e.g. "SELECT #2 AS x FROM t WHERE (SELECT x)" copies "#2" into the + // subquery; it then binds here at depth > 0 and must be resolved as a correlated reference to the outer position. auto column = binder.bind_context.PositionToColumn(ref); expr = std::move(column); return BindExpression(expr, depth, root_expression); diff --git a/src/duckdb/src/planner/binder/expression/bind_unnest_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_unnest_expression.cpp index 9ae278b80..02c930fa2 100644 --- a/src/duckdb/src/planner/binder/expression/bind_unnest_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_unnest_expression.cpp @@ -21,12 +21,23 @@ namespace duckdb { +static unique_ptr BindStructExtract(ClientContext &context, vector> arguments, + const LogicalType &child_type) { + auto result = BindBuiltinScalarExpression(context, StructExtractFun::Name, std::move(arguments)); + if (result->GetReturnType() != child_type) { + // a NULL input is folded into an untyped NULL constant + result = BoundCastExpression::AddCastToType(context, std::move(result), child_type); + } + return result; +} + static unique_ptr CreateBoundStructExtract(ClientContext &context, unique_ptr expr, - const vector &key_path, bool keep_parent_names) { + const LogicalType &child_type, const vector &key_path, + bool keep_parent_names) { vector> arguments; arguments.push_back(std::move(expr)); arguments.push_back(make_uniq(Value(key_path.back()))); - auto result = BindBuiltinScalarFunction(context, StructExtractFun::Name, std::move(arguments)); + auto result = BindStructExtract(context, std::move(arguments), child_type); if (keep_parent_names) { auto alias = StringUtil::Join(key_path, "."); @@ -37,18 +48,18 @@ static unique_ptr CreateBoundStructExtract(ClientContext &context, u } else { result->SetAlias(Identifier(key_path[0])); } - return std::move(result); + return result; } static unique_ptr CreateBoundStructExtractIndex(ClientContext &context, unique_ptr expr, - idx_t key) { + const LogicalType &child_type, idx_t key) { vector> arguments; arguments.push_back(std::move(expr)); arguments.push_back(make_uniq(Value::BIGINT(int64_t(key)))); - auto result = BindBuiltinScalarFunction(context, StructExtractFun::Name, std::move(arguments)); + auto result = BindStructExtract(context, std::move(arguments), child_type); result->SetAlias(Identifier("element" + to_string(key))); - return std::move(result); + return result; } void SelectBinder::ThrowIfUnnestInLambda(const ColumnBinding &column_binding) { @@ -153,7 +164,13 @@ BindResult UnnestBinder::Bind(FunctionExpression &function, idx_t depth, bool ro if (error.HasError()) { return BindResult(std::move(error)); } + if (!const_child->IsFoldable()) { + throw BinderException(function, "UNNEST parameter %s must be a constant", alias); + } auto value = ExpressionExecutor::EvaluateScalar(context, *const_child, true); + if (value.IsNull()) { + throw BinderException(function, "UNNEST parameter %s cannot be NULL", alias); + } if (alias == "recursive") { auto recursive = value.GetValue(); if (recursive) { @@ -281,8 +298,8 @@ BindResult UnnestBinder::Bind(FunctionExpression &function, idx_t depth, bool ro auto &child_types = StructType::GetChildTypes(expr->GetReturnType()); if (expr->GetReturnType().id() == LogicalTypeId::TUPLE) { for (idx_t child_index = 0; child_index < child_types.size(); child_index++) { - new_expressions.push_back( - CreateBoundStructExtractIndex(context, expr->Copy(), child_index + 1)); + new_expressions.push_back(CreateBoundStructExtractIndex( + context, expr->Copy(), child_types[child_index].second, child_index + 1)); } } else { for (auto &entry : child_types) { @@ -292,8 +309,8 @@ BindResult UnnestBinder::Bind(FunctionExpression &function, idx_t depth, bool ro current_key_path.emplace_back(expr->GetAlias()); } current_key_path.emplace_back(entry.first); - new_expressions.push_back( - CreateBoundStructExtract(context, expr->Copy(), current_key_path, keep_parent_names)); + new_expressions.push_back(CreateBoundStructExtract(context, expr->Copy(), entry.second, + current_key_path, keep_parent_names)); } } has_structs = true; diff --git a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp index 10a898ccd..77a0e953d 100644 --- a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp @@ -59,9 +59,11 @@ static LogicalType BindRangeExpression(ClientContext &context, const string &nam if (bound->GetReturnType() == LogicalType::SQLNULL) { throw BinderException(error_context, "Window RANGE expressions cannot be NULL"); } + // the function binder casts the operands and folds a constant offset to its value. The origin recognizes a + // constant offset in the bound call by value, so keep a copy of it - any other offset stays in place + unique_ptr offset_copy = origin && bound->IsFoldable() ? bound->Copy() : nullptr; + optional_ptr offset = offset_copy ? offset_copy.get() : bound.get(); children.emplace_back(std::move(bound)); - optional_ptr order_input = children[0].get(); - optional_ptr offset_input = children[1].get(); FunctionBinder function_binder(context); auto function = @@ -72,7 +74,7 @@ static LogicalType BindRangeExpression(ClientContext &context, const string &nam throw BinderException(error_context, "Invalid type for Window RANGE expression"); } if (origin) { - *origin = WindowRangeBoundary::Capture(*function, order_input, offset_input); + *origin = WindowRangeBoundary::Capture(*function, *bound_order, *offset); } bound = std::move(function); return bound->GetReturnType(); @@ -368,7 +370,7 @@ BindResult BaseSelectBinder::BindWindowExpression(WindowExpression &window, idx_ } // Cast all three to match - optional_ptr original_order = bound_order.get(); + auto &original_order = *bound_order; bound_order = BoundCastExpression::AddCastToType(context, std::move(bound_order), order_type); if (!WindowRangeCast::Capture(*bound_order, original_order, result->SQLRangeOrderCastsMutable())) { result->SQLRangeStartBoundaryMutable().reset(); @@ -398,12 +400,12 @@ BindResult BaseSelectBinder::BindWindowExpression(WindowExpression &window, idx_ result->StartExprMutable() = CastWindowExpression(std::move(bound_start), start_type); result->EndExprMutable() = CastWindowExpression(std::move(bound_end), end_type); if (result->SQLRangeStartBoundary() && - !WindowRangeCast::Capture(*result->StartExpr(), original_start, + !WindowRangeCast::Capture(*result->StartExpr(), *original_start, result->SQLRangeStartBoundaryMutable()->result_casts)) { result->SQLRangeStartBoundaryMutable().reset(); } if (result->SQLRangeEndBoundary() && - !WindowRangeCast::Capture(*result->EndExpr(), original_end, + !WindowRangeCast::Capture(*result->EndExpr(), *original_end, result->SQLRangeEndBoundaryMutable()->result_casts)) { result->SQLRangeEndBoundaryMutable().reset(); } diff --git a/src/duckdb/src/planner/binder/query_node/bind_trigger_expansion.cpp b/src/duckdb/src/planner/binder/query_node/bind_trigger_expansion.cpp index 237cbc936..93461a6c0 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_trigger_expansion.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_trigger_expansion.cpp @@ -460,7 +460,6 @@ BoundStatement Binder::ExpandTriggers(QueryNode &node, TableCatalogEntry &table, if (!has_returning) { auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::CHANGED_ROWS; return chain; } @@ -533,7 +532,6 @@ BoundStatement Binder::ExpandTriggers(QueryNode &node, TableCatalogEntry &table, result.plan = std::move(returning_projection); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::QUERY_RESULT; return result; } @@ -720,7 +718,6 @@ BoundStatement Binder::ExpandRowTriggers(QueryNode &node, vector(std::move(stmt.info)); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_call.cpp b/src/duckdb/src/planner/binder/statement/bind_call.cpp index 9f602b671..28e8026a0 100644 --- a/src/duckdb/src/planner/binder/statement/bind_call.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_call.cpp @@ -15,10 +15,8 @@ BoundStatement Binder::Bind(CallStatement &stmt) { select_node->from_table = std::move(table_function); select_statement.node = std::move(select_node); - // CALL is `SELECT * FROM func()` (which already propagates call_return_type) forced to materialize. - auto result = Bind(select_statement); - GetStatementProperties().result_eagerness = ResultEagerness::FORCED; - return result; + // CALL is `SELECT * FROM func()`, which already propagates call_return_type + return Bind(select_statement); } } // namespace duckdb diff --git a/src/duckdb/src/planner/binder/statement/bind_connect.cpp b/src/duckdb/src/planner/binder/statement/bind_connect.cpp index b6f196b40..59f485d09 100644 --- a/src/duckdb/src/planner/binder/statement/bind_connect.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_connect.cpp @@ -37,7 +37,6 @@ BoundStatement Binder::Bind(ConnectStatement &stmt) { result.plan = make_uniq(std::move(stmt.info)); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; return result; } @@ -50,7 +49,6 @@ BoundStatement Binder::Bind(DisconnectStatement &stmt) { result.plan = make_uniq(std::move(stmt.info)); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_copy.cpp b/src/duckdb/src/planner/binder/statement/bind_copy.cpp index c62f134b1..756aa4d9e 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy.cpp @@ -105,15 +105,30 @@ identifier_map_t Binder::GetFullCopyOptionsList(const CopyFunction & } static idx_t ParseBytesArg(const Identifier &name, Value &arg) { + if (arg.IsNull()) { + throw BinderException("NULL is not supported as a valid option for COPY option \"%s\"", + StringUtil::Upper(name.GetIdentifierName())); + } + optional_idx result; if (arg.type().id() == LogicalTypeId::VARCHAR) { - return DBConfig::ParseMemoryLimit(arg.ToString()); + result = DBConfig::ParseMemoryLimit(arg.ToString()); + } else { + auto cast_arg = arg.DefaultTryCastAs(LogicalType::UBIGINT); + if (!cast_arg) { + throw BinderException("Unable to parse bytes from \"%s\" for copy option \"%s\" ", arg.ToString(), + StringUtil::Upper(name.GetIdentifierName())); + } + auto bytes = cast_arg->GetValue(); + if (bytes != DConstants::INVALID_INDEX) { + result = bytes; + } } - auto cast_arg = arg.DefaultTryCastAs(LogicalType::UBIGINT); - if (!cast_arg) { - throw BinderException("Unable to parse bytes from \"%s\" for copy option \"%s\" ", arg.ToString(), - StringUtil::Upper(name.GetIdentifierName())); + if (!result.IsValid()) { + // e.g. '-1' or 'none', which ParseMemoryLimit parses as unlimited + throw BinderException("Copy option \"%s\" must be a valid size, not \"%s\"", + StringUtil::Upper(name.GetIdentifierName()), arg.ToString()); } - return cast_arg->GetValue(); + return result.GetIndex(); } struct CopyToParsedOptions { @@ -462,7 +477,11 @@ BoundStatement Binder::BindCopyTo(CopyStatement &stmt, const CopyFunction &funct if (option_values.empty()) { throw BinderException("BATCH_SIZE/ROW_GROUP_SIZE cannot be empty"); } - parsed_options.batch_size = option_values[0].GetValue(); + auto batch_size = option_values[0].GetValue(); + if (batch_size == DConstants::INVALID_INDEX) { + throw BinderException("BATCH_SIZE/ROW_GROUP_SIZE value %llu is too large", batch_size); + } + parsed_options.batch_size = batch_size; } else if (option_name == "batch_size_bytes" || option_name == "row_group_size_bytes") { if (option_values.empty()) { throw BinderException("BATCH_SIZE_BYTES/ROW_GROUP_SIZE_BYTES cannot be empty"); @@ -480,7 +499,11 @@ BoundStatement Binder::BindCopyTo(CopyStatement &stmt, const CopyFunction &funct if (option_values.empty()) { throw BinderException("BATCHES_PER_FILE/ROW_GROUPS_PER_FILE cannot be empty"); } - parsed_options.batches_per_file = option_values[0].GetValue(); + auto batches_per_file = option_values[0].GetValue(); + if (batches_per_file == 0 || batches_per_file == DConstants::INVALID_INDEX) { + throw BinderException("BATCHES_PER_FILE/ROW_GROUPS_PER_FILE must be a positive number"); + } + parsed_options.batches_per_file = batches_per_file; } else if (option_name == "partition_by") { auto converted = ConvertVectorToValue(std::move(option_values)); parsed_options.partition_cols = ParseColumnsOrdered(converted, select_node.names, option_name); @@ -719,13 +742,7 @@ vector BindCopyOption(ClientContext &context, Binder &binder, const Ident return result; } } - const bool is_partition_by = name == "partition_by"; TableFunctionBinder option_binder(binder, context, "Copy", "Copy options", conversion_policy); - - if (is_partition_by) { - // Partition columns such as 'user' must not resolve to SQL value functions. - option_binder.DisableSQLValueFunctions(); - } auto bound_expr = option_binder.Bind(expr); if (bound_expr->HasParameter()) { throw ParameterNotResolvedException(); @@ -804,7 +821,7 @@ void Binder::BindCopyOptions(CopyInfo &info, const CopyFunction &function) { partition_path = std::move(option_expr); continue; } - // column list options accept bare column names + // column list options accept bare column names, also ones that spell a SQL value function such as user auto copy_option = copy_options.find(option_name); const bool is_column_list = copy_option != copy_options.end() && copy_option->second.type == LogicalType::LIST(LogicalType::VARCHAR); @@ -941,7 +958,6 @@ BoundStatement Binder::Bind(CopyStatement &stmt, CopyToType copy_to_type) { } auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::CHANGED_ROWS; if (stmt.info->is_from) { return BindCopyFrom(stmt, function); diff --git a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp index beb487b31..7596bfa3c 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp @@ -128,7 +128,6 @@ BoundStatement Binder::Bind(CopyDatabaseStatement &stmt) { result.plan = std::move(plan); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; DatabaseModificationType modification; diff --git a/src/duckdb/src/planner/binder/statement/bind_create.cpp b/src/duckdb/src/planner/binder/statement/bind_create.cpp index e46295169..954fd986f 100644 --- a/src/duckdb/src/planner/binder/statement/bind_create.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_create.cpp @@ -247,6 +247,9 @@ QualifiedName Binder::ResolveCatalog(CatalogEntryRetriever &retriever, const Qua QualifiedName Binder::BindTableName(CatalogEntryRetriever &retriever, const QualifiedName &name) { auto resolved = ResolveCatalog(retriever, name, false); auto &path = resolved.Path(); + if (path.size() < 2) { + throw InternalException("Binder::BindTableName called with an empty name"); + } vector schema_path(path.begin() + 1, path.end() - 1); if (schema_path.size() <= 1) { // unqualified, or qualified with a single schema level: keep the (catalog, schema, name) shape so that the @@ -1098,7 +1101,6 @@ BoundStatement Binder::Bind(CreateStatement &stmt) { } properties.return_type = return_type; - properties.result_eagerness = ResultEagerness::FORCED; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp index 31935a631..25be3c9ac 100644 --- a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp @@ -727,8 +727,6 @@ unique_ptr Binder::BindCreateTableInfo(unique_ptrtype != StatementType::SELECT_STATEMENT) { - throw NotImplementedException("EXPLAIN (SQL) supports SELECT, VALUES and WITH queries only"); + if (stmt.explain_type == ExplainType::EXPLAIN_SQL) { + switch (stmt.stmt->type) { + case StatementType::SELECT_STATEMENT: + case StatementType::INSERT_STATEMENT: + case StatementType::UPDATE_STATEMENT: + case StatementType::DELETE_STATEMENT: + case StatementType::MERGE_INTO_STATEMENT: + break; + default: + throw NotImplementedException( + "EXPLAIN (SQL) supports SELECT, VALUES, WITH, INSERT, UPDATE, DELETE and MERGE INTO statements only"); + } } // bind the underlying statement diff --git a/src/duckdb/src/planner/binder/statement/bind_export.cpp b/src/duckdb/src/planner/binder/statement/bind_export.cpp index 659b3bdf1..81f22f66f 100644 --- a/src/duckdb/src/planner/binder/statement/bind_export.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_export.cpp @@ -325,7 +325,6 @@ BoundStatement Binder::Bind(ExportStatement &stmt) { result.plan = std::move(export_node); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp index 743baba76..bb986b527 100644 --- a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp @@ -43,9 +43,7 @@ BoundStatement Binder::Bind(ExternalResourceStatement &stmt) { SelectStatement select_statement; select_statement.node = std::move(select_node); - auto show_result = Bind(select_statement); - GetStatementProperties().result_eagerness = ResultEagerness::FORCED; - return show_result; + return Bind(select_statement); } BoundStatement result; @@ -76,7 +74,6 @@ BoundStatement Binder::Bind(ExternalResourceStatement &stmt) { result.plan = make_uniq(std::move(data)); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::NOTHING; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_insert.cpp b/src/duckdb/src/planner/binder/statement/bind_insert.cpp index 830f0dc74..1d8513f95 100644 --- a/src/duckdb/src/planner/binder/statement/bind_insert.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_insert.cpp @@ -712,7 +712,6 @@ BoundStatement Binder::BindNode(InsertQueryNode &node) { result.plan = std::move(insert); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::CHANGED_ROWS; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_load.cpp b/src/duckdb/src/planner/binder/statement/bind_load.cpp index 291295f14..f0c8f6d4e 100644 --- a/src/duckdb/src/planner/binder/statement/bind_load.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_load.cpp @@ -31,7 +31,6 @@ BoundStatement Binder::Bind(LoadStatement &stmt) { result.plan = make_uniq(std::move(stmt.info)); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = load_type == LoadType::CREATE_REPOSITORY ? StatementReturnType::QUERY_RESULT : StatementReturnType::NOTHING; return result; diff --git a/src/duckdb/src/planner/binder/statement/bind_logical_plan.cpp b/src/duckdb/src/planner/binder/statement/bind_logical_plan.cpp index e1b5fe86f..e2a79696a 100644 --- a/src/duckdb/src/planner/binder/statement/bind_logical_plan.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_logical_plan.cpp @@ -26,7 +26,6 @@ BoundStatement Binder::Bind(LogicalPlanStatement &stmt) { result.plan = std::move(stmt.plan); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::AUTO; properties.return_type = StatementReturnType::QUERY_RESULT; // TODO could also be something else if (parent) { diff --git a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp index 08045d206..c053c9189 100644 --- a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp @@ -3,6 +3,7 @@ #include "duckdb/parser/query_node/merge_query_node.hpp" #include "duckdb/planner/tableref/bound_joinref.hpp" #include "duckdb/planner/operator/logical_get.hpp" +#include "duckdb/planner/operator/logical_join.hpp" #include "duckdb/planner/expression_binder/where_binder.hpp" #include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" #include "duckdb/planner/operator/logical_merge_into.hpp" @@ -334,8 +335,8 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { throw NotImplementedException("Expected a join after binding a join operator - but got a %s", join_ref.get().type); } - // kind of hacky, CreatePlan turns a RIGHT join into a LEFT join so the children get reversed from what we need - bool inverted = join.type == JoinType::RIGHT; + // CreatePlan can turn a RIGHT join into a LEFT join - in that case the children are reversed + bool inverted = join.type == JoinType::RIGHT && join_ref.get().Cast().join_type == JoinType::LEFT; auto &source = join_ref.get().children[inverted ? 1 : 0]; // bind WHEN_MATCHED merge actions (can contain references to both source and target) @@ -447,7 +448,6 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { result.types = {LogicalType::BIGINT}; auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::CHANGED_ROWS; return result; } diff --git a/src/duckdb/src/planner/binder/statement/bind_prepare.cpp b/src/duckdb/src/planner/binder/statement/bind_prepare.cpp index 74f0953cc..38d1d419b 100644 --- a/src/duckdb/src/planner/binder/statement/bind_prepare.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_prepare.cpp @@ -20,7 +20,6 @@ BoundStatement Binder::Bind(PrepareStatement &stmt) { // this is required because most clients ALWAYS invoke prepared statements auto &properties = GetStatementProperties(); properties.requires_valid_transaction = false; - properties.result_eagerness = ResultEagerness::FORCED; properties.bound_all_parameters = true; properties.parameter_count = 0; properties.return_type = StatementReturnType::NOTHING; diff --git a/src/duckdb/src/planner/binder/statement/bind_select.cpp b/src/duckdb/src/planner/binder/statement/bind_select.cpp index 15221a46a..0085a19b8 100644 --- a/src/duckdb/src/planner/binder/statement/bind_select.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_select.cpp @@ -7,7 +7,6 @@ namespace duckdb { BoundStatement Binder::Bind(SelectStatement &stmt) { auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::AUTO; properties.return_type = StatementReturnType::QUERY_RESULT; auto result = Bind(*stmt.node); // A bare table-function passthrough honors the function's call_return_type (e.g. a CONNECT-routed diff --git a/src/duckdb/src/planner/binder/statement/bind_update.cpp b/src/duckdb/src/planner/binder/statement/bind_update.cpp index b02a17a68..01ce1e195 100644 --- a/src/duckdb/src/planner/binder/statement/bind_update.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_update.cpp @@ -281,7 +281,6 @@ BoundStatement Binder::BindNode(UpdateQueryNode &node) { result.plan = std::move(update); auto &properties = GetStatementProperties(); - properties.result_eagerness = ResultEagerness::FORCED; properties.return_type = StatementReturnType::CHANGED_ROWS; return result; } diff --git a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp index 151cf7424..031e644aa 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp @@ -51,6 +51,10 @@ static void ConstructPivots(PivotRef &ref, vector &pivot_valu } else { new_value.name = std::move(name); } + if (new_value.name.empty()) { + // the pivot value is an empty string - column names cannot be empty + new_value.name = "''"; + } if (last_pivot) { pivot_values.push_back(std::move(new_value)); } else { diff --git a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp index e70652ff6..30f39a605 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp @@ -425,6 +425,7 @@ BoundStatement Binder::Bind(TableFunctionRef &ref) { auto binder = Binder::CreateBinder(context, this); binder->SetCanContainNulls(true); + binder->AddBoundTableMacro(macro_func); binder->alias = ref.alias.empty() ? "unnamed_query" : ref.alias; BoundStatement query; diff --git a/src/duckdb/src/planner/binder/tableref/plan_joinref.cpp b/src/duckdb/src/planner/binder/tableref/plan_joinref.cpp index c6b18f6b4..5cee18c17 100644 --- a/src/duckdb/src/planner/binder/tableref/plan_joinref.cpp +++ b/src/duckdb/src/planner/binder/tableref/plan_joinref.cpp @@ -372,16 +372,15 @@ static void PlanMarkJoin(LogicalOperator &op) { return; } bool all_equal = !join.conditions.empty(); - bool all_null_safe = all_equal; for (auto &condition : join.conditions) { if (!condition.IsComparison()) { - all_equal = all_null_safe = false; + all_equal = false; break; } all_equal &= condition.GetComparisonType() == ExpressionType::COMPARE_EQUAL; - all_null_safe &= condition.GetComparisonType() == ExpressionType::COMPARE_NOT_DISTINCT_FROM; } - if (all_equal || all_null_safe || (join.conditions.size() == 1 && join.conditions[0].IsComparison())) { + if (all_equal || join.HasNullSafeMarkJoinConditions() || + (join.conditions.size() == 1 && join.conditions[0].IsComparison())) { return; } } diff --git a/src/duckdb/src/planner/collation_binding.cpp b/src/duckdb/src/planner/collation_binding.cpp index 457f72a11..9bad73fe6 100644 --- a/src/duckdb/src/planner/collation_binding.cpp +++ b/src/duckdb/src/planner/collation_binding.cpp @@ -159,7 +159,8 @@ static bool PushStructCollation(ClientContext &context, unique_ptr & is_null->GetChildrenMutable().push_back(lambda_parameter_expr->Copy()); auto null_result = make_uniq(Value(result->GetReturnType())); auto body = make_uniq(std::move(is_null), std::move(null_result), std::move(result)); - auto lambda = make_uniq(ExpressionType::LAMBDA, LogicalType::LAMBDA, std::move(body), 1); + auto lambda = + make_uniq(ExpressionType::LAMBDA, LogicalType::LAMBDA, std::move(body), idx_t(1)); lambda->SetParameterNames({lambda_parameter}); // Invoke evaluates the source once, even when several fields need collations. diff --git a/src/duckdb/src/planner/expression/bound_cast_expression.cpp b/src/duckdb/src/planner/expression/bound_cast_expression.cpp index 101995c08..896f4c36b 100644 --- a/src/duckdb/src/planner/expression/bound_cast_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_cast_expression.cpp @@ -298,6 +298,11 @@ bool BoundCastExpression::CastCanThrow(const LogicalType &source_type, const Log if (source_type.id() == LogicalTypeId::BIGNUM) { return true; } + // Floating point values that are not finite (NaN, inf) cannot be represented as a BIGNUM + if (target_type.id() == LogicalTypeId::BIGNUM && + (source_type.id() == LogicalTypeId::FLOAT || source_type.id() == LogicalTypeId::DOUBLE)) { + return true; + } // Casts of nested types are executed on the child types - recurse into them if (source_type.IsNested() || target_type.IsNested()) { return NestedCastCanThrow(source_type, target_type); diff --git a/src/duckdb/src/planner/expression/bound_constant_expression.cpp b/src/duckdb/src/planner/expression/bound_constant_expression.cpp index 692e79df3..41f8a477a 100644 --- a/src/duckdb/src/planner/expression/bound_constant_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_constant_expression.cpp @@ -1,6 +1,8 @@ #include "duckdb/planner/expression/bound_constant_expression.hpp" #include "duckdb/common/types/hash.hpp" +#include "duckdb/common/type_visitor.hpp" #include "duckdb/common/value_operations/value_operations.hpp" +#include "duckdb/common/vector_operations/vector_operations.hpp" namespace duckdb { @@ -28,12 +30,24 @@ string BoundConstantExpression::ToString() const { return value.ToSQLString(); } +//! Two constants are the same expression if their values have the same representation. VARIANT values compare by +//! meaning - 1::TINYINT and 1::INTEGER are equal - but as constants they are different: variant_typeof tells them +//! apart, and they hash differently. Vectors compare a VARIANT as the STRUCT it is stored as. +static bool SameRepresentation(const Value &left, const Value &right) { + if (!TypeVisitor::Contains(left.type(), LogicalTypeId::VARIANT)) { + return !ValueOperations::DistinctFrom(left, right); + } + Vector left_vector(left, count_t(1)); + Vector right_vector(right, count_t(1)); + return VectorOperations::NotDistinctFrom(left_vector, right_vector, nullptr, 1, nullptr, nullptr) == 1; +} + bool BoundConstantExpression::Equals(const BaseExpression &other_p) const { if (!Expression::Equals(other_p)) { return false; } auto &other = other_p.Cast(); - return value.type() == other.value.type() && !ValueOperations::DistinctFrom(value, other.value); + return value.type() == other.value.type() && SameRepresentation(value, other.value); } hash_t BoundConstantExpression::Hash() const { diff --git a/src/duckdb/src/planner/expression/window_range_info.cpp b/src/duckdb/src/planner/expression/window_range_info.cpp index cfe8da279..2301b1f02 100644 --- a/src/duckdb/src/planner/expression/window_range_info.cpp +++ b/src/duckdb/src/planner/expression/window_range_info.cpp @@ -15,10 +15,15 @@ static bool HasDirectRangeArguments(const BoundScalarFunction &function) { return !properties.GetCaptureArgumentAliases() && !properties.RequiresExpressionNames(); } -bool WindowRangeCast::Capture(const Expression &expression, optional_ptr input, - vector &casts) { +bool WindowRangeCast::Capture(const Expression &expression, const Expression &operand, vector &casts) { optional_ptr current = expression; - while (current.get() != input.get()) { + // the operand is the expression itself when it is still in place, or a copy of it when the function binder may + // have folded it (a subquery can neither be copied nor compared, it is always matched by identity) + while (current.get() != &operand && !current->Equals(operand)) { + if (operand.IsFoldable() && current->GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { + // the function binder folded the constant operand to its value + return true; + } if (!BoundCastExpression::IsCast(*current)) { return false; } @@ -53,9 +58,8 @@ optional_ptr WindowRangeCast::Match(const Expression &expressi return current; } -unique_ptr WindowRangeBoundary::Capture(const Expression &expression, - optional_ptr order, - optional_ptr offset) { +unique_ptr WindowRangeBoundary::Capture(const Expression &expression, const Expression &order, + const Expression &offset) { if (expression.GetExpressionClass() != ExpressionClass::BOUND_FUNCTION) { return nullptr; } diff --git a/src/duckdb/src/planner/expression_binder/table_function_binder.cpp b/src/duckdb/src/planner/expression_binder/table_function_binder.cpp index b338d14a1..3a00d5bb5 100644 --- a/src/duckdb/src/planner/expression_binder/table_function_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/table_function_binder.cpp @@ -60,7 +60,7 @@ BindResult TableFunctionBinder::BindColumnReference(unique_ptr } } - if (accept_sql_value_functions) { + if (identifier_conversion_policy != IdentifierConversionPolicy::ALLOW) { auto value_function = ExpressionBinder::GetSQLValueFunction(column_names.back()); if (value_function) { return BindExpression(value_function, depth, root_expression); diff --git a/src/duckdb/src/planner/expression_nullability.cpp b/src/duckdb/src/planner/expression_nullability.cpp index 6ffac554b..c317acc7c 100644 --- a/src/duckdb/src/planner/expression_nullability.cpp +++ b/src/duckdb/src/planner/expression_nullability.cpp @@ -47,7 +47,7 @@ static bool GetColumnRefBinding(const Expression &expr, ColumnBinding &binding) return true; } -static bool FilterRejectsNull(const Expression &filter, const Expression &expr) { +bool FilterRejectsNull(const Expression &filter, const Expression &expr) { if (filter.GetExpressionType() == ExpressionType::CONJUNCTION_AND) { auto &conjunction = filter.Cast(); for (auto &child : conjunction.GetChildren()) { @@ -229,11 +229,11 @@ bool NotNullExpressionAnalyzer::IsNotNull(LogicalOperator &op, const Expression auto column_index = binding.column_index.GetIndex(); auto &cte_source = *cte->children[0]; auto source_bindings = cte_source.GetColumnBindings(); - if (column_index >= source_bindings.size() || column_index >= cte_source.types.size()) { + if (column_index >= source_bindings.size()) { return false; } seen_ctes.push_back(cte_ref.cte_index); - auto source_expr = BoundColumnRefExpression(cte_source.types[column_index], source_bindings[column_index]); + auto source_expr = BoundColumnRefExpression(expr.GetReturnType(), source_bindings[column_index]); auto result = IsNotNull(cte_source, source_expr, seen_ctes); seen_ctes.pop_back(); return result; diff --git a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp index dbb5ad7ce..222b68dbc 100644 --- a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp @@ -11,6 +11,7 @@ #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/operator/subtract.hpp" +#include "duckdb/common/vector/constant_vector.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/common/vector_size.hpp" #include "duckdb/execution/expression_executor_state.hpp" @@ -148,18 +149,48 @@ bool BloomFilterFunctionData::Equals(const FunctionData &other_p) const { key_column_name == other.key_column_name && key_type == other.key_type; } -static idx_t SelectBloomFilter(Vector &input, const BloomFilterFunctionData &func_data, SelectionVector &result_sel, - idx_t count) { +struct BloomFilterLocalState : public SelectivityTrackingLocalState { + BloomFilterLocalState(idx_t n_vectors_to_check, float selectivity_threshold) + : SelectivityTrackingLocalState(n_vectors_to_check, selectivity_threshold), hashes(LogicalType::HASH) { + } + + void Prepare(idx_t count) { + if (count <= capacity) { + return; + } + hashes.Initialize(VectorDataInitialization::UNINITIALIZED, count); + bloom_sel.Initialize(count); + capacity = count; + } + + Vector hashes; + SelectionVector bloom_sel; + SelectionVector result_sel; + idx_t capacity = 0; +}; + +static idx_t SelectBloomFilter(Vector &input, const BloomFilterFunctionData &func_data, BloomFilterLocalState &state, + SelectionVector &result_sel, idx_t count) { D_ASSERT(func_data.filter); - Vector hashes(LogicalType::HASH, count); + state.Prepare(count); + auto &hashes = state.hashes; VectorOperations::Hash(input, hashes, count); - hashes.Flatten(); - UnifiedVectorFormat input_data; input.ToUnifiedFormat(input_data); + auto &bloom_sel = input_data.validity.CannotHaveNull() ? result_sel : state.bloom_sel; + idx_t bloom_count; + if (hashes.GetVectorType() == VectorType::CONSTANT_VECTOR) { + bloom_count = func_data.filter->LookupOne(*ConstantVector::GetData(hashes)) ? count : 0; + for (idx_t i = 0; i < bloom_count; i++) { + bloom_sel.set_index(i, i); + } + } else { + bloom_count = func_data.filter->LookupHashes(hashes, bloom_sel, count); + } - SelectionVector bloom_sel(count); - const auto bloom_count = func_data.filter->LookupHashes(hashes, bloom_sel, count); + if (input_data.validity.CannotHaveNull()) { + return bloom_count; + } idx_t result_count = 0; idx_t bloom_idx = 0; @@ -188,7 +219,7 @@ BloomFilterInitLocalState(ExpressionState &state, const BoundFunctionExpression if (!data.filter) { return nullptr; } - return InitSelectivityTrackingLocalState(data.n_vectors_to_check, data.selectivity_threshold); + return make_uniq(data.n_vectors_to_check, data.selectivity_threshold); } static idx_t BloomFilterSelect(DataChunk &args, ExpressionState &state, optional_ptr sel, @@ -196,23 +227,22 @@ static idx_t BloomFilterSelect(DataChunk &args, ExpressionState &state, optional auto &func_expr = state.expr.Cast(); auto &func_data = func_expr.BindInfo()->Cast(); auto local_state_ptr = ExecuteFunctionState::GetFunctionState(state); - auto tracking_state = local_state_ptr ? &local_state_ptr->Cast() : nullptr; - auto count = args.size(); if (!func_data.filter) { return SetAllTrueSelection(count, sel, true_sel, false_sel); } - if (tracking_state && !tracking_state->IsActive()) { - tracking_state->Update(0, 0); + D_ASSERT(local_state_ptr); + auto &local_state = local_state_ptr->Cast(); + if (func_data.n_vectors_to_check != 0 && !local_state.IsActive()) { + local_state.Update(0, 0); return SetAllTrueSelection(count, sel, true_sel, false_sel); } - SelectionVector temp_true(count); - auto result_true_sel = (!true_sel || (sel && true_sel.get() == sel.get())) ? &temp_true : true_sel.get(); - auto approved_count = SelectBloomFilter(args.data[0], func_data, *result_true_sel, count); - approved_count = TranslateSelection(count, sel, *result_true_sel, approved_count, true_sel, false_sel); - if (tracking_state) { - tracking_state->Update(approved_count, count); + auto &result_true_sel = GetFilterResultSelection(count, sel, true_sel, local_state.result_sel); + auto approved_count = SelectBloomFilter(args.data[0], func_data, local_state, result_true_sel, count); + approved_count = TranslateSelection(count, sel, result_true_sel, approved_count, true_sel, false_sel); + if (func_data.n_vectors_to_check != 0) { + local_state.Update(approved_count, count); } return approved_count; } diff --git a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp index 953d9f372..13e56d61d 100644 --- a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp @@ -125,10 +125,10 @@ static idx_t DynamicFilterSelect(DataChunk &args, ExpressionState &state, option constant = func_data.filter_data->constant; } - SelectionVector temp_true(count); - auto result_true_sel = (!true_sel || (sel && true_sel.get() == sel.get())) ? &temp_true : true_sel.get(); - auto approved_count = SelectDynamicFilter(args.data[0], comparison_type, constant, *result_true_sel, count); - return TranslateSelection(count, sel, *result_true_sel, approved_count, true_sel, false_sel); + SelectionVector temp_true; + auto &result_true_sel = GetFilterResultSelection(count, sel, true_sel, temp_true); + auto approved_count = SelectDynamicFilter(args.data[0], comparison_type, constant, result_true_sel, count); + return TranslateSelection(count, sel, result_true_sel, approved_count, true_sel, false_sel); } ScalarFunction DynamicFilterScalarFun::GetFunction(const LogicalType &input_type) { diff --git a/src/duckdb/src/planner/filter/table_filter_prefix_range_function.cpp b/src/duckdb/src/planner/filter/table_filter_prefix_range_function.cpp index 20709c8f6..62bfabad8 100644 --- a/src/duckdb/src/planner/filter/table_filter_prefix_range_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_prefix_range_function.cpp @@ -76,6 +76,7 @@ class PrefixRangeBitmap { // Only mark initialized as true when local bitmaps are merged. initialized = false; + dense = false; } unique_ptr InitializeBuildState(ClientContext &context) const { @@ -109,8 +110,12 @@ class PrefixRangeBitmap { } void MergeBuildState(PrefixRangeBitmapBuildState &state) { + const auto last_word_mask = ~uint64_t(0) >> (WORD_MASK - UnsafeNumericCast((span >> shift) & WORD_MASK)); + dense = true; for (idx_t word_idx = 0; word_idx < word_count; word_idx++) { bitmap[word_idx] |= state.bitmap[word_idx]; + const auto expected = word_idx + 1 == word_count ? last_word_mask : ~uint64_t(0); + dense &= bitmap[word_idx] == expected; } initialized = true; } @@ -127,6 +132,9 @@ class PrefixRangeBitmap { const U comparable = CONVERTER::Convert(value.GetValueUnsafe()); const U y = comparable - min; + if (dense) { + return y <= span; + } const U bit_idx = y >> shift; const uint8_t in_range = y <= span; const uint32_t word_idx = (bit_idx >> WORD_SHIFT) & (0U - in_range); @@ -138,6 +146,9 @@ class PrefixRangeBitmap { idx_t LookupKeys(Vector &keys, SelectionVector &result_sel, idx_t count) const { D_ASSERT(count <= keys.size()); auto key_entries = keys.Values(); + if (dense) { + return LookupDense(key_entries, SelectionVector(), result_sel, count); + } idx_t found_count = 0; for (idx_t i = 0; i < count; i++) { const auto key_entry = key_entries[i]; @@ -160,6 +171,9 @@ class PrefixRangeBitmap { template idx_t LookupKeys(Vector &keys, const SelectionVector &sel, SelectionVector &result_sel, idx_t count) const { auto key_entries = keys.Values(); + if (dense) { + return LookupDense(key_entries, sel, result_sel, count); + } idx_t found_count = 0; for (idx_t i = 0; i < count; i++) { const auto key_entry = key_entries[sel.get_index_unsafe(i)]; @@ -179,6 +193,23 @@ class PrefixRangeBitmap { return found_count; } + template + idx_t LookupDense(const VectorIterator &keys, const SelectionVector &sel, SelectionVector &result_sel, + idx_t count) const { + // A bitmap with every bucket set is equivalent to its bounds. + idx_t found_count = 0; + for (idx_t i = 0; i < count; i++) { + const auto entry = keys[sel.get_index(i)]; + if (!entry.IsValid()) { + continue; + } + const U y = CONVERTER::Convert(entry.GetValue()) - min; + result_sel.set_index(found_count, i); + found_count += y <= span; + } + return found_count; + } + FilterPropagateResult LookupRange(U lower_bound, U upper_bound) const { const U lb_y = lower_bound - min; const U lb_bit_idx = lb_y >> shift; @@ -237,6 +268,8 @@ class PrefixRangeBitmap { static constexpr idx_t WORD_MASK = 63; bool initialized = false; + //! All buckets within span are set, excluding padding bits in the final word. + bool dense = false; U min; U span; idx_t shift; @@ -619,10 +652,10 @@ static idx_t PrefixRangeSelect(DataChunk &args, ExpressionState &state, optional return SetAllTrueSelection(count, sel, true_sel, false_sel); } - SelectionVector temp_true(count); - auto result_true_sel = (!true_sel || (sel && true_sel.get() == sel.get())) ? &temp_true : true_sel.get(); - auto approved_count = SelectPrefixRange(args.data[0], func_data, *result_true_sel, count); - approved_count = TranslateSelection(count, sel, *result_true_sel, approved_count, true_sel, false_sel); + SelectionVector temp_true; + auto &result_true_sel = GetFilterResultSelection(count, sel, true_sel, temp_true); + auto approved_count = SelectPrefixRange(args.data[0], func_data, result_true_sel, count); + approved_count = TranslateSelection(count, sel, result_true_sel, approved_count, true_sel, false_sel); if (tracking_state) { tracking_state->Update(approved_count, count); } diff --git a/src/duckdb/src/planner/filter/table_filter_selectivity_optional_function.cpp b/src/duckdb/src/planner/filter/table_filter_selectivity_optional_function.cpp index e6d709e2a..683345063 100644 --- a/src/duckdb/src/planner/filter/table_filter_selectivity_optional_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_selectivity_optional_function.cpp @@ -34,7 +34,7 @@ void SelectivityOptionalFilterState::SelectivityStats::Update(idx_t accepted, id tuples_accepted = 0; tuples_processed = 0; status = FilterStatus::ACTIVE; - } else if (vectors_processed >= n_vectors_to_check) { + } else if (status == FilterStatus::ACTIVE && vectors_processed >= n_vectors_to_check) { // pause the filter if we processed enough vectors and the selectivity is too high if (GetSelectivity() >= selectivity_threshold) { status = FilterStatus::PAUSED_DUE_TO_HIGH_SELECTIVITY; @@ -145,10 +145,10 @@ static idx_t SelectivityOptionalFilterSelect(DataChunk &args, ExpressionState &s return SetAllTrueSelection(count, sel, true_sel, false_sel); } - SelectionVector temp_true(count); - auto result_true_sel = (!true_sel || (sel && true_sel.get() == sel.get())) ? &temp_true : true_sel.get(); - auto approved_count = local_state.executor.SelectExpression(args, *result_true_sel); - approved_count = TranslateSelection(count, sel, *result_true_sel, approved_count, true_sel, false_sel); + SelectionVector temp_true; + auto &result_true_sel = GetFilterResultSelection(count, sel, true_sel, temp_true); + auto approved_count = local_state.executor.SelectExpression(args, result_true_sel); + approved_count = TranslateSelection(count, sel, result_true_sel, approved_count, true_sel, false_sel); local_state.Update(approved_count, count); return approved_count; } diff --git a/src/duckdb/src/planner/logical_plan_verifier.cpp b/src/duckdb/src/planner/logical_plan_verifier.cpp index 872ed14a1..e81783c90 100644 --- a/src/duckdb/src/planner/logical_plan_verifier.cpp +++ b/src/duckdb/src/planner/logical_plan_verifier.cpp @@ -217,6 +217,15 @@ struct LogicalPlanVerificationState { return resolved_outputs.find(reference(op)) != resolved_outputs.end(); } + void AddInvalidResolvedSchema(LogicalOperator &op) { + LogicalPlanVerificationIssue issue; + issue.code = LogicalPlanVerificationIssueCode::INTERNAL_INVARIANT; + issue.path = GetPath(op); + issue.construct = GetOperatorConstruct(op); + issue.message = "Resolved operator bindings require a complete output type for every column"; + issues.push_back(std::move(issue)); + } + bool HasNullSlots(LogicalOperator &op) const { for (auto &child : op.children) { if (!child) { @@ -325,15 +334,18 @@ void LogicalPlanVerifier::Verify(ClientContext &context, LogicalOperator &op) { } } -bool LogicalPlanVerifier::ResolveOperatorTypes(LogicalOperator &op, LogicalPlanVerificationState &verification_state) { - op.types.clear(); +bool LogicalPlanVerifier::ResolveOperatorTypes(LogicalOperator &op, LogicalPlanVerificationState &verification_state, + TypeResolution resolution) { + if (resolution == TypeResolution::RESOLVE) { + op.types.clear(); + } bool children_resolved = true; for (auto &child : op.children) { if (!child) { children_resolved = false; continue; } - if (!ResolveOperatorTypes(*child, verification_state)) { + if (!ResolveOperatorTypes(*child, verification_state, resolution)) { children_resolved = false; } } @@ -341,8 +353,23 @@ bool LogicalPlanVerifier::ResolveOperatorTypes(LogicalOperator &op, LogicalPlanV return false; } verification_state.resolved_inputs.insert(reference(op)); - op.ResolveTypes(); + if (resolution == TypeResolution::RESOLVE) { + op.ResolveTypes(); + } auto bindings = op.GetColumnBindings(); + if (resolution == TypeResolution::EXISTING) { + bool valid = bindings.size() == op.types.size(); + for (auto &type : op.types) { + valid &= type.IsComplete(); + } + for (auto &binding : bindings) { + valid &= binding.table_index.IsValid() && binding.column_index.IsValid(); + } + if (!valid) { + verification_state.AddInvalidResolvedSchema(op); + return false; + } + } if (op.type == LogicalOperatorType::LOGICAL_EXTENSION_OPERATOR) { auto &extension_op = op.Cast(); auto identifier = extension_op.GetTypeBindingVerificationIdentifier(); @@ -439,6 +466,20 @@ LogicalPlanVerificationResult LogicalPlanVerifie return VerifyAlwaysInternal(op, nullptr); } +LogicalPlanVerificationResult +LogicalPlanVerifier::VerifyResolvedBindings(LogicalOperator &op) { + LogicalPlanVerificationState verification_state(op); + ResolveOperatorTypes(op, verification_state, TypeResolution::EXISTING); + VerifyColumnBindings(op, verification_state); + unordered_map seen_indexes; + VerifyTableIndexes(op, verification_state, seen_indexes); + if (!verification_state.issues.empty()) { + return LogicalPlanVerificationResult::Failure( + std::move(verification_state.issues)); + } + return LogicalPlanVerificationResult::Success(LogicalPlanVerificationSuccess()); +} + LogicalPlanVerificationResult LogicalPlanVerifier::VerifyAlwaysInternal(LogicalOperator &op, optional_ptr first_error) { LogicalPlanVerificationState verification_state(op); diff --git a/src/duckdb/src/planner/operator/logical_comparison_join.cpp b/src/duckdb/src/planner/operator/logical_comparison_join.cpp index a6afc8b77..8554da064 100644 --- a/src/duckdb/src/planner/operator/logical_comparison_join.cpp +++ b/src/duckdb/src/planner/operator/logical_comparison_join.cpp @@ -96,6 +96,28 @@ static bool MarkJoinTypesMatch(const LogicalType &left, const LogicalType &right return !mismatch && index == collations.size(); } +bool LogicalComparisonJoin::HasNullSafeMarkJoinConditions() const { + if (join_type != JoinType::MARK) { + return false; + } + bool has_hash_key = false; + for (auto &condition : conditions) { + if (!condition.IsComparison()) { + return false; + } + switch (condition.GetComparisonType()) { + case ExpressionType::COMPARE_NOT_DISTINCT_FROM: + has_hash_key = true; + break; + case ExpressionType::COMPARE_DISTINCT_FROM: + break; + default: + return false; + } + } + return has_hash_key; +} + bool LogicalComparisonJoin::TryGetMarkJoinGroupTypes(vector &group_types) const { group_types.clear(); if (join_type != JoinType::MARK || conditions.size() < 2) { diff --git a/src/duckdb/src/planner/operator/logical_explain.cpp b/src/duckdb/src/planner/operator/logical_explain.cpp index 08231e4ff..b52578f63 100644 --- a/src/duckdb/src/planner/operator/logical_explain.cpp +++ b/src/duckdb/src/planner/operator/logical_explain.cpp @@ -3,6 +3,7 @@ #include "duckdb/planner/operator/logical_column_data_get.hpp" #include "duckdb/common/types/column/column_data_collection.hpp" #include "duckdb/common/types/data_chunk.hpp" +#include "duckdb/parser/peg/sql_formatter.hpp" namespace duckdb { @@ -16,7 +17,17 @@ unique_ptr LogicalExplain::CreateSQLResult(ClientContext &conte D_ASSERT(explain_type == ExplainType::EXPLAIN_SQL); D_ASSERT(children.size() == 1); LogicalPlanSQLExportOptions options; - options.output_names = sql_output_names; + switch (children[0]->type) { + case LogicalOperatorType::LOGICAL_INSERT: + case LogicalOperatorType::LOGICAL_UPDATE: + case LogicalOperatorType::LOGICAL_DELETE: + case LogicalOperatorType::LOGICAL_MERGE_INTO: + // a data-modifying statement reports a change count; that is not a projection of the generated SQL + break; + default: + options.output_names = sql_output_names; + break; + } auto exported = LogicalPlanSQLExporter::Export(context, *children[0], options); if (exported.HasError()) { auto &issue = exported.GetIssues()[0]; @@ -51,10 +62,11 @@ unique_ptr LogicalExplain::CreateSQLResult(ClientContext &conte auto collection = make_uniq(context, result_types, ColumnDataAllocatorType::IN_MEMORY_ALLOCATOR); if (exported.IsSuccess()) { + auto sql = FormatSQL(exported.GetValue().query->ToString()); DataChunk chunk; chunk.Initialize(Allocator::Get(context), result_types); chunk.data[0].Append(Value("sql")); - chunk.data[1].Append(Value(exported.GetValue().query->ToString())); + chunk.data[1].Append(Value(sql)); collection->Append(chunk); } return make_uniq(table_index, std::move(result_types), std::move(collection)); diff --git a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp index 1298a4281..a66dba15e 100644 --- a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp +++ b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp @@ -146,7 +146,9 @@ BoundExpressionSQLExportResult BoundExpressionSQLExportState::Export(const Expre auto result = ExportInternal(expression, path); const bool preserves_collation = expression.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT || (expression.GetExpressionClass() == ExpressionClass::BOUND_FUNCTION && - expression.GetReturnType().id() != LogicalTypeId::VARCHAR); + expression.GetReturnType().id() != LogicalTypeId::VARCHAR) || + (expression.GetExpressionClass() == ExpressionClass::BOUND_AGGREGATE && + expression.GetReturnType().IsAggregateState()); if (expression.GetExpressionClass() == ExpressionClass::BOUND_COLUMN_REF || preserves_collation) { return result; } @@ -402,9 +404,11 @@ BoundExpressionSQLExportResult BoundExpressionSQLExportState::ExportCast(const B path, "aggregate_state_try_cast", "Aggregate state TRY_CAST or custom casts require a SQL representation")}); } - auto result = ExportAggregateFunction::StateToSQL( - expression.GetReturnType(), - BoundExpressionSQLExportState::SQLCast(storage_type, std::move(child.GetValue()))); + auto storage = RestoreResultType(storage_type, std::move(child.GetValue()), path); + if (storage.HasError()) { + return storage; + } + auto result = ExportAggregateFunction::StateToSQL(expression.GetReturnType(), std::move(storage.GetValue())); if (!result) { return BoundExpressionSQLExportResult::Failure({BoundExpressionSQLExportState::UnsupportedFeature( path, "aggregate_state_parameters", "Aggregate state SQL parameters are not representable")}); diff --git a/src/duckdb/src/planner/sql_export/logical_plan_sql_exporter.cpp b/src/duckdb/src/planner/sql_export/logical_plan_sql_exporter.cpp index b089f4833..22dbc50d0 100644 --- a/src/duckdb/src/planner/sql_export/logical_plan_sql_exporter.cpp +++ b/src/duckdb/src/planner/sql_export/logical_plan_sql_exporter.cpp @@ -4,7 +4,11 @@ #include "duckdb/planner/sql_export/bound_expression_sql_exporter_internal.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/parser/expression/constant_expression.hpp" +#include "duckdb/parser/expression/subquery_expression.hpp" +#include "duckdb/parser/parsed_expression_iterator.hpp" #include "duckdb/parser/query_node/select_node.hpp" +#include "duckdb/parser/tableref/basetableref.hpp" +#include "duckdb/parser/common_table_expression_info.hpp" #include "duckdb/planner/bound_expression_sql_exporter.hpp" #include "duckdb/planner/operator/logical_get.hpp" #include "duckdb/planner/expression/bound_aggregate_expression.hpp" @@ -17,6 +21,8 @@ #include "duckdb/planner/operator/logical_expression_get.hpp" #include "duckdb/planner/sql_export_helpers.hpp" #include "duckdb/planner/operator/logical_projection.hpp" +#include "duckdb/common/unordered_set.hpp" +#include namespace duckdb { @@ -28,14 +34,16 @@ static LogicalPlanVerificationIssue UnsupportedOperator(const LogicalPlanVerific "The logical operator does not have a SQL AST representation in this exporter"); } -static optional ConstantSQLInput(const Expression &expression, LogicalOperator &input) { +static optional ConstantSQLInput(const Expression &expression, LogicalOperator &input, + const LogicalPlanSQLExportContext &context) { if (expression.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { return expression.Cast().GetValue(); } if (auto wrapped = CMUtils::GetWrappedInput(expression)) { - return ConstantSQLInput(*wrapped, input); + return ConstantSQLInput(*wrapped, input, context); } - if (expression.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF || input.children.size() != 1) { + if (context.FindSource(input) || expression.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF || + input.children.size() != 1) { return {}; } auto &column = expression.Cast(); @@ -48,7 +56,7 @@ static optional ConstantSQLInput(const Expression &expression, LogicalOpe if (column.Binding().table_index != projection.table_index) { return {}; } - return ConstantSQLInput(*projection.expressions[column.Binding().column_index], *input.children[0]); + return ConstantSQLInput(*projection.expressions[column.Binding().column_index], *input.children[0], context); } case LogicalOperatorType::LOGICAL_AGGREGATE_AND_GROUP_BY: { auto &aggregate = input.Cast(); @@ -61,14 +69,14 @@ static optional ConstantSQLInput(const Expression &expression, LogicalOpe return {}; } } - return ConstantSQLInput(*aggregate.groups[index], *input.children[0]); + return ConstantSQLInput(*aggregate.groups[index], *input.children[0], context); } case LogicalOperatorType::LOGICAL_FILTER: case LogicalOperatorType::LOGICAL_ORDER_BY: case LogicalOperatorType::LOGICAL_TOP_N: case LogicalOperatorType::LOGICAL_LIMIT: case LogicalOperatorType::LOGICAL_DISTINCT: - return ConstantSQLInput(expression, *input.children[0]); + return ConstantSQLInput(expression, *input.children[0], context); default: return {}; } @@ -92,6 +100,10 @@ static LogicalPlanSQLExportResult ApplyOutputNames(LogicalPlanSQLExportResult re if (result.HasError()) { return result; } + if (SQLExportDMLHelpers::IsDMLNode(*result.GetValue().query)) { + // a data-modifying statement's result shape is inherent; its names are not a projection + return result; + } if (output_names.size() != result.GetValue().fields.size()) { return LogicalPlanSQLExportResult::Failure({LogicalPlanSQLExportHelpers::PlanUnsupportedFeature( LogicalPlanVerificationPath(), "output_names", "Output name count does not match the exported plan")}); @@ -137,16 +149,25 @@ struct LogicalPlanSQLExportContext::SourceScope { LogicalPlanSQLExportContext::LogicalPlanSQLExportContext(ClientContext &context_p) : context(context_p) { } -LogicalPlanSQLExportResult LogicalPlanSQLExportContext::Export(LogicalOperator &op, - const LogicalPlanVerificationPath &path) { +optional_ptr +LogicalPlanSQLExportContext::FindSource(const LogicalOperator &op) const { for (auto scope = source_scope; scope; scope = scope->parent) { for (auto &source : scope->sources) { if (&source.op.get() == &op) { - return EnsureSQLProjection(LogicalPlanSQLExportResult::Success( - {CreateNamedSource(source.name, source.relation.fields), source.relation.fields})); + return &source; } } } + return nullptr; +} + +LogicalPlanSQLExportResult LogicalPlanSQLExportContext::Export(LogicalOperator &op, + const LogicalPlanVerificationPath &path) { + if (auto source = FindSource(op)) { + auto read = CreateNamedSource(source->name, source->relation.fields); + source_reads.insert(*read->from_table); + return EnsureSQLProjection(LogicalPlanSQLExportResult::Success({std::move(read), source->relation.fields})); + } ancestors.push_back(op); auto result = op.ToSQL(*this, path); ancestors.pop_back(); @@ -187,6 +208,45 @@ Identifier LogicalPlanSQLExportContext::NextRelationAlias(const Identifier &pref } } +static void VisitRelationReferences(QueryNode &query, const std::function &callback) { + std::function visit_expression = [&](const ParsedExpression &expression) { + if (expression.GetExpressionClass() == ExpressionClass::SUBQUERY) { + VisitRelationReferences(*expression.Cast().Subquery()->node, callback); + } + ParsedExpressionIterator::EnumerateChildren(expression, visit_expression); + }; + ParsedExpressionIterator::EnumerateQueryNodeChildren( + query, [&](unique_ptr &expression) { visit_expression(*expression); }, callback); +} + +void LogicalPlanSQLExportContext::ReserveRelationNames(QueryNode &query) { + VisitRelationReferences(query, [&](TableRef &ref) { + if (ref.type == TableReferenceType::BASE_TABLE) { + relation_aliases.insert(ref.Cast().Table()); + } + }); +} + +optional +LogicalPlanSQLExportContext::FindCapturedSourceReference(QueryNode &query, const identifier_set_t &source_names) const { + optional captured; + VisitRelationReferences(query, [&](TableRef &ref) { + if (captured || ref.type != TableReferenceType::BASE_TABLE) { + return; + } + // Qualified references bind past the generated WITH scope. + auto &name = ref.Cast().GetQualifiedName(); + if (!name.Catalog().empty() || !name.Schema().empty()) { + return; + } + if (source_names.count(name.Name()) && + source_reads.find(reference(ref)) == source_reads.end()) { + captured = name.Name(); + } + }); + return captured; +} + LogicalPlanVerificationResult LogicalPlanSQLExportContext::ExportChild(LogicalOperator &child, const LogicalPlanVerificationPath &path) { auto exported = Export(child, path); @@ -217,7 +277,7 @@ LogicalPlanVerificationResult> LogicalPlanSQLExport if (arguments[i]->GetExpressionClass() == ExpressionClass::BOUND_CONSTANT) { continue; } - auto value = ConstantSQLInput(*arguments[i], *op.children[0]); + auto value = ConstantSQLInput(*arguments[i], *op.children[0], *this); if (value && value->type().EqualsIncludingCollation(arguments[i]->GetReturnType())) { if (!restored) { restored = expression.Copy(); @@ -277,6 +337,11 @@ LogicalPlanSQLExporter::Export(ClientContext &context, LogicalOperator &root, return LogicalPlanVerificationResult::Failure(verification); } LogicalPlanSQLExportContext state(context); + if (auto returning = SQLExportDMLHelpers::MatchReturningRoot(root)) { + // output names become the RETURNING aliases, so they are not reapplied below + return SQLExportDMLHelpers::ExportReturning(state, *returning, options.output_names, + LogicalPlanVerificationPath()); + } auto result = state.Export(root, LogicalPlanVerificationPath()); if (!options.output_names) { return result; @@ -284,4 +349,99 @@ LogicalPlanSQLExporter::Export(ClientContext &context, LogicalOperator &root, return ApplyOutputNames(std::move(result), *options.output_names); } +LogicalPlanSQLExportResult +LogicalPlanSQLExporter::ExportWithSources(ClientContext &context, LogicalOperator &root, + const vector &replacements, + const LogicalPlanSQLExportOptions &options) { + auto verification = LogicalPlanVerifier::VerifyResolvedBindings(root); + if (verification.HasError()) { + return LogicalPlanSQLExportResult::Failure(verification); + } + auto invalid = [](const string &message) { + auto issue = SQLExportHelpers::MakeIssue( + LogicalPlanVerificationIssueCode::INTERNAL_INVARIANT, LogicalPlanVerificationPhase::PLAN_EXPORT, + LogicalPlanVerificationPath(), LogicalPlanVerificationConstructIdentity::ExportFeature("source_boundary"), + message); + return LogicalPlanSQLExportResult::Failure({std::move(issue)}); + }; + unordered_set boundaries; + for (auto &replacement : replacements) { + auto &op = replacement.op.get(); + if (!boundaries.insert(&op).second || !replacement.relation.query) { + return invalid("SQL source replacement requires a unique subtree and an owned relation"); + } + auto bindings = op.GetColumnBindings(); + if (replacement.relation.fields.size() != bindings.size() || op.types.size() != bindings.size()) { + return invalid("SQL source replacement width differs from its subtree"); + } + for (idx_t i = 0; i < bindings.size(); i++) { + auto &field = replacement.relation.fields[i]; + auto &type = field.optimizer_type ? *field.optimizer_type : field.type; + if (field.source_binding != bindings[i] || !field.type.IsComplete() || + !type.EqualsIncludingCollation(op.types[i])) { + return invalid("SQL source replacement bindings or types differ from its subtree"); + } + } + } + unordered_set reached; + bool overlap = false; + std::function visit = [&](LogicalOperator &op, bool replaced) { + if (boundaries.count(&op)) { + overlap |= replaced; + reached.insert(&op); + replaced = true; + } + for (auto &child : op.children) { + visit(*child, replaced); + } + }; + visit(root, false); + if (overlap || reached.size() != boundaries.size()) { + return invalid("SQL source replacements must be disjoint subtrees of this plan"); + } + LogicalPlanSQLExportContext state(context); + vector sources; + // All definitions share a CTE scope. Reserve references from every replacement + // before allocating any name, so a generated source cannot capture another query's input. + for (auto &replacement : replacements) { + state.ReserveRelationNames(*replacement.relation.query); + } + for (auto &replacement : replacements) { + sources.push_back({replacement.op, + state.NextRelationAlias(Identifier("__export_source")), + {replacement.relation.query->Copy(), replacement.relation.fields}}); + } + auto exported = state.ExportChild(root, LogicalPlanVerificationPath(), sources); + if (exported.HasError()) { + return LogicalPlanSQLExportResult::Failure(exported); + } + auto result = std::move(exported.GetValue().relation); + auto consumers = std::move(result.query->cte_map); + result.query->cte_map = CommonTableExpressionMap(); + for (auto &source : sources) { + auto info = make_uniq(); + for (idx_t i = 0; i < source.relation.fields.size(); i++) { + info->aliases.push_back(LogicalPlanSQLExportHelpers::FieldIdentifier(i)); + } + info->query_node = std::move(source.relation.query); + info->materialized = CTEMaterialize::CTE_MATERIALIZE_ALWAYS; + result.query->cte_map.map.insert(source.name, std::move(info)); + } + for (auto &consumer : consumers.map) { + result.query->cte_map.map.insert(consumer.first, std::move(consumer.second)); + } + // A source callback may emit an unqualified reference the plan verifier never sees; + // refuse a result whose generated names would rebind such a reference. + identifier_set_t source_names; + for (auto &source : sources) { + source_names.insert(source.name); + } + if (auto captured = state.FindCapturedSourceReference(*result.query, source_names)) { + return invalid("A generated SQL source name would capture a relation reference of the exported plan: " + + captured->GetIdentifierName()); + } + auto success = LogicalPlanSQLExportResult::Success(std::move(result)); + return options.output_names ? ApplyOutputNames(std::move(success), *options.output_names) : std::move(success); +} + } // namespace duckdb diff --git a/src/duckdb/src/planner/sql_export/sql_export_cte.cpp b/src/duckdb/src/planner/sql_export/sql_export_cte.cpp index d21a0e459..987570745 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_cte.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_cte.cpp @@ -35,7 +35,19 @@ LogicalPlanSQLExportResult LogicalMaterializedCTE::ToSQL(LogicalPlanSQLExportCon return LogicalPlanSQLExportResult::Failure(producer); } export_context.PushNamedRelation(cte.table_index, name, false); - auto consumer = export_context.ExportChild(*cte.children[1], LogicalPlanSQLExportHelpers::PlanChildPath(path, 1)); + auto consumer_path = LogicalPlanSQLExportHelpers::PlanChildPath(path, 1); + // A data-modifying consumer exports as the statement itself rather than a relation to select from. + auto consumer = [&]() -> LogicalPlanVerificationResult { + if (!SQLExportDMLHelpers::MatchDMLBody(*cte.children[1])) { + return export_context.ExportChild(*cte.children[1], consumer_path); + } + auto body = SQLExportDMLHelpers::ExportBody(export_context, *cte.children[1], consumer_path); + if (body.HasError()) { + return LogicalPlanVerificationResult::Failure(body); + } + return LogicalPlanVerificationResult::Success( + {std::move(body.GetValue()), Identifier()}); + }(); auto references = export_context.PopNamedRelation(); if (consumer.HasError()) { return LogicalPlanSQLExportResult::Failure(consumer); @@ -55,9 +67,46 @@ LogicalPlanSQLExportResult LogicalMaterializedCTE::ToSQL(LogicalPlanSQLExportCon } info->query_node = std::move(producer.GetValue().relation.query); info->materialized = CTEMaterialize::CTE_MATERIALIZE_ALWAYS; + if (SQLExportDMLHelpers::IsDMLNode(*consumer.GetValue().relation.query)) { + // a data-modifying statement cannot be wrapped in a SELECT: it carries this CTE itself, + // in front of the definitions its export already collected + auto relation = std::move(consumer.GetValue().relation); + if (relation.fields.size() != fields.GetValue().size()) { + return LogicalPlanSQLExportResult::Failure({LogicalPlanSQLExportHelpers::PlanUnsupportedFeature( + path, "cte_dml_consumer", "The data-modifying CTE consumer does not forward the CTE's columns")}); + } + for (idx_t i = 0; i < relation.fields.size(); i++) { + if (relation.fields[i].source_binding != fields.GetValue()[i].source_binding) { + return LogicalPlanSQLExportResult::Failure({LogicalPlanSQLExportHelpers::PlanUnsupportedFeature( + path, "cte_dml_consumer", "The data-modifying CTE consumer does not forward the CTE's columns")}); + } + } + auto collected = std::move(relation.query->cte_map); + relation.query->cte_map = CommonTableExpressionMap(); + relation.query->cte_map.map.insert(name, std::move(info)); + for (auto &entry : collected.map) { + relation.query->cte_map.map.insert(entry.first, std::move(entry.second)); + } + return LogicalPlanSQLExportResult::Success(std::move(relation)); + } auto select = export_context.ForwardFields(consumer.GetValue(), fields.GetValue()); - select->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(consumer.GetValue())); select->cte_map.map.insert(name, std::move(info)); + // A data-modifying CTE definition must stay in the top-level WITH clause. If the consumer + // collected one, hoist its entire WITH clause above the subquery boundary, behind this CTE's + // own definition: a definition may reference those before it, so they move together. + auto &consumer_query = *consumer.GetValue().relation.query; + bool hoist = false; + for (auto &entry : consumer_query.cte_map.map) { + hoist |= SQLExportDMLHelpers::IsDMLNode(*entry.second->query_node); + } + if (hoist) { + auto collected = std::move(consumer_query.cte_map); + consumer_query.cte_map = CommonTableExpressionMap(); + for (auto &entry : collected.map) { + select->cte_map.map.insert(entry.first, std::move(entry.second)); + } + } + select->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(consumer.GetValue())); return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); } @@ -195,6 +244,25 @@ LogicalPlanSQLExportResult LogicalRecursiveCTE::ExportSQLDefinition(LogicalPlanS LogicalPlanVerificationResult LogicalPlanSQLExportContext::ExportNamedProducer(LogicalOperator &op, const LogicalPlanVerificationPath &path, const Identifier &name) { + if (FindSource(op)) { + return ExportChild(op, path); + } + // A data-modifying body exports as the statement itself; parsed DML nodes are legal CTE bodies. + // This runs before the identity-projection reduction so a RETURNING projection stays intact. + if (SQLExportDMLHelpers::MatchDMLBody(op)) { + auto exported = SQLExportDMLHelpers::ExportBody(*this, op, path); + if (exported.HasError()) { + return LogicalPlanVerificationResult::Failure(exported); + } + if (exported.GetValue().query->type == QueryNodeType::MERGE_QUERY_NODE) { + // ON CONFLICT inserts bind to MERGE INTO, which SQL does not accept as a CTE body + return LogicalPlanVerificationResult::Failure( + {LogicalPlanSQLExportHelpers::PlanUnsupportedFeature(path, "cte_merge_body", + "MERGE INTO cannot be exported as a CTE body")}); + } + return LogicalPlanVerificationResult::Success( + {std::move(exported.GetValue()), NextRelationAlias()}); + } if (op.type == LogicalOperatorType::LOGICAL_PROJECTION) { D_ASSERT(op.children.size() == 1); auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); diff --git a/src/duckdb/src/planner/sql_export/sql_export_dml.cpp b/src/duckdb/src/planner/sql_export/sql_export_dml.cpp new file mode 100644 index 000000000..d88d04a51 --- /dev/null +++ b/src/duckdb/src/planner/sql_export/sql_export_dml.cpp @@ -0,0 +1,641 @@ +#include "duckdb/catalog/catalog_entry/schema_catalog_entry.hpp" +#include "duckdb/catalog/catalog_entry/table_catalog_entry.hpp" +#include "duckdb/parser/expression/columnref_expression.hpp" +#include "duckdb/parser/expression/comparison_expression.hpp" +#include "duckdb/parser/expression/subquery_expression.hpp" +#include "duckdb/parser/query_node/delete_query_node.hpp" +#include "duckdb/parser/query_node/insert_query_node.hpp" +#include "duckdb/parser/query_node/merge_query_node.hpp" +#include "duckdb/parser/query_node/update_query_node.hpp" +#include "duckdb/parser/statement/insert_statement.hpp" +#include "duckdb/parser/statement/merge_into_statement.hpp" +#include "duckdb/parser/statement/select_statement.hpp" +#include "duckdb/parser/tableref/basetableref.hpp" +#include "duckdb/planner/column_binding_map.hpp" +#include "duckdb/planner/expression/bound_columnref_expression.hpp" +#include "duckdb/planner/expression_iterator.hpp" +#include "duckdb/planner/operator/logical_delete.hpp" +#include "duckdb/planner/operator/logical_insert.hpp" +#include "duckdb/planner/operator/logical_merge_into.hpp" +#include "duckdb/planner/operator/logical_projection.hpp" +#include "duckdb/planner/operator/logical_update.hpp" +#include "duckdb/planner/sql_export/logical_plan_sql_exporter_internal.hpp" + +namespace duckdb { + +static LogicalPlanVerificationIssue UnsupportedDML(const LogicalPlanVerificationPath &path, string feature, + string message) { + return LogicalPlanSQLExportHelpers::PlanUnsupportedFeature(path, std::move(feature), std::move(message)); +} + +//! The single row-id column SQL can reference on the modified table; multi-column row ids have no SQL name, +//! and a physical column with the row-id name would capture the generated references. +static LogicalPlanVerificationResult ExportedRowIdName(const TableCatalogEntry &table, + const LogicalPlanVerificationPath &path) { + auto row_id_columns = table.GetRowIdColumns(); + auto virtual_columns = table.GetVirtualColumns(); + auto entry = row_id_columns.size() == 1 ? virtual_columns.find(row_id_columns[0]) : virtual_columns.end(); + if (entry == virtual_columns.end() || table.ColumnExists(entry->second.name)) { + return LogicalPlanVerificationResult::Failure( + {UnsupportedDML(path, "dml_row_id", "The modified table does not expose a single named row-id column")}); + } + return LogicalPlanVerificationResult::Success(Identifier(entry->second.name)); +} + +static unique_ptr ModifiedTableRef(const TableCatalogEntry &table) { + auto reference = make_uniq(); + reference->SetQualifiedName(table.schema.GetQualifiedName(table.name)); + return std::move(reference); +} + +static LogicalPlanSQLExportResult ExportInsertNode(LogicalInsert &op, LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (op.on_conflict_info.action_type != OnConflictAction::THROW || !op.insert_values.empty()) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "insert_on_conflict", "INSERT ... ON CONFLICT cannot be exported")}); + } + if (op.children.size() != 1) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_source", "INSERT without a source relation cannot be exported")}); + } + auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + auto child = context.Export(*op.children[0], LogicalPlanSQLExportHelpers::PlanChildPath(path, 0)); + if (child.HasError()) { + return child; + } + // The bound source produces every physical column in declaration order, with defaults filled in. + vector target_columns; + for (auto &column : op.table.GetColumns().Physical()) { + target_columns.push_back(column.Name()); + } + if (child.GetValue().fields.size() != target_columns.size()) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "insert_source", "The INSERT source does not cover the table's physical columns")}); + } + auto node = make_uniq(); + node->qualified_name = op.table.schema.GetQualifiedName(op.table.name); + node->columns = std::move(target_columns); + node->select_statement = make_uniq(); + node->select_statement->node = std::move(child.GetValue().query); + return LogicalPlanSQLExportResult::Success({std::move(node), std::move(fields.GetValue())}); +} + +static LogicalPlanSQLExportResult ExportDeleteNode(LogicalDelete &op, LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (op.children.size() != 1) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_source", "DELETE without a source relation cannot be exported")}); + } + auto row_id_name = ExportedRowIdName(op.table, path); + if (row_id_name.HasError()) { + return LogicalPlanSQLExportResult::Failure(row_id_name); + } + auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + // The row-id reference the operator deletes by carries the row-id column's name as its alias. + optional_ptr row_id_reference; + bool ambiguous = false; + for (auto &expression : op.expressions) { + if (expression->GetAlias() != row_id_name.GetValue()) { + continue; + } + ambiguous |= static_cast(row_id_reference); + if (expression->GetExpressionClass() == ExpressionClass::BOUND_COLUMN_REF) { + row_id_reference = &expression->Cast(); + } + } + if (!row_id_reference || ambiguous || row_id_reference->Depth() != 0) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_row_id", "The deleted row-id column was not found in the plan")}); + } + auto child = context.ExportChild(*op.children[0], LogicalPlanSQLExportHelpers::PlanChildPath(path, 0)); + if (child.HasError()) { + return LogicalPlanSQLExportResult::Failure(child); + } + auto &child_fields = child.GetValue().relation.fields; + optional_idx row_id_field; + for (idx_t i = 0; i < child_fields.size(); i++) { + if (child_fields[i].source_binding == row_id_reference->Binding()) { + row_id_field = i; + break; + } + } + if (!row_id_field.IsValid()) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_row_id", "The deleted row-id column was not found in the exported source")}); + } + auto plain = LogicalPlanSQLExportHelpers::PlainScope(*child.GetValue().relation.query); + auto select = make_uniq(); + select->select_list.push_back( + LogicalPlanSQLExportHelpers::ChildColumn(child.GetValue(), row_id_field.GetIndex(), plain)); + LogicalPlanSQLExportHelpers::SetChildScope(*select, std::move(child.GetValue()), plain); + auto subquery = make_uniq(); + subquery->SubqueryMutable() = make_uniq(); + subquery->SubqueryMutable()->node = std::move(select); + subquery->GetSubqueryTypeMutable() = SubqueryType::ANY; + subquery->GetComparisonTypeMutable() = ExpressionType::COMPARE_EQUAL; + subquery->GetChildMutable() = make_uniq(row_id_name.GetValue()); + auto node = make_uniq(); + node->table = ModifiedTableRef(op.table); + node->condition = std::move(subquery); + return LogicalPlanSQLExportResult::Success({std::move(node), std::move(fields.GetValue())}); +} + +static LogicalPlanSQLExportResult ExportUpdateNode(LogicalUpdate &op, LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (op.children.size() != 1) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_source", "UPDATE without a source relation cannot be exported")}); + } + auto row_id_name = ExportedRowIdName(op.table, path); + if (row_id_name.HasError()) { + return LogicalPlanSQLExportResult::Failure(row_id_name); + } + auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + // Keep the table's own name reserved so the generated source alias cannot capture it. + context.NextRelationAlias(op.table.name); + auto child = context.ExportChild(*op.children[0], LogicalPlanSQLExportHelpers::PlanChildPath(path, 0)); + if (child.HasError()) { + return LogicalPlanSQLExportResult::Failure(child); + } + auto &child_fields = child.GetValue().relation.fields; + if (op.expressions.size() != op.columns.size() || child_fields.size() <= op.columns.size()) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_row_id", "The updated row-id column was not found in the exported source")}); + } + // The bound source keeps the row id as its final column. + auto row_id_field = child_fields.size() - 1; + auto expression_context = + LogicalPlanSQLExportHelpers::CreateBindingContext(context.GetClientContext(), {child.GetValue()}); + vector> set_expressions; + for (auto &expression : op.expressions) { + set_expressions.push_back(*expression); + } + auto set_info = make_uniq(); + for (idx_t i = 0; i < op.columns.size(); i++) { + auto exported = context.ExportExpression(op, set_expressions, i, expression_context, path); + if (exported.HasError()) { + return LogicalPlanSQLExportResult::Failure(exported); + } + set_info->columns.push_back(op.table.GetColumns().GetColumn(op.columns[i]).Name()); + set_info->expressions.push_back(std::move(exported.GetValue())); + } + set_info->condition = make_uniq( + ExpressionType::COMPARE_EQUAL, make_uniq(row_id_name.GetValue(), op.table.name), + LogicalPlanSQLExportHelpers::ChildColumn(child.GetValue(), row_id_field)); + auto node = make_uniq(); + node->table = ModifiedTableRef(op.table); + node->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(child.GetValue())); + node->set_info = std::move(set_info); + return LogicalPlanSQLExportResult::Success({std::move(node), std::move(fields.GetValue())}); +} + +//! The merged rows are keyed back to the target by its row id: a MATCHED source row carries the +//! matched row id, a NOT MATCHED (by target) row carries NULL and never rejoins. A target row the +//! source never matched does not appear as unmatched under this key, so WHEN NOT MATCHED BY SOURCE +//! actions cannot be reconstructed. +static LogicalPlanSQLExportResult ExportMergeNode(LogicalMergeInto &op, LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (op.children.size() != 1) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_source", "MERGE INTO without a source relation cannot be exported")}); + } + if (op.actions.count(MergeActionCondition::WHEN_NOT_MATCHED_BY_SOURCE)) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "merge_not_matched_by_source", + "WHEN NOT MATCHED BY SOURCE actions cannot be keyed by the exported row id")}); + } + auto row_id_name = ExportedRowIdName(op.table, path); + if (row_id_name.HasError()) { + return LogicalPlanSQLExportResult::Failure(row_id_name); + } + auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + // Keep the table's own name reserved so the generated source alias cannot capture it. + context.NextRelationAlias(op.table.name); + auto child = context.ExportChild(*op.children[0], LogicalPlanSQLExportHelpers::PlanChildPath(path, 0)); + if (child.HasError()) { + return LogicalPlanSQLExportResult::Failure(child); + } + // The bound source keeps the row id as its final column, after every action input. + if (op.row_id_start + 1 != child.GetValue().relation.fields.size()) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(path, "dml_row_id", "The merged row-id column was not found in the exported source")}); + } + auto expression_context = + LogicalPlanSQLExportHelpers::CreateBindingContext(context.GetClientContext(), {child.GetValue()}); + // The action inputs are exported in a stable order: per action its condition, then its expressions. + vector> action_inputs; + for (auto &entry : op.actions) { + for (auto &action : entry.second) { + if (action->condition) { + action_inputs.push_back(*action->condition); + } + for (auto &expression : action->expressions) { + action_inputs.push_back(*expression); + } + } + } + auto node = make_uniq(); + node->target = ModifiedTableRef(op.table); + node->join_condition = make_uniq( + ExpressionType::COMPARE_EQUAL, make_uniq(row_id_name.GetValue(), op.table.name), + LogicalPlanSQLExportHelpers::ChildColumn(child.GetValue(), op.row_id_start)); + idx_t input_ordinal = 0; + auto export_input = [&]() { + return context.ExportExpression(op, action_inputs, input_ordinal++, expression_context, path); + }; + for (auto &entry : op.actions) { + vector> actions; + for (auto &bound_action : entry.second) { + auto action = make_uniq(); + action->action_type = bound_action->action_type; + if (bound_action->condition) { + auto condition = export_input(); + if (condition.HasError()) { + return LogicalPlanSQLExportResult::Failure(condition); + } + action->condition = std::move(condition.GetValue()); + } + switch (bound_action->action_type) { + case MergeActionType::MERGE_UPDATE: { + if (bound_action->columns.size() != bound_action->expressions.size()) { + return LogicalPlanSQLExportResult::Failure({UnsupportedDML( + path, "merge_update", "The MERGE update action does not pair its columns and expressions")}); + } + auto set_info = make_uniq(); + for (idx_t i = 0; i < bound_action->columns.size(); i++) { + auto value = export_input(); + if (value.HasError()) { + return LogicalPlanSQLExportResult::Failure(value); + } + set_info->columns.push_back(op.table.GetColumns().GetColumn(bound_action->columns[i]).Name()); + set_info->expressions.push_back(std::move(value.GetValue())); + } + action->update_info = std::move(set_info); + break; + } + case MergeActionType::MERGE_INSERT: { + // the bound action provides every physical column in declaration order, with defaults filled in + for (auto &column : op.table.GetColumns().Physical()) { + action->insert_columns.push_back(column.Name()); + } + if (bound_action->expressions.size() != action->insert_columns.size()) { + return LogicalPlanSQLExportResult::Failure({UnsupportedDML( + path, "merge_insert", "The MERGE insert action does not cover the table's physical columns")}); + } + for (idx_t i = 0; i < bound_action->expressions.size(); i++) { + auto value = export_input(); + if (value.HasError()) { + return LogicalPlanSQLExportResult::Failure(value); + } + action->expressions.push_back(std::move(value.GetValue())); + } + break; + } + case MergeActionType::MERGE_ERROR: { + for (idx_t i = 0; i < bound_action->expressions.size(); i++) { + auto message = export_input(); + if (message.HasError()) { + return LogicalPlanSQLExportResult::Failure(message); + } + action->expressions.push_back(std::move(message.GetValue())); + } + break; + } + default: + // DELETE and DO NOTHING carry no inputs + break; + } + actions.push_back(std::move(action)); + } + node->actions.emplace(entry.first, std::move(actions)); + } + node->source = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(child.GetValue())); + return LogicalPlanSQLExportResult::Success({std::move(node), std::move(fields.GetValue())}); +} + +static optional DMLStatementIssue(const LogicalOperator &op, + const LogicalPlanVerificationPath &path) { + if (op.type == LogicalOperatorType::LOGICAL_UPDATE && op.Cast().capture_old_rows) { + return UnsupportedDML(path, "dml_old_rows", "UPDATE with OLD row capture cannot be exported"); + } + // A bare operator never returns modified rows; that shape only exists under a RETURNING projection. + bool return_chunk; + switch (op.type) { + case LogicalOperatorType::LOGICAL_INSERT: + return_chunk = op.Cast().return_chunk; + break; + case LogicalOperatorType::LOGICAL_UPDATE: + return_chunk = op.Cast().return_chunk; + break; + case LogicalOperatorType::LOGICAL_MERGE_INTO: + return_chunk = op.Cast().return_chunk; + break; + default: + return_chunk = op.Cast().return_chunk; + break; + } + if (return_chunk) { + return UnsupportedDML(path, "dml_returning", "A data-modifying operator returning rows cannot be exported"); + } + return optional(); +} + +static optional DMLBoundaryIssue(const LogicalPlanSQLExportContext &context, + const LogicalOperator &op, + const LogicalPlanVerificationPath &path) { + if (context.Ancestors().size() != 1) { + return UnsupportedDML(path, "dml_subplan", "A data-modifying operator below the plan root cannot be exported"); + } + return DMLStatementIssue(op, path); +} + +LogicalPlanSQLExportResult LogicalInsert::ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (auto issue = DMLBoundaryIssue(context, *this, path)) { + return LogicalPlanSQLExportResult::Failure({std::move(*issue)}); + } + return ExportInsertNode(*this, context, path); +} + +LogicalPlanSQLExportResult LogicalDelete::ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (auto issue = DMLBoundaryIssue(context, *this, path)) { + return LogicalPlanSQLExportResult::Failure({std::move(*issue)}); + } + return ExportDeleteNode(*this, context, path); +} + +LogicalPlanSQLExportResult LogicalUpdate::ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (auto issue = DMLBoundaryIssue(context, *this, path)) { + return LogicalPlanSQLExportResult::Failure({std::move(*issue)}); + } + return ExportUpdateNode(*this, context, path); +} + +LogicalPlanSQLExportResult LogicalMergeInto::ToSQL(LogicalPlanSQLExportContext &context, + const LogicalPlanVerificationPath &path) { + if (auto issue = DMLBoundaryIssue(context, *this, path)) { + return LogicalPlanSQLExportResult::Failure({std::move(*issue)}); + } + return ExportMergeNode(*this, context, path); +} + +//! An identity projection over a data-modifying body only forwards its columns under new bindings. +static bool IsIdentityProjectionOverDML(LogicalOperator &op) { + if (op.type != LogicalOperatorType::LOGICAL_PROJECTION || op.children.size() != 1 || + !SQLExportDMLHelpers::MatchDMLBody(*op.children[0])) { + return false; + } + auto child_fields = LogicalPlanSQLExportHelpers::CreateFields(*op.children[0], LogicalPlanVerificationPath()); + return child_fields.IsSuccess() && + LogicalPlanSQLExportHelpers::IsIdentityProjection(op.Cast(), child_fields.GetValue()); +} + +bool SQLExportDMLHelpers::MatchDMLBody(LogicalOperator &op) { + switch (op.type) { + case LogicalOperatorType::LOGICAL_INSERT: + case LogicalOperatorType::LOGICAL_UPDATE: + case LogicalOperatorType::LOGICAL_DELETE: + case LogicalOperatorType::LOGICAL_MERGE_INTO: + return true; + default: + return MatchReturningRoot(op) || IsIdentityProjectionOverDML(op); + } +} + +bool SQLExportDMLHelpers::IsDMLNode(const QueryNode &query) { + switch (query.type) { + case QueryNodeType::INSERT_QUERY_NODE: + case QueryNodeType::UPDATE_QUERY_NODE: + case QueryNodeType::DELETE_QUERY_NODE: + case QueryNodeType::MERGE_QUERY_NODE: + return true; + default: + return false; + } +} + +LogicalPlanSQLExportResult SQLExportDMLHelpers::ExportBody(LogicalPlanSQLExportContext &context, LogicalOperator &op, + const LogicalPlanVerificationPath &path) { + if (auto returning = MatchReturningRoot(op)) { + return ExportReturning(context, *returning, optional>(), path); + } + if (IsIdentityProjectionOverDML(op)) { + auto fields = LogicalPlanSQLExportHelpers::CreateFields(op, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + LogicalPlanSQLExportContext::AncestorScope projection_scope(context, op); + auto exported = ExportBody(context, *op.children[0], LogicalPlanSQLExportHelpers::PlanChildPath(path, 0)); + if (exported.HasError()) { + return exported; + } + // the projection only renames the forwarded columns' bindings + D_ASSERT(exported.GetValue().fields.size() == fields.GetValue().size()); + exported.GetValue().fields = std::move(fields.GetValue()); + return exported; + } + if (auto issue = DMLStatementIssue(op, path)) { + return LogicalPlanSQLExportResult::Failure({std::move(*issue)}); + } + LogicalPlanSQLExportContext::AncestorScope op_scope(context, op); + switch (op.type) { + case LogicalOperatorType::LOGICAL_INSERT: + return ExportInsertNode(op.Cast(), context, path); + case LogicalOperatorType::LOGICAL_UPDATE: + return ExportUpdateNode(op.Cast(), context, path); + case LogicalOperatorType::LOGICAL_MERGE_INTO: + return ExportMergeNode(op.Cast(), context, path); + default: + return ExportDeleteNode(op.Cast(), context, path); + } +} + +optional_ptr SQLExportDMLHelpers::MatchReturningRoot(LogicalOperator &root) { + if (root.type != LogicalOperatorType::LOGICAL_PROJECTION || root.children.size() != 1) { + return nullptr; + } + auto &child = *root.children[0]; + switch (child.type) { + case LogicalOperatorType::LOGICAL_INSERT: + if (!child.Cast().return_chunk) { + return nullptr; + } + break; + case LogicalOperatorType::LOGICAL_UPDATE: + if (!child.Cast().return_chunk) { + return nullptr; + } + break; + case LogicalOperatorType::LOGICAL_DELETE: + if (!child.Cast().return_chunk) { + return nullptr; + } + break; + case LogicalOperatorType::LOGICAL_MERGE_INTO: + if (!child.Cast().return_chunk) { + return nullptr; + } + break; + default: + return nullptr; + } + return &root.Cast(); +} + +//! Binding context resolving the modified rows a RETURNING projection references: the table's physical +//! columns bind positionally, and each referenced virtual column (e.g. rowid) is appended behind them +//! carrying the column name it was bound from. +static LogicalPlanVerificationResult +ReturningBindingContext(ClientContext &client, const LogicalProjection &projection, const TableCatalogEntry &table, + TableIndex table_index, const LogicalPlanVerificationPath &path, + vector statement_columns = {}) { + column_binding_map_t entries; + idx_t physical_count = 0; + for (auto &column : table.GetColumns().Physical()) { + entries.emplace(ColumnBinding(table_index, ProjectionIndex(physical_count++)), + ResolvedSQLColumnReference {{column.Name()}, column.Type()}); + } + for (auto &entry : table.GetVirtualColumns()) { + statement_columns.push_back(entry.second); + } + optional unsupported; + for (auto &expression : projection.expressions) { + ExpressionIterator::VisitExpression( + *expression, [&](const BoundColumnRefExpression &column) { + if (unsupported || column.Binding().table_index != table_index || + column.Binding().column_index < physical_count) { + return; + } + for (auto &entry : statement_columns) { + if (entry.name != column.GetAlias() || table.ColumnExists(entry.name) || + !entry.type.EqualsIncludingCollation(column.GetReturnType())) { + continue; + } + entries.emplace(column.Binding(), ResolvedSQLColumnReference {{entry.name}, entry.type}); + return; + } + unsupported = UnsupportedDML(path, "dml_returning_reference", + "RETURNING references a column SQL cannot name on the modified table"); + }); + } + if (unsupported) { + return LogicalPlanVerificationResult::Failure({std::move(*unsupported)}); + } + BoundExpressionSQLExportContext result; + result.client_context = &client; + result.discard_optimizer_metadata = true; + result.resolve_binding = + [entries = std::move(entries)](const ColumnBinding &binding) -> optional { + auto entry = entries.find(binding); + if (entry != entries.end()) { + return entry->second; + } + return {}; + }; + return LogicalPlanVerificationResult::Success(std::move(result)); +} + +LogicalPlanSQLExportResult SQLExportDMLHelpers::ExportReturning(LogicalPlanSQLExportContext &context, + LogicalProjection &root, + const optional> &output_names, + const LogicalPlanVerificationPath &path) { + auto fields = LogicalPlanSQLExportHelpers::CreateFields(root, path); + if (fields.HasError()) { + return LogicalPlanSQLExportResult::Failure(fields); + } + auto &op = *root.children[0]; + auto child_path = LogicalPlanSQLExportHelpers::PlanChildPath(path, 0); + LogicalPlanSQLExportContext::AncestorScope root_scope(context, root); + LogicalPlanSQLExportContext::AncestorScope op_scope(context, op); + optional_ptr table; + TableIndex table_index; + vector statement_columns; + auto exported = [&]() -> LogicalPlanSQLExportResult { + switch (op.type) { + case LogicalOperatorType::LOGICAL_INSERT: { + auto &insert = op.Cast(); + table = &insert.table; + table_index = insert.table_index; + return ExportInsertNode(insert, context, child_path); + } + case LogicalOperatorType::LOGICAL_UPDATE: { + auto &update = op.Cast(); + if (update.capture_old_rows) { + return LogicalPlanSQLExportResult::Failure( + {UnsupportedDML(child_path, "dml_old_rows", "UPDATE with OLD row capture cannot be exported")}); + } + table = &update.table; + table_index = update.table_index; + return ExportUpdateNode(update, context, child_path); + } + case LogicalOperatorType::LOGICAL_MERGE_INTO: { + auto &merge = op.Cast(); + table = &merge.table; + table_index = merge.table_index; + // MERGE RETURNING binds the statement-provided merge_action column instead of the table's virtuals + statement_columns.emplace_back(Identifier("merge_action"), LogicalType::VARCHAR); + return ExportMergeNode(merge, context, child_path); + } + default: { + auto &del = op.Cast(); + table = &del.table; + table_index = del.table_index; + return ExportDeleteNode(del, context, child_path); + } + } + }(); + if (exported.HasError()) { + return exported; + } + auto expression_context = ReturningBindingContext(context.GetClientContext(), root, *table, table_index, path, + std::move(statement_columns)); + if (expression_context.HasError()) { + return LogicalPlanSQLExportResult::Failure(expression_context); + } + auto expressions = LogicalPlanSQLExportHelpers::CollectExpressions(root); + vector> returning_list; + for (idx_t i = 0; i < root.expressions.size(); i++) { + auto expression = context.ExportExpression(root, expressions, i, expression_context.GetValue(), path); + if (expression.HasError()) { + return LogicalPlanSQLExportResult::Failure(expression); + } + if (output_names && output_names->size() == root.expressions.size()) { + expression.GetValue()->SetAlias((*output_names)[i]); + } else if (!root.expressions[i]->GetAlias().empty()) { + expression.GetValue()->SetAlias(root.expressions[i]->GetAlias()); + } + returning_list.push_back(std::move(expression.GetValue())); + } + auto &query = *exported.GetValue().query; + switch (query.type) { + case QueryNodeType::INSERT_QUERY_NODE: + query.Cast().returning_list = std::move(returning_list); + break; + case QueryNodeType::UPDATE_QUERY_NODE: + query.Cast().returning_list = std::move(returning_list); + break; + case QueryNodeType::MERGE_QUERY_NODE: + query.Cast().returning_list = std::move(returning_list); + break; + default: + query.Cast().returning_list = std::move(returning_list); + break; + } + return LogicalPlanSQLExportResult::Success({std::move(exported.GetValue().query), std::move(fields.GetValue())}); +} + +} // namespace duckdb diff --git a/src/duckdb/src/planner/sql_export/sql_export_joins.cpp b/src/duckdb/src/planner/sql_export/sql_export_joins.cpp index 77d4c1fd6..a8c70b1c0 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_joins.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_joins.cpp @@ -13,18 +13,19 @@ namespace duckdb { static string MarkConditionUnsupportedReason(const LogicalComparisonJoin &join) { + if (join.HasNullSafeMarkJoinConditions()) { + return string(); + } bool comparisons_only = !join.conditions.empty(); bool all_equal = true; - bool all_null_safe = true; for (auto &condition : join.conditions) { if (!condition.IsComparison()) { comparisons_only = false; continue; } all_equal &= condition.GetComparisonType() == ExpressionType::COMPARE_EQUAL; - all_null_safe &= condition.GetComparisonType() == ExpressionType::COMPARE_NOT_DISTINCT_FROM; } - const bool has_supported_conjunction = join.conditions.size() == 1 || all_equal || all_null_safe; + const bool has_supported_conjunction = join.conditions.size() == 1 || all_equal; if (!comparisons_only || !has_supported_conjunction) { return "The MARK condition requires conjunction execution semantics"; } diff --git a/src/duckdb/src/planner/sql_export/sql_export_limit.cpp b/src/duckdb/src/planner/sql_export/sql_export_limit.cpp index 0496d0ec9..4cc9c8911 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_limit.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_limit.cpp @@ -86,6 +86,11 @@ LimitSQLExporter::LimitExpressionResult LimitSQLExporter::LimitBindingFailure(co LimitSQLExporter::LimitExpressionResult LimitSQLExporter::ResolveLimitColumn(const ColumnBinding &binding, LogicalOperator &input, const LogicalPlanVerificationPath &path) { + if (context.FindSource(input)) { + // A row-stream replacement exposes columns, not the original expressions + // that made this LIMIT input independent of the rows being limited. + return LimitBindingFailure(path); + } auto bindings = input.GetColumnBindings(); auto found = std::find(bindings.begin(), bindings.end(), binding); if (found == bindings.end()) { diff --git a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp index 2a32c15e2..2806aa549 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp @@ -502,7 +502,8 @@ LogicalPlanSQLExportResult LogicalWindow::ToSQL(LogicalPlanSQLExportContext &con if (fields.HasError()) { return LogicalPlanSQLExportResult::Failure(fields); } - if (op.children[0]->type == LogicalOperatorType::LOGICAL_GET) { + // A replaced input exports as a named source, so its ordinality fusion does not apply. + if (op.children[0]->type == LogicalOperatorType::LOGICAL_GET && !context.FindSource(*op.children[0])) { auto &get = op.children[0]->Cast(); if (get.source_ordinality == OrdinalityType::WITH_ORDINALITY && !get.ordinality_idx.IsValid()) { bool supported = diff --git a/src/duckdb/src/planner/statement_preprocessor.cpp b/src/duckdb/src/planner/statement_preprocessor.cpp index f3268cce0..a7c210d39 100644 --- a/src/duckdb/src/planner/statement_preprocessor.cpp +++ b/src/duckdb/src/planner/statement_preprocessor.cpp @@ -111,7 +111,7 @@ vector> StatementPreprocessor::TryReparsePragma(unique_ const auto bound_info = binder->BindPragma(*info, error_context); if (bound_info->function.query) { // Needs reparsing - FunctionParameters parameters {bound_info->parameters, bound_info->named_parameters}; + FunctionParameters parameters {bound_info->parameters, bound_info->named_parameters, &bound_info->function}; const auto query_to_reparse = bound_info->function.query(context, parameters); Parser parser(context); parser.ParseQuery(query_to_reparse); diff --git a/src/duckdb/src/storage/arena_allocator.cpp b/src/duckdb/src/storage/arena_allocator.cpp index e5ad43294..29d4b314a 100644 --- a/src/duckdb/src/storage/arena_allocator.cpp +++ b/src/duckdb/src/storage/arena_allocator.cpp @@ -133,6 +133,33 @@ data_ptr_t ArenaAllocator::ReallocateAligned(data_ptr_t pointer, idx_t old_size, return Reallocate(pointer, old_size, AlignValue(size)); } +ArenaAllocator::Position ArenaAllocator::GetPosition() const { + Position result; + if (head) { + result.chunk = head.get(); + result.current_position = head->current_position; + } + return result; +} + +void ArenaAllocator::Rewind(const Position &position) { + if (!position.chunk) { + // nothing was allocated when the position was taken + FreeAll(); + return; + } + // destroy the chunks that were allocated after the position was taken, newest first + while (head.get() != position.chunk) { + D_ASSERT(head && head->next); + allocated_size -= head->maximum_size; + auto older = std::move(head->next); + head = std::move(older); + head->prev = nullptr; + } + D_ASSERT(position.current_position <= head->current_position); + head->current_position = position.current_position; +} + void ArenaAllocator::FreeAll() { Reset(); if (head) { diff --git a/src/duckdb/src/storage/buffer/block_handle.cpp b/src/duckdb/src/storage/buffer/block_handle.cpp index 844871e02..8bfc529ca 100644 --- a/src/duckdb/src/storage/buffer/block_handle.cpp +++ b/src/duckdb/src/storage/buffer/block_handle.cpp @@ -15,8 +15,7 @@ BlockMemory::BlockMemory(BufferManager &buffer_manager, block_id_t block_id_p, M : buffer_manager(buffer_manager), block_id(block_id_p), state(BlockState::BLOCK_UNLOADED), readers(0), tag(tag_p), buffer_type(FileBufferType::BLOCK), buffer(nullptr), eviction_seq_num(0), has_queue_entry(false), lru_timestamp_msec(), destroy_buffer_upon(DestroyBufferUpon::BLOCK), memory_usage(block_alloc_size_p), - memory_charge(tag, buffer_manager.GetBufferPool()), unswizzled(nullptr), - eviction_queue_idx(DConstants::INVALID_INDEX) { + memory_charge(tag, buffer_manager.GetBufferPool()), eviction_queue_idx(DConstants::INVALID_INDEX) { } BlockMemory::BlockMemory(BufferManager &buffer_manager, block_id_t block_id_p, MemoryTag tag_p, @@ -25,14 +24,11 @@ BlockMemory::BlockMemory(BufferManager &buffer_manager, block_id_t block_id_p, M : buffer_manager(buffer_manager), block_id(block_id_p), state(BlockState::BLOCK_LOADED), readers(0), tag(tag_p), buffer_type(buffer_p->GetBufferType()), buffer(std::move(buffer_p)), eviction_seq_num(0), has_queue_entry(false), lru_timestamp_msec(), destroy_buffer_upon(destroy_buffer_upon_p), memory_usage(size_p), - memory_charge(tag, buffer_manager.GetBufferPool()), unswizzled(nullptr), - eviction_queue_idx(DConstants::INVALID_INDEX) { + memory_charge(tag, buffer_manager.GetBufferPool()), eviction_queue_idx(DConstants::INVALID_INDEX) { memory_charge = std::move(reservation); // Moved to constructor body due to tidy check. } BlockMemory::~BlockMemory() { // NOLINT: allow internal exceptions - // The block memory is being destroyed, meaning that any unswizzled pointers are now binary junk. - SetSwizzling(nullptr); D_ASSERT(!GetBuffer() || GetBuffer()->GetBufferType() == GetBufferType()); if (HasLiveQueueEntry() && GetBufferType() != FileBufferType::TINY_BUFFER) { // The block still has a live entry in the eviction queue. That entry is now dead; @@ -131,7 +127,6 @@ unique_ptr BlockMemory::UnloadAndTakeBlock(BlockLock &l, QueryContex // The block was already unloaded: nothing to do. return nullptr; } - D_ASSERT(IsSwizzled()); D_ASSERT(CanUnload() == CanUnloadResult::CAN_UNLOAD); if (BlockId() >= MAXIMUM_BLOCK && MustWriteToTemporaryFile()) { @@ -211,16 +206,17 @@ BufferHandle BlockHandle::LoadFromBuffer(BlockLock &l, data_ptr_t data, unique_p memcpy(block->InternalBuffer(), data, block->AllocSize()); memory.GetBuffer() = std::move(block); memory.SetState(BlockState::BLOCK_LOADED); - memory.SetReaders(1); + memory.SetReaders(l, 1); memory.GetMemoryCharge() = std::move(reservation); return BufferHandle(shared_from_this(), memory.GetBuffer()); } -BufferHandle BlockHandle::Load(QueryContext context, unique_ptr reusable_buffer) { +BufferHandle BlockHandle::Load(BlockLock &l, QueryContext context, unique_ptr reusable_buffer) { + memory.VerifyMutex(l); if (memory.GetState() == BlockState::BLOCK_LOADED) { // The block has already been loaded. D_ASSERT(memory.GetBuffer()); - memory.IncrementReaders(); + memory.IncrementReaders(l); return BufferHandle(shared_from_this(), memory.GetBuffer()); } @@ -240,7 +236,7 @@ BufferHandle BlockHandle::Load(QueryContext context, unique_ptr reus buffer = buffer_manager.ReadTemporaryBuffer(context, memory.GetMemoryTag(), *this, std::move(reusable_buffer)); } memory.SetState(BlockState::BLOCK_LOADED); - memory.SetReaders(1); + memory.SetReaders(l, 1); return BufferHandle(shared_from_this(), memory.GetBuffer()); } diff --git a/src/duckdb/src/storage/buffer/buffer_pool.cpp b/src/duckdb/src/storage/buffer/buffer_pool.cpp index 9a0dc97c0..dc5e68a38 100644 --- a/src/duckdb/src/storage/buffer/buffer_pool.cpp +++ b/src/duckdb/src/storage/buffer/buffer_pool.cpp @@ -287,7 +287,7 @@ bool BufferPool::AddToEvictionQueue(BlockLock &lock, shared_ptr &ha queue.IncrementDeadNodes(); } - auto ts = memory.NextEvictionSequenceNumber(); + auto ts = memory.NextEvictionSequenceNumber(lock); if (track_eviction_timestamps) { memory.SetLRUTimestamp(std::chrono::time_point_cast(std::chrono::steady_clock::now()) .time_since_epoch() @@ -624,29 +624,36 @@ BufferPool::MemoryUsage::MemoryUsage() { v = 0; } for (auto &cache : memory_usage_caches) { - for (auto &v : cache) { + for (auto &v : cache.counters) { v = 0; } } } void BufferPool::MemoryUsage::UpdateUsedMemory(MemoryTag tag, int64_t size) { - auto tag_idx = (idx_t)tag; - if ((idx_t)AbsValue(size) < MEMORY_USAGE_CACHE_THRESHOLD) { + if (size == 0) { + return; + } + auto tag_idx = static_cast(tag); + if (static_cast(AbsValue(size)) < MEMORY_USAGE_TAG_CACHE_THRESHOLD) { // update cache and update global counter when cache exceeds threshold // Get corresponding cache slot based on current CPU core index // Two threads may access the same cache simultaneously, // ensuring correctness through atomic operations - auto cache_idx = (idx_t)TaskScheduler::GetEstimatedCPUId() % MEMORY_USAGE_CACHE_COUNT; - auto &cache = memory_usage_caches[cache_idx]; + auto cache_idx = static_cast(TaskScheduler::GetEstimatedCPUId()) % MEMORY_USAGE_CACHE_COUNT; + auto &cache = memory_usage_caches[cache_idx].counters; auto new_tag_size = cache[tag_idx].fetch_add(size, std::memory_order_relaxed) + size; - if ((idx_t)AbsValue(new_tag_size) >= MEMORY_USAGE_CACHE_THRESHOLD) { + if (static_cast(AbsValue(new_tag_size)) >= MEMORY_USAGE_TAG_CACHE_THRESHOLD) { // cached tag memory usage exceeds threshold auto tag_size = cache[tag_idx].exchange(0, std::memory_order_relaxed); memory_usage[tag_idx].fetch_add(tag_size, std::memory_order_relaxed); } + if (static_cast(AbsValue(size)) >= MEMORY_USAGE_CACHE_THRESHOLD) { + memory_usage[TOTAL_MEMORY_USAGE_INDEX].fetch_add(size, std::memory_order_relaxed); + return; + } auto new_total_size = cache[TOTAL_MEMORY_USAGE_INDEX].fetch_add(size, std::memory_order_relaxed) + size; - if ((idx_t)AbsValue(new_total_size) >= MEMORY_USAGE_CACHE_THRESHOLD) { + if (static_cast(AbsValue(new_total_size)) >= MEMORY_USAGE_CACHE_THRESHOLD) { // cached total memory usage exceeds threshold auto total_size = cache[TOTAL_MEMORY_USAGE_INDEX].exchange(0, std::memory_order_relaxed); memory_usage[TOTAL_MEMORY_USAGE_INDEX].fetch_add(total_size, std::memory_order_relaxed); diff --git a/src/duckdb/src/storage/checkpoint_manager.cpp b/src/duckdb/src/storage/checkpoint_manager.cpp index 3a4536e28..05574c6ce 100644 --- a/src/duckdb/src/storage/checkpoint_manager.cpp +++ b/src/duckdb/src/storage/checkpoint_manager.cpp @@ -48,7 +48,7 @@ ActiveCheckpointWrapper::ActiveCheckpointWrapper(optional_ptr con if (!context) { return; } - checkpoint_connection = make_uniq(db.GetDatabase()); + checkpoint_connection = make_uniq(Connection::CreateInternal(db.GetDatabase())); checkpoint_context = checkpoint_connection->context.get(); } diff --git a/src/duckdb/src/storage/compression/bitpacking.cpp b/src/duckdb/src/storage/compression/bitpacking.cpp index 92e64b470..b491b6c3b 100644 --- a/src/duckdb/src/storage/compression/bitpacking.cpp +++ b/src/duckdb/src/storage/compression/bitpacking.cpp @@ -81,6 +81,58 @@ static void ValidateBitpackingMode(BitpackingMode mode) { throw DataCorruptionException("Corrupted bitpacking segment: read exceeds the segment row count"); } +[[noreturn]] static void ThrowBitpackingInvalidBoolean() { + throw DataCorruptionException("Corrupted bitpacking segment: BOOL group contains values other than 0 and 1"); +} + +//! BOOL is bitpacked as int8_t - verify from the group header that the group can only decode to 0 and 1. The values of +//! DELTA_FOR groups also depend on the deltas, so these are verified after decoding (VerifyBooleanValues). +template +static void ValidateBooleanGroup(BitpackingMode mode, bitpacking_width_t width, T frame_of_reference, T &constant, + idx_t count) { + auto first = static_cast(frame_of_reference); + int64_t last; + switch (mode) { + case BitpackingMode::CONSTANT: + if (constant == NumericLimits::Minimum()) { + // groups that only contain NULLs store the initial maximum as their constant + constant = 0; + } + first = static_cast(constant); + last = first; + break; + case BitpackingMode::FOR: + if (width > 1) { + ThrowBitpackingInvalidBoolean(); + } + last = first + width; + break; + case BitpackingMode::CONSTANT_DELTA: + last = first + static_cast(count - 1) * static_cast(constant); + break; + case BitpackingMode::DELTA_FOR: + // the deltas between 0 and 1 values are in [-1, 1] + if (width > 2 || first < -1 || first + (int64_t(1) << width) - 1 > 1) { + ThrowBitpackingInvalidBoolean(); + } + return; + default: + ThrowBitpackingInvalidBoolean(); + } + if (first < 0 || first > 1 || last < 0 || last > 1) { + ThrowBitpackingInvalidBoolean(); + } +} + +template +static void VerifyBooleanValues(const T *values, idx_t count) { + for (idx_t i = 0; i < count; i++) { + if (values[i] != 0 && values[i] != 1) { + ThrowBitpackingInvalidBoolean(); + } + } +} + template ::type> static bitpacking_width_t ValidateBitpackingWidth(T stored_width) { if (static_cast(stored_width) > sizeof(T) * 8) { @@ -711,8 +763,9 @@ struct BitpackingScanState : public SegmentScanState { explicit BitpackingScanState(BufferHandle handle_p, ColumnSegment &segment) : handle(std::move(handle_p)), reader(CompressionSegmentReader::FromSegment(handle, segment, "bitpacking segment")), - segment_count(segment.count.load()), group_count(segment_count / BITPACKING_METADATA_GROUP_SIZE + - (segment_count % BITPACKING_METADATA_GROUP_SIZE != 0)), + is_bool(segment.GetType().InternalType() == PhysicalType::BOOL), segment_count(segment.count.load()), + group_count(segment_count / BITPACKING_METADATA_GROUP_SIZE + + (segment_count % BITPACKING_METADATA_GROUP_SIZE != 0)), metadata_table_start(GetMetadataTableStart(reader, group_count)), metadata_table(reader.GetArray(metadata_table_start, group_count)) { if (metadata_table_start < BitpackingPrimitives::BITPACKING_HEADER_SIZE) { @@ -726,6 +779,8 @@ struct BitpackingScanState : public SegmentScanState { BufferHandle handle; //! Group data between the segment header and reverse metadata table. CompressionSegmentReader reader; + //! BOOL segments are bitpacked as int8_t, but may only contain 0 and 1 + bool is_bool; //! Segment row count retained for consistent group bounds. idx_t segment_count; @@ -833,6 +888,10 @@ struct BitpackingScanState : public SegmentScanState { // The row count for this group comes from the segment count read from disk. auto group_row_count = MinValue(BITPACKING_METADATA_GROUP_SIZE, segment_count - group_index * BITPACKING_METADATA_GROUP_SIZE); + if (is_bool) { + ValidateBooleanGroup(current_group.mode, current_width, current_frame_of_reference, current_constant, + group_row_count); + } // The payload size comes from the group row count and width read from disk, so calculate and validate it. idx_t algorithm_group_count = 0; @@ -1056,6 +1115,9 @@ void BitpackingScanPartial(ColumnSegment &segment, ColumnScanState &state, idx_t ApplyFrameOfReference(current_results, static_cast(current_group.frame_of_reference)); current_group.delta_offset = static_cast(DeltaDecode(current_results, static_cast(current_group.delta_offset))); + if (scan_state.is_bool) { + VerifyBooleanValues(current_result_ptr, to_scan); + } } else { ApplyFrameOfReference(unsafe_array_ptr(current_result_ptr, to_scan), current_group.frame_of_reference); @@ -1130,6 +1192,9 @@ void BitpackingFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t r value += static_cast(group.delta_offset); } *current_result_ptr = static_cast(value); + if (scan_state.is_bool) { + VerifyBooleanValues(current_result_ptr, 1); + } } template diff --git a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp index 758c17019..a6e555a82 100644 --- a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp @@ -62,6 +62,10 @@ void CompressedStringScanState::Initialize(bool initialize_dictionary) { dictionary_indices_width = (bitpacking_width_t)(Load(data_ptr_cast(&header_ptr->dictionary_indices_width))); string_lengths_width = (bitpacking_width_t)(Load(data_ptr_cast(&header_ptr->string_lengths_width))); + if (dictionary_indices_width > sizeof(uint32_t) * 8 || string_lengths_width > sizeof(uint32_t) * 8) { + throw IOException("Failed to scan dictionary string - bitpacking width was invalid. Database file appears to " + "be corrupted."); + } auto string_lengths_space = BitpackingPrimitives::GetRequiredSize(dict_count, string_lengths_width); auto dictionary_indices_space = diff --git a/src/duckdb/src/storage/compression/dictionary/analyze.cpp b/src/duckdb/src/storage/compression/dictionary/analyze.cpp index d7ff65164..efbda4dc6 100644 --- a/src/duckdb/src/storage/compression/dictionary/analyze.cpp +++ b/src/duckdb/src/storage/compression/dictionary/analyze.cpp @@ -4,7 +4,7 @@ namespace duckdb { DictionaryAnalyzeState::DictionaryAnalyzeState(BlockManager &block_manager) : AnalyzeState(block_manager), segment_count(0), current_tuple_count(0), current_unique_count(0), - current_dict_size(0), current_width(0), next_width(0) { + current_dict_size(0), heap(block_manager.buffer_manager.GetBufferAllocator()), current_width(0), next_width(0) { } bool DictionaryAnalyzeState::LookupString(string_t str) { diff --git a/src/duckdb/src/storage/compression/dictionary/compression.cpp b/src/duckdb/src/storage/compression/dictionary/compression.cpp index 3c100ed16..5dd79e061 100644 --- a/src/duckdb/src/storage/compression/dictionary/compression.cpp +++ b/src/duckdb/src/storage/compression/dictionary/compression.cpp @@ -146,8 +146,9 @@ idx_t DictionaryCompressionCompressState::Finalize() { D_ASSERT(current_width == BitpackingPrimitives::MinimumBitWidth(index_buffer.size() - 1)); D_ASSERT(DictionaryCompression::HasEnoughSpace(current_segment->count, index_buffer.size(), current_dictionary.size, current_width, info.GetBlockSize())); - D_ASSERT((uint64_t)*max_element(std::begin(selection_buffer), std::end(selection_buffer)) == - index_buffer.size() - 1); + // an empty segment (e.g. the child of a column of empty lists) has no selection entries + D_ASSERT(selection_buffer.empty() || (uint64_t)*max_element(std::begin(selection_buffer), + std::end(selection_buffer)) == index_buffer.size() - 1); // Early-out, if the block is sufficiently full. if (total_size >= info.GetCompactionFlushLimit()) { diff --git a/src/duckdb/src/storage/compression/fsst.cpp b/src/duckdb/src/storage/compression/fsst.cpp index 25dbe3a76..b30090418 100644 --- a/src/duckdb/src/storage/compression/fsst.cpp +++ b/src/duckdb/src/storage/compression/fsst.cpp @@ -68,7 +68,8 @@ struct FSSTStorage { //===--------------------------------------------------------------------===// struct FSSTAnalyzeState : public AnalyzeState { explicit FSSTAnalyzeState(BlockManager &block_manager) - : AnalyzeState(block_manager), count(0), fsst_string_total_size(0), empty_strings(0) { + : AnalyzeState(block_manager), count(0), fsst_string_heap(block_manager.buffer_manager.GetBufferAllocator()), + fsst_string_total_size(0), empty_strings(0) { } ~FSSTAnalyzeState() override { @@ -155,30 +156,37 @@ idx_t FSSTStorage::StringFinalAnalyze(AnalyzeState &state_p) { size_t output_buffer_size = 7 + 2 * state.fsst_string_total_size; // size as specified in fsst.h - vector fsst_string_sizes; - vector fsst_string_ptrs; - for (auto &str : state.fsst_strings) { - fsst_string_sizes.push_back(str.GetSize()); - fsst_string_ptrs.push_back((unsigned char *)str.GetData()); // NOLINT + // the sample can be large, so all scratch buffers are allocated through the buffer allocator + auto &allocator = state.info.GetBlockManager().buffer_manager.GetBufferAllocator(); + auto fsst_string_sizes_data = allocator.Allocate(string_count * sizeof(size_t)); + auto fsst_string_ptrs_data = allocator.Allocate(string_count * sizeof(unsigned char *)); + auto fsst_string_sizes = reinterpret_cast(fsst_string_sizes_data.get()); + auto fsst_string_ptrs = reinterpret_cast(fsst_string_ptrs_data.get()); + for (idx_t i = 0; i < string_count; i++) { + auto &str = state.fsst_strings[i]; + fsst_string_sizes[i] = str.GetSize(); + fsst_string_ptrs[i] = (unsigned char *)str.GetData(); // NOLINT } - state.fsst_encoder = duckdb_fsst_create(string_count, &fsst_string_sizes[0], &fsst_string_ptrs[0], 0); + state.fsst_encoder = duckdb_fsst_create(string_count, fsst_string_sizes, fsst_string_ptrs, 0); // TODO: do we really need to encode to get a size estimate? - auto compressed_ptrs = vector(string_count, nullptr); - auto compressed_sizes = vector(string_count, 0); - unique_ptr compressed_buffer(new unsigned char[output_buffer_size]); + auto compressed_ptrs_data = allocator.Allocate(string_count * sizeof(unsigned char *)); + auto compressed_sizes_data = allocator.Allocate(string_count * sizeof(size_t)); + auto compressed_ptrs = reinterpret_cast(compressed_ptrs_data.get()); + auto compressed_sizes = reinterpret_cast(compressed_sizes_data.get()); + auto compressed_buffer = allocator.Allocate(output_buffer_size); - auto res = - duckdb_fsst_compress(state.fsst_encoder, string_count, &fsst_string_sizes[0], &fsst_string_ptrs[0], - output_buffer_size, compressed_buffer.get(), &compressed_sizes[0], &compressed_ptrs[0]); + auto res = duckdb_fsst_compress(state.fsst_encoder, string_count, fsst_string_sizes, fsst_string_ptrs, + output_buffer_size, compressed_buffer.get(), compressed_sizes, compressed_ptrs); if (string_count != res) { throw std::runtime_error("FSST output buffer is too small unexpectedly"); } // Sum and and Max compressed lengths - for (auto &size : compressed_sizes) { + for (idx_t i = 0; i < string_count; i++) { + const auto size = compressed_sizes[i]; compressed_dict_size += size; max_compressed_string_length = MaxValue(max_compressed_string_length, size); } diff --git a/src/duckdb/src/storage/compression/numeric_constant.cpp b/src/duckdb/src/storage/compression/numeric_constant.cpp index 49296bc10..94d751c24 100644 --- a/src/duckdb/src/storage/compression/numeric_constant.cpp +++ b/src/duckdb/src/storage/compression/numeric_constant.cpp @@ -187,6 +187,9 @@ template void ConstantFillFunction(ColumnSegment &segment, Vector &result, idx_t start_idx, idx_t count) { const auto &nstats = segment.GetStats(); + if (!NumericStats::HasMin(nstats)) { + throw DataCorruptionException("Constant segment is missing its constant value"); + } auto data = FlatVector::GetDataMutable(result); auto constant_value = NumericStats::GetMin(nstats); for (idx_t i = 0; i < count; i++) { diff --git a/src/duckdb/src/storage/data_table.cpp b/src/duckdb/src/storage/data_table.cpp index 2d068c373..a7884c327 100644 --- a/src/duckdb/src/storage/data_table.cpp +++ b/src/duckdb/src/storage/data_table.cpp @@ -1584,7 +1584,7 @@ void DataTable::Checkpoint(TableDataWriter &writer, Serializer &serializer) { writer.SetRowGroupCount(info->CheckpointRowGroupCount(writer.GetCheckpointOptions())); // checkpoint each individual row group TableStatistics global_stats; - row_groups->Checkpoint(writer, global_stats); + const auto has_new_stats = row_groups->Checkpoint(writer, global_stats); row_groups->SetRowGroupAppendMode(RowGroupAppendMode::SUGGEST_NEW); if (writer.GetRebuildIndexes()) { MetricsTimer timer; @@ -1602,7 +1602,9 @@ void DataTable::Checkpoint(TableDataWriter &writer, Serializer &serializer) { // table pointer // index data writer.FinalizeTable(global_stats, *info, *row_groups, serializer); - row_groups->SetStats(global_stats); + if (has_new_stats) { + row_groups->SetStats(global_stats); + } } void DataTable::CommitDropColumn(const idx_t column_index, CommitDropState &drop_state) { diff --git a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp index 15d9d4c59..5d9dbd3cb 100644 --- a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp +++ b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp @@ -144,6 +144,30 @@ class FetchBlockTask : public BaseExecutorTask { BufferHandle &result_pin; }; +//===----------------------------------------------------------------------===// +// ReadRangeTask +//===----------------------------------------------------------------------===// + +class ReadRangeTask : public BaseExecutorTask { +public: + ReadRangeTask(CachingFileHandle &caching_file_handle_p, TaskExecutor &executor, QueryContext context_p, + data_ptr_t buffer_p, idx_t nr_bytes_p, idx_t location_p) + : BaseExecutorTask(executor), caching_file_handle(caching_file_handle_p), context(context_p), buffer(buffer_p), + nr_bytes(nr_bytes_p), location(location_p) { + } + + void ExecuteTask() override { + caching_file_handle.ReadAndRecord(context, buffer, nr_bytes, location); + } + +private: + CachingFileHandle &caching_file_handle; + QueryContext context; + data_ptr_t buffer; + idx_t nr_bytes; + idx_t location; +}; + } // namespace //===----------------------------------------------------------------------===// @@ -261,8 +285,8 @@ void CachingFileHandle::ReconcileCacheAfterRead(CachedFile &cached_file) { CachingFileHandle::CachingFileHandle(QueryContext context, CachingFileSystem &caching_file_system_p, const OpenFileInfo &path_p, FileOpenFlags flags_p, optional_ptr opener_p) - : context(context), caching_file_system(caching_file_system_p), - external_file_cache(caching_file_system.external_file_cache), path(path_p), flags(std::move(flags_p)), + : context(context), file_system(caching_file_system_p.file_system), db(caching_file_system_p.db), + external_file_cache(caching_file_system_p.external_file_cache), path(path_p), flags(std::move(flags_p)), opener(opener_p), validate(ExternalFileCacheUtil::GetCacheValidationMode(path_p, context.GetClientContext(), caching_file_system_p.db)), cached_file(nullptr), position(0) { @@ -300,9 +324,9 @@ shared_ptr CachingFileHandle::GetFileHandle() { // Request parallel access on the underlying filesystem handle to avoid races in implementations that // require explicit opt-in for concurrent pread-style access (e.g., HTTPFS). auto internal_flags = flags | FileFlags::FILE_FLAGS_PARALLEL_ACCESS; - file_handle = caching_file_system.file_system.OpenFile(path, internal_flags, opener); + file_handle = file_system.OpenFile(path, internal_flags, opener); // Snapshot the metadata with a single Stats call, avoiding repeated metadata lookups (e.g., fstat) - validation_info = GetValidationInfo(caching_file_system.file_system, *file_handle); + validation_info = GetValidationInfo(file_system, *file_handle); { annotated_lock_guard meta_guard(cached_file->meta_lock); @@ -340,7 +364,7 @@ FileBufferHandleGroup CachingFileHandle::Read(const idx_t nr_bytes, const idx_t if (!external_file_cache.IsEnabled() || !external_file_cache.ShouldCacheFile(path.path) || !CanUseCache()) { auto buf = AllocateUncachedReadBuffer(external_file_cache.GetBufferManager(), nr_bytes); - ReadAndRecord(context, buf.GetDataMutable(), nr_bytes, location); + ReadUncached(buf.GetDataMutable(), nr_bytes, location); vector mem_handles; mem_handles.push_back({std::move(buf), 0, nr_bytes}); return FileBufferHandleGroup(std::move(mem_handles)); @@ -355,7 +379,7 @@ FileBufferHandleGroup CachingFileHandle::Read(const idx_t nr_bytes, const idx_t const idx_t min_block_size = MinValue(external_file_cache.GetCacheMinBlockSize(current_cached_file->path), max_block_size); auto request_sizing = flags.GetRequestSizing(); - switch (Settings::Get(caching_file_system.db)) { + switch (Settings::Get(db)) { case ExternalFileCacheRequestSizing::GRID: request_sizing = RequestSizing::BY_CACHE; break; @@ -385,7 +409,7 @@ FileBufferHandleGroup CachingFileHandle::Read(const idx_t nr_bytes, const idx_t // Schedule block fetch tasks for all blocks. vector pins(num_blocks); - auto &scheduler = TaskScheduler::GetScheduler(caching_file_system.db); + auto &scheduler = TaskScheduler::GetScheduler(db); TaskExecutor executor(scheduler, TaskSchedulerType::ASYNC); for (idx_t idx = 0; idx < num_blocks; idx++) { @@ -422,6 +446,25 @@ FileBufferHandleGroup CachingFileHandle::Read(const idx_t nr_bytes, const idx_t return FileBufferHandleGroup(std::move(mem_handles)); } +void CachingFileHandle::ReadUncached(data_ptr_t buffer, idx_t nr_bytes, idx_t location) { + const idx_t max_block_size = external_file_cache.GetCacheMaxBlockSize(path.path); + auto &scheduler = TaskScheduler::GetScheduler(db); + // idle worker threads also run async tasks, so the pieces are only sure to run serially without any other thread + const bool single_threaded = scheduler.NumberOfThreads() <= 1 && scheduler.NumberOfAsyncThreads() == 0; + if (nr_bytes <= max_block_size || !external_file_cache.ShouldCacheFile(path.path) || !CanSeek() || + single_threaded) { + ReadAndRecord(context, buffer, nr_bytes, location); + return; + } + // split like cached reads, so the requests are the same whether or not the cache is enabled + TaskExecutor executor(scheduler, TaskSchedulerType::ASYNC); + for (idx_t offset = 0; offset < nr_bytes; offset += max_block_size) { + executor.ScheduleTask(make_uniq(*this, executor, context, buffer + offset, + MinValue(max_block_size, nr_bytes - offset), location + offset)); + } + executor.WorkOnTasks(); +} + FileBufferHandleGroup CachingFileHandle::Read(idx_t &nr_bytes) { // If we can't seek, we can't use the cache for these calls, // because we won't be able to seek over any parts we skipped by reading from the cache diff --git a/src/duckdb/src/storage/external_file_cache/external_file_cache.cpp b/src/duckdb/src/storage/external_file_cache/external_file_cache.cpp index d5d7048bc..01fa9d64e 100644 --- a/src/duckdb/src/storage/external_file_cache/external_file_cache.cpp +++ b/src/duckdb/src/storage/external_file_cache/external_file_cache.cpp @@ -14,6 +14,33 @@ namespace duckdb { +namespace { + +bool IsDroppedBlock(CacheBlock &block) { + const annotated_lock_guard block_guard(block.mtx); + if (block.state != CacheBlockState::LOADED || !block.block_handle) { + return false; + } + auto &memory = block.block_handle->GetMemory(); + return memory.IsUnloaded() && !memory.MustWriteToTemporaryFile(); +} + +bool IsLoadedBlock(CacheBlock &block) { + const annotated_lock_guard block_guard(block.mtx); + return block.state == CacheBlockState::LOADED; +} + +idx_t GapBlockCount(idx_t nr_bytes, idx_t max_block_size) { + return (nr_bytes + max_block_size - 1) / max_block_size; +} + +//! Whether the last modified timestamp is usable as a cache validator +bool HasUsableLastModified(timestamp_t last_modified) { + return last_modified.IsFinite() && last_modified != timestamp_t(0); +} + +} // namespace + bool CacheValidationInfo::IsCacheReuseProhibited() const { return cache_valid_until && *cache_valid_until == timestamp_t::ninfinity(); } @@ -90,24 +117,6 @@ bool ExternalFileCache::ShouldCacheFile(const string &path) const { return Settings::Get(db); } -static bool IsDroppedBlock(CacheBlock &block) { - const annotated_lock_guard block_guard(block.mtx); - if (block.state != CacheBlockState::LOADED || !block.block_handle) { - return false; - } - auto &memory = block.block_handle->GetMemory(); - return memory.IsUnloaded() && !memory.MustWriteToTemporaryFile(); -} - -static bool IsLoadedBlock(CacheBlock &block) { - const annotated_lock_guard block_guard(block.mtx); - return block.state == CacheBlockState::LOADED; -} - -static idx_t GapBlockCount(idx_t nr_bytes, idx_t max_block_size) { - return (nr_bytes + max_block_size - 1) / max_block_size; -} - vector> ExternalFileCache::AcquireBlocks(CachedFile &cached_file, idx_t location, idx_t nr_bytes, idx_t max_block_size) { D_ASSERT(nr_bytes > 0); @@ -131,7 +140,7 @@ vector> ExternalFileCache::AcquireBlocks(CachedFile &cach idx_t pos = location; while (pos < end) { if (it != blocks.end() && it->first <= pos) { - if (!IsDroppedBlock(*it->second)) { + if (!IsDroppedBlock(*it->second) || it->second.use_count() > 1) { result.push_back(it->second); pos = it->first + it->second->size; ++it; @@ -184,11 +193,6 @@ ExternalFileCache::CachedFile::CachedFile(string path_p, idx_t generation_p) : path(std::move(path_p)), generation(generation_p) { } -//! Whether the last modified timestamp is usable as a cache validator -static bool HasUsableLastModified(timestamp_t last_modified) { - return last_modified.IsFinite() && last_modified != timestamp_t(0); -} - bool ExternalFileCache::IsValid(bool validate, const string &cached_version_tag, timestamp_t cached_last_modified, const string ¤t_version_tag, timestamp_t current_last_modified) { if (!validate) { diff --git a/src/duckdb/src/storage/serialization/serialize_nodes.cpp b/src/duckdb/src/storage/serialization/serialize_nodes.cpp index 07759010f..6af9fc271 100644 --- a/src/duckdb/src/storage/serialization/serialize_nodes.cpp +++ b/src/duckdb/src/storage/serialization/serialize_nodes.cpp @@ -466,6 +466,7 @@ void MultiFileOptions::Serialize(Serializer &serializer) const { serializer.WritePropertyWithDefault>(110, "schema", schema); serializer.WritePropertyWithDefault(111, "file_row_number", file_row_number); serializer.WriteProperty(112, "column_statistics", column_statistics); + serializer.WritePropertyWithDefault>(113, "bound_schema", bound_schema); } MultiFileOptions MultiFileOptions::Deserialize(Deserializer &deserializer) { @@ -483,6 +484,7 @@ MultiFileOptions MultiFileOptions::Deserialize(Deserializer &deserializer) { deserializer.ReadPropertyWithDefault>(110, "schema", result.schema); deserializer.ReadPropertyWithDefault(111, "file_row_number", result.file_row_number); deserializer.ReadProperty(112, "column_statistics", result.column_statistics); + deserializer.ReadPropertyWithDefault>(113, "bound_schema", result.bound_schema); return result; } diff --git a/src/duckdb/src/storage/serialization/serialize_parsed_expression.cpp b/src/duckdb/src/storage/serialization/serialize_parsed_expression.cpp index 33611533b..6612e100d 100644 --- a/src/duckdb/src/storage/serialization/serialize_parsed_expression.cpp +++ b/src/duckdb/src/storage/serialization/serialize_parsed_expression.cpp @@ -92,6 +92,7 @@ unique_ptr ParsedExpression::Deserialize(Deserializer &deseria result->alias = std::move(alias); result->query_location = query_location; result->query_location.length = query_location_length; + result->VerifyDeserialized(); return result; } diff --git a/src/duckdb/src/storage/serialization/serialize_query_node.cpp b/src/duckdb/src/storage/serialization/serialize_query_node.cpp index 31be0071c..865f0c2d5 100644 --- a/src/duckdb/src/storage/serialization/serialize_query_node.cpp +++ b/src/duckdb/src/storage/serialization/serialize_query_node.cpp @@ -66,6 +66,7 @@ unique_ptr QueryNode::Deserialize(Deserializer &deserializer) { if (type == QueryNodeType::CTE_NODE) { result = std::move(result->Cast().child); } + QueryNode::VerifyDeserialized(result); return result; } @@ -222,6 +223,13 @@ unique_ptr SelectNode::Deserialize(Deserializer &deserializer) { if (!result->from_table) { result->from_table = make_uniq(); } + for (auto &grouping_set : result->groups.grouping_sets) { + for (auto &group_index : grouping_set) { + if (!group_index.IsValid() || group_index.GetIndex() >= result->groups.group_expressions.size()) { + throw SerializationException("Grouping set refers to a group that does not exist"); + } + } + } return std::move(result); } diff --git a/src/duckdb/src/storage/serialization/serialize_tableref.cpp b/src/duckdb/src/storage/serialization/serialize_tableref.cpp index be0bbc9fd..2a87cfe42 100644 --- a/src/duckdb/src/storage/serialization/serialize_tableref.cpp +++ b/src/duckdb/src/storage/serialization/serialize_tableref.cpp @@ -62,6 +62,7 @@ unique_ptr TableRef::Deserialize(Deserializer &deserializer) { result->sample = std::move(sample); result->query_location = query_location; result->query_location.length = query_location_length; + result->VerifyDeserialized(); return result; } diff --git a/src/duckdb/src/storage/standard_buffer_manager.cpp b/src/duckdb/src/storage/standard_buffer_manager.cpp index c7bef30fa..8ff71a261 100644 --- a/src/duckdb/src/storage/standard_buffer_manager.cpp +++ b/src/duckdb/src/storage/standard_buffer_manager.cpp @@ -375,7 +375,7 @@ BufferHandle StandardBufferManager::Pin(const QueryContext &context, shared_ptr< // check if the block is already loaded if (block_memory.GetState() == BlockState::BLOCK_LOADED) { // the block is loaded, increment the reader count and set the BufferHandle - buf = handle->Load(context); + buf = handle->Load(lock, context); } required_memory = block_memory.GetMemoryUsage(); } @@ -396,11 +396,11 @@ BufferHandle StandardBufferManager::Pin(const QueryContext &context, shared_ptr< if (block_memory.GetState() == BlockState::BLOCK_LOADED) { // the block is loaded, increment the reader count and return a pointer to the handle reservation.Resize(0); - buf = handle->Load(context); + buf = handle->Load(lock, context); } else { // now we can actually load the current block D_ASSERT(block_memory.GetReaders() == 0); - buf = handle->Load(context, std::move(reusable_buffer)); + buf = handle->Load(lock, context, std::move(reusable_buffer)); if (!buf.IsValid()) { reservation.Resize(0); return buf; // Buffer was destroyed (e.g., due to DestroyBufferUpon::Eviction) @@ -460,7 +460,7 @@ void StandardBufferManager::Unpin(shared_ptr &handle) { return; } D_ASSERT(block_memory.GetReaders() > 0); - auto new_readers = block_memory.DecrementReaders(); + auto new_readers = block_memory.DecrementReaders(lock); if (new_readers == 0) { VerifyZeroReaders(lock, handle); if (block_memory.MustAddToEvictionQueue()) { @@ -509,17 +509,21 @@ string StandardBufferManager::GetTemporaryPath(block_id_t id) { } void StandardBufferManager::RequireTemporaryDirectory() { + if (temporary_directory.initialized.load(std::memory_order_acquire)) { + return; + } + lock_guard guard(temporary_directory.lock); + if (temporary_directory.handle) { + return; + } if (temporary_directory.path.empty()) { throw InvalidInputException( "Out-of-memory: cannot write buffer because no temporary directory is specified!\nTo enable " "temporary buffer eviction set a temporary directory using PRAGMA temp_directory='/path/to/tmp.tmp'"); } - lock_guard guard(temporary_directory.lock); - if (!temporary_directory.handle) { - // temp directory has not been created yet: initialize it - temporary_directory.handle = make_uniq( - db, temporary_directory.path, temporary_directory.size_on_disk, temporary_directory.maximum_swap_space); - } + temporary_directory.handle = make_uniq( + db, temporary_directory.path, temporary_directory.size_on_disk, temporary_directory.maximum_swap_space); + temporary_directory.initialized.store(true, std::memory_order_release); } bool StandardBufferManager::EncryptTemporaryFiles() { @@ -662,7 +666,8 @@ unique_ptr StandardBufferManager::ReadTemporaryBuffer(QueryContext c } void StandardBufferManager::DeleteTemporaryFile(BlockMemory &memory) { - if (!memory.IsUnloaded()) { + // Without spill storage, there are no temporary buffers to delete. + if (!memory.IsUnloaded() || GetUsedSwap() == 0) { return; } auto id = memory.BlockId(); diff --git a/src/duckdb/src/storage/statistics/distinct_statistics.cpp b/src/duckdb/src/storage/statistics/distinct_statistics.cpp index d1ed53b81..da67816f0 100644 --- a/src/duckdb/src/storage/statistics/distinct_statistics.cpp +++ b/src/duckdb/src/storage/statistics/distinct_statistics.cpp @@ -1,6 +1,8 @@ #include "duckdb/storage/statistics/distinct_statistics.hpp" +#include "duckdb/common/random_engine.hpp" #include "duckdb/common/string_util.hpp" +#include "duckdb/common/types/hash.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" namespace duckdb { @@ -16,6 +18,88 @@ unique_ptr DistinctStatistics::Copy() const { return make_uniq(log->Copy(), sample_count, total_count); } +static SelectionVector CreateSampleSelection(idx_t sample_count) { + SelectionVector result(sample_count); + RandomEngine random(0); + for (idx_t i = 0; i < sample_count; i++) { + const auto begin = i * STANDARD_VECTOR_SIZE / sample_count; + const auto end = (i + 1) * STANDARD_VECTOR_SIZE / sample_count; + result.set_index(i, begin + random.NextRandomInteger64() % (end - begin)); + } + return result; +} + +template +static void UpdateSampleValues(HyperLogLog &log, const Vector &input, const SelectionVector &selection, idx_t count) { + auto values = input.Values(); + if (values.CanHaveNull()) { + for (idx_t i = 0; i < count; i++) { + auto entry = values[selection.get_index(i)]; + if (entry.IsValid()) { + log.InsertElement(duckdb::Hash(entry.GetValue())); + } + } + } else { + for (idx_t i = 0; i < count; i++) { + log.InsertElement(duckdb::Hash(values[selection.get_index(i)].GetValue())); + } + } +} + +static bool TryUpdateSampleValues(HyperLogLog &log, const Vector &input, const SelectionVector &selection, + idx_t count) { + if (input.GetVectorType() != VectorType::FLAT_VECTOR) { + return false; + } + switch (input.GetType().InternalType()) { + case PhysicalType::INT8: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::INT16: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::INT32: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::INT64: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::UINT8: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::UINT16: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::UINT32: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::UINT64: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::INT128: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::UINT128: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::FLOAT: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::DOUBLE: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::INTERVAL: + UpdateSampleValues(log, input, selection, count); + break; + case PhysicalType::VARCHAR: + UpdateSampleValues(log, input, selection, count); + break; + default: + return false; + } + return true; +} + void DistinctStatistics::Merge(const DistinctStatistics &other) { log->Merge(*other.log); sample_count += other.sample_count; @@ -31,7 +115,24 @@ void DistinctStatistics::UpdateSample(const Vector &new_data, idx_t count, Vecto // But never more than the original count count = MinValue(count, original_count); - UpdateInternal(new_data, count, hashes); + if (original_count != STANDARD_VECTOR_SIZE || count == original_count) { + UpdateInternal(new_data, count, hashes); + return; + } + + static auto integral_sample = + CreateSampleSelection(MaxValue(LossyNumericCast(INTEGRAL_SAMPLE_RATE * STANDARD_VECTOR_SIZE), 1)); + static auto other_sample = + CreateSampleSelection(MaxValue(LossyNumericCast(BASE_SAMPLE_RATE * STANDARD_VECTOR_SIZE), 1)); + auto &sample_selection = new_data.GetType().IsIntegral() ? integral_sample : other_sample; + if (TryUpdateSampleValues(*log, new_data, sample_selection, count)) { + sample_count += count; + return; + } + // Borrow the static indices to avoid reference-count contention between insertion threads. + SelectionVector selection(sample_selection.data(), count); + Vector sample(new_data, selection, count); + UpdateInternal(sample, count, hashes); } void DistinctStatistics::Update(const Vector &new_data, idx_t count, Vector &hashes) { diff --git a/src/duckdb/src/storage/storage_manager.cpp b/src/duckdb/src/storage/storage_manager.cpp index 8cd7ea6f6..4f311510f 100644 --- a/src/duckdb/src/storage/storage_manager.cpp +++ b/src/duckdb/src/storage/storage_manager.cpp @@ -142,7 +142,7 @@ void StorageOptions::Initialize(unordered_map &options) { entry.second.ToString()); } } else if (entry.first == "mmap_reserve_size") { - mmap_reserve_size = DBConfig::ParseMemoryLimit(entry.second.ToString()); + mmap_reserve_size = DBConfig::ParseMemoryLimitOrMaximum(entry.second.ToString()); } else { throw BinderException("Unrecognized option for attach \"%s\"", entry.first); } diff --git a/src/duckdb/src/storage/table/column_data.cpp b/src/duckdb/src/storage/table/column_data.cpp index 07c6a78d5..66f3d82e0 100644 --- a/src/duckdb/src/storage/table/column_data.cpp +++ b/src/duckdb/src/storage/table/column_data.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/exception/transaction_exception.hpp" #include "duckdb/common/types/validity_mask.hpp" +#include "duckdb/common/operator/add.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/common/serializer/binary_deserializer.hpp" #include "duckdb/common/vector/flat_vector.hpp" @@ -918,13 +919,20 @@ void ColumnData::InitializeColumn(PersistentColumnData &column_data) { } void ColumnData::InitializeColumn(PersistentColumnData &column_data, BaseStatistics &target_stats) { - D_ASSERT(type.InternalType() == column_data.logical_type.InternalType()); + if (type.InternalType() != column_data.logical_type.InternalType()) { + throw DataCorruptionException("Column data of type %s found for a column of type %s", column_data.logical_type, + type); + } // construct the segments based on the data pointers this->count = 0; for (auto &data_pointer : column_data.pointers) { // Update the count and statistics data_pointer.row_start = count; - this->count += data_pointer.tuple_count; + idx_t new_count; + if (!TryAddOperator::Operation(count.load(), data_pointer.tuple_count, new_count)) { + throw DataCorruptionException("Column data has more rows than can be represented"); + } + this->count = new_count; // Merge the statistics. If this is a child column, the target_stats reference will point into the parents stats // otherwise if this is a top level column, `stats->statistics` == `target_stats` @@ -1030,14 +1038,21 @@ static PersistentColumnData GetPersistentColumnDataType(Deserializer &deserializ } // Otherwise, the type of this segment may depend on extra data + auto &column_type = deserializer.Get(); switch (extra_data->GetType()) { case ExtraPersistentColumnDataType::VARIANT: { + if (column_type.id() != LogicalTypeId::VARIANT) { + throw DataCorruptionException("VARIANT column data found for a column of type %s", column_type); + } auto unshredded_type = VariantShredding::GetUnshreddedType(); PersistentColumnData result(LogicalType::VARIANT()); result.extra_data = std::move(extra_data); return result; } case ExtraPersistentColumnDataType::GEOMETRY: { + if (column_type.id() != LogicalTypeId::GEOMETRY) { + throw DataCorruptionException("GEOMETRY column data found for a column of type %s", column_type); + } const auto &geometry_data = extra_data->Cast(); // WKB is stored as the column's own type, which may carry type parameters (e.g., a CRS) PersistentColumnData result(geometry_data.storage_type == GeometryStorageType::WKB diff --git a/src/duckdb/src/storage/table/column_segment.cpp b/src/duckdb/src/storage/table/column_segment.cpp index f164f4cd3..36f1f2f1b 100644 --- a/src/duckdb/src/storage/table/column_segment.cpp +++ b/src/duckdb/src/storage/table/column_segment.cpp @@ -490,8 +490,8 @@ static idx_t ExecuteExpressionFilterSelection(SelectionVector &sel, Vector &vect chunk.data.emplace_back(Vector::Ref(vector)); chunk.SetChildCardinality(scan_count); SelectionVector identity_sel; - optional_ptr current_sel = &sel; - if (!sel.IsSet()) { + optional_ptr current_sel = sel.IsSet() ? &sel : nullptr; + if (!sel.IsSet() && (vector.GetType().IsNested() || approved_tuple_count != scan_count)) { identity_sel = SelectionVector::Incremental(approved_tuple_count); current_sel = &identity_sel; } diff --git a/src/duckdb/src/storage/table/list_column_data.cpp b/src/duckdb/src/storage/table/list_column_data.cpp index 9704b2930..f13bfa668 100644 --- a/src/duckdb/src/storage/table/list_column_data.cpp +++ b/src/duckdb/src/storage/table/list_column_data.cpp @@ -176,7 +176,11 @@ void ListColumnData::Skip(ColumnScanState &state, idx_t count) { // however, let's just read all "count" entries for now Vector offset_vector(LogicalType::UBIGINT, count); idx_t scan_count = ScanVector(state, offset_vector, count, ScanVectorType::SCAN_FLAT_VECTOR); - D_ASSERT(scan_count > 0); + if (scan_count == 0) { + // no offsets were scanned - there is nothing to skip in the child, and reading offset[-1] below would be + // an out-of-bounds read + return; + } UnifiedVectorFormat offsets; offset_vector.ToUnifiedFormat(offsets); diff --git a/src/duckdb/src/storage/table/row_group.cpp b/src/duckdb/src/storage/table/row_group.cpp index 0b3d88839..c645fe3a4 100644 --- a/src/duckdb/src/storage/table/row_group.cpp +++ b/src/duckdb/src/storage/table/row_group.cpp @@ -1266,6 +1266,9 @@ void RowGroup::RevertAppend(idx_t new_count) { for (auto &column : GetColumns()) { column->RevertAppend(UnsafeNumericCast(new_count)); } + if (transient) { + *transient = SuballocationBlock(); + } SetCount(new_count); Verify(); } @@ -1281,17 +1284,32 @@ void RowGroup::InitializeAppend(SegmentNode &row_group, RowGroupAppend append_state.row_group = row_group; row_group.GetNode().InitializeAppendInternal(append_state); } + +void RowGroup::InitializeNextAppend(SegmentNode &row_group, RowGroupAppendState &append_state) { + D_ASSERT(append_state.row_group); + auto &previous = append_state.row_group->GetNode(); + auto &next = row_group.GetNode(); + D_ASSERT(!RefersToSameObject(previous, next)); + D_ASSERT(RefersToSameObject(previous.GetCollection(), next.GetCollection())); + D_ASSERT(!next.transient); + next.transient = std::move(previous.transient); + InitializeAppend(row_group, append_state); +} + void RowGroup::InitializeAppendInternal(RowGroupAppendState &append_state) { if (!RefersToSameObject(append_state.row_group->GetNode(), *this)) { throw InternalException("RowGroup::InitializeAppend mismatch - call RowGroupAppendState::InitializeAppend"); } append_state.offset_in_row_group = this->count; + if (!transient) { + transient = make_uniq(); + } // for each column, initialize the append state append_state.states = make_unsafe_uniq_array(GetColumnCount()); for (idx_t i = 0; i < GetColumnCount(); i++) { auto &col_data = GetColumn(i); auto &state = append_state.states[i]; - state.transient = &append_state.transient; + state.transient = transient.get(); col_data.InitializeAppend(state); } } diff --git a/src/duckdb/src/storage/table/row_group_collection.cpp b/src/duckdb/src/storage/table/row_group_collection.cpp index c6fdd38b7..8d3d37f57 100644 --- a/src/duckdb/src/storage/table/row_group_collection.cpp +++ b/src/duckdb/src/storage/table/row_group_collection.cpp @@ -341,7 +341,6 @@ bool RowGroupCollection::InitializeScanInRowGroup(ClientContext &context, Collec RowGroupCollection &collection, SegmentNode &row_group, idx_t vector_index, idx_t max_row, bool initialize_columns) { state.max_row = max_row; - state.row_groups = collection.GetRowGroups(); if (state.column_scans.empty()) { // initialize the scan state state.Initialize(context, collection.GetTypes()); @@ -356,7 +355,6 @@ void RowGroupCollection::InitializeParallelScan(ParallelCollectionScanState &sta state.vector_index = 0; state.max_row = state.row_groups->GetBaseRowId() + next_row_id.load(); state.batch_index = 0; - state.processed_rows = 0; state.skipped_rows = 0; if (state.reorderer) { // row groups pruned by the reorderer are never scanned @@ -410,7 +408,6 @@ optional_idx RowGroupCollection::NextParallelScan(ClientContext &context, Parall max_row = MinValue(max_row, state.max_row); const idx_t assignment_start = row_start + vector_index * STANDARD_VECTOR_SIZE; assignment_rows = max_row > assignment_start ? max_row - assignment_start : 0; - state.processed_rows += assignment_rows; scan_state.batch_index = ++state.batch_index; if (!state.row_number_base.IsValid() && scan_state.row_number_base.IsValid()) { state.row_number_base = scan_state.row_number_base.GetIndex(); @@ -724,7 +721,7 @@ optional_idx RowGroupCollection::Append(DataChunk &chunk, TableAppendState &stat AppendRowGroup(l, next_start); // set up the append state for this row_group auto last_row_group = state.row_groups->GetLastSegment(l); - RowGroup::InitializeAppend(*last_row_group, state.row_group_append_state); + RowGroup::InitializeNextAppend(*last_row_group, state.row_group_append_state); state.row_group_start = next_start; } state.current_row += row_t(total_append_count); @@ -1423,8 +1420,8 @@ class VacuumTask : public BaseCheckpointTask { // move to the next row group current_append_idx++; - RowGroup::InitializeAppend(*new_row_groups[current_append_idx], - append_state.row_group_append_state); + RowGroup::InitializeNextAppend(*new_row_groups[current_append_idx], + append_state.row_group_append_state); // slice chunk for the next append append_chunk.Slice(append_count, remaining); } @@ -1728,7 +1725,7 @@ unique_ptr RowGroupCollection::GetCheckpointTask(CollectionCheck return make_uniq(checkpoint_state, segment_idx); } -void RowGroupCollection::Checkpoint(TableDataWriter &writer, TableStatistics &global_stats) { +bool RowGroupCollection::Checkpoint(TableDataWriter &writer, TableStatistics &global_stats) { auto row_groups = GetRowGroups(); CollectionCheckpointState checkpoint_state(*this, writer, global_stats, *row_groups); @@ -1813,19 +1810,17 @@ void RowGroupCollection::Checkpoint(TableDataWriter &writer, TableStatistics &gl row_group.CheckpointDeletes(*row_group_writer); } writer.WriteUnchangedTable(metadata_pointer, metadata_pointers, total_rows.load(), next_row_id.load()); - // copy over existing stats into the global stats - CopyStats(global_stats); - return; + return false; } } // not all segments have stayed the same - we need to make a new segment tree with the new set of segments + // initialize new empty stats - before locking the new segment tree, as this takes the stats lock + global_stats.InitializeEmpty(stats); + auto new_row_groups = make_shared_ptr(*this, row_groups->GetBaseRowId()); auto l = new_row_groups->Lock(); - // initialize new empty stats - global_stats.InitializeEmpty(stats); - idx_t new_total_rows = 0; idx_t new_next_row_id = 0; auto base_row_id = row_groups->GetBaseRowId(); @@ -2084,6 +2079,7 @@ void RowGroupCollection::Checkpoint(TableDataWriter &writer, TableStatistics &gl if (vacuum_state.index_strategy == VacuumIndexStrategy::REBUILD && writer.RowIdsChanged()) { writer.SetRebuildIndexes(); } + return true; } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/table/scan_state.cpp b/src/duckdb/src/storage/table/scan_state.cpp index 1edf103b8..d7083ddbb 100644 --- a/src/duckdb/src/storage/table/scan_state.cpp +++ b/src/duckdb/src/storage/table/scan_state.cpp @@ -219,7 +219,7 @@ TableScanOptions &CollectionScanState::GetOptions() { } ParallelCollectionScanState::ParallelCollectionScanState() - : collection(nullptr), current_row_group(nullptr), processed_rows(0), skipped_rows(0) { + : collection(nullptr), current_row_group(nullptr), skipped_rows(0) { } void ParallelCollectionScanState::AssignRowGroup(optional_ptr> row_group) { diff --git a/src/duckdb/src/storage/table/struct_column_data.cpp b/src/duckdb/src/storage/table/struct_column_data.cpp index 3b2240430..bae1392a4 100644 --- a/src/duckdb/src/storage/table/struct_column_data.cpp +++ b/src/duckdb/src/storage/table/struct_column_data.cpp @@ -43,7 +43,8 @@ void StructColumnData::SetDataType(ColumnDataType data_type) { } idx_t StructColumnData::GetMaxEntry() { - return sub_columns[0]->GetMaxEntry(); + // every struct has a validity column, while an empty TUPLE has no child columns + return validity->GetMaxEntry(); } FilterPropagateResult StructColumnData::CheckZonemap(ColumnScanState &state, TableFilter &filter, diff --git a/src/duckdb/src/storage/table/table_statistics.cpp b/src/duckdb/src/storage/table/table_statistics.cpp index 38ecd9a62..48d52364c 100644 --- a/src/duckdb/src/storage/table/table_statistics.cpp +++ b/src/duckdb/src/storage/table/table_statistics.cpp @@ -29,6 +29,8 @@ void TableStatistics::InitializeEmpty(const TableStatistics &other) { D_ASSERT(Empty()); D_ASSERT(!table_sample); + // hold other's lock while reading its stats - a concurrent ANALYZE/VACUUM can replace them (e.g. SetDistinct) + lock_guard l(*other.stats_lock); stats_lock = make_shared_ptr(); if (other.table_sample) { D_ASSERT(other.table_sample->type == SampleType::RESERVOIR_SAMPLE); diff --git a/src/duckdb/src/storage/wal_replay.cpp b/src/duckdb/src/storage/wal_replay.cpp index 99ecfd490..54611faa2 100644 --- a/src/duckdb/src/storage/wal_replay.cpp +++ b/src/duckdb/src/storage/wal_replay.cpp @@ -88,17 +88,24 @@ class ReplayState { class WriteAheadLogDeserializer { public: WriteAheadLogDeserializer(ReplayState &state_p, BufferedFileReader &stream_p, bool deserialize_only = false) - : state(state_p), db(state.db), context(state.context), catalog(state.catalog), data(nullptr), - stream(nullptr, 0), deserializer(stream_p), deserialize_only(deserialize_only) { + : state(state_p), db(state.db), context(state.context), catalog(state.catalog), stream(nullptr, 0), + deserializer(stream_p), deserialize_only(deserialize_only) { deserializer.Set(catalog); } - WriteAheadLogDeserializer(ReplayState &state_p, unique_ptr data_p, idx_t size, - bool deserialize_only = false) + WriteAheadLogDeserializer(ReplayState &state_p, AllocatedData data_p, idx_t size, bool deserialize_only = false) : state(state_p), db(state.db), context(state.context), catalog(state.catalog), data(std::move(data_p)), stream(data.get(), size), deserializer(stream), deserialize_only(deserialize_only) { deserializer.Set(catalog); } + //! WAL entries are read into memory in full, so the buffer is tracked by the buffer manager + static AllocatedData AllocateEntryBuffer(ReplayState &state_p, idx_t size) { + if (size == 0) { + throw SerializationException("Corrupt WAL file: found an empty entry"); + } + return BufferAllocator::Get(state_p.db).Allocate(size); + } + static WriteAheadLogDeserializer GetEntryDeserializer(ReplayState &state_p, BufferedFileReader &stream, bool deserialize_only = false) { if (state_p.wal_version == 1) { @@ -121,7 +128,7 @@ class WriteAheadLogDeserializer { } // allocate a buffer and read data into the buffer - auto buffer = unique_ptr(new data_t[size]); + auto buffer = AllocateEntryBuffer(state_p, size); stream.ReadData(buffer.get(), size); // compute and verify the checksum @@ -181,8 +188,8 @@ class WriteAheadLogDeserializer { } //! Allocate a decryption buffer - auto buffer = unique_ptr(new data_t[ciphertext_size]); - auto out_buffer = unique_ptr(new data_t[size]); + auto buffer = AllocateEntryBuffer(state_p, ciphertext_size); + auto out_buffer = AllocateEntryBuffer(state_p, size); stream.ReadData(buffer.get(), ciphertext_size); encryption_state->Process(buffer.get(), ciphertext_size, buffer.get(), ciphertext_size); @@ -246,7 +253,7 @@ class WriteAheadLogDeserializer { void ReplayEntry(WALType wal_type); bool CanSkipPayload(WALType wal_type) const { // Unframed WAL v1 entries must still be deserialized; Replay* guards suppress their side effects. - if (!deserialize_only || !data) { + if (!deserialize_only || !data.get()) { return false; } D_ASSERT(state.wal_version == 2 || state.wal_version == 3); @@ -327,7 +334,7 @@ class WriteAheadLogDeserializer { AttachedDatabase &db; ClientContext &context; Catalog &catalog; - unique_ptr data; + AllocatedData data; MemoryStream stream; BinaryDeserializer deserializer; bool deserialize_only; @@ -629,7 +636,8 @@ unique_ptr WriteAheadLogReplayer::ReplayLog(unique_ptr(storage_manager, wal_path, 0, WALInitState::UNINITIALIZED_REQUIRES_TRUNCATE); + return make_uniq(storage_manager, wal_path, idx_t(0), + WALInitState::UNINITIALIZED_REQUIRES_TRUNCATE); } // we need to recover from the WAL: actually set up the replay state @@ -1390,9 +1398,16 @@ void WriteAheadLogDeserializer::ReplayUpdate() { throw DataCorruptionException("Corrupt WAL: update without table"); } - if (column_path[0] >= state.current_table->GetColumns().PhysicalColumnCount()) { + if (column_path.empty() || column_path[0] >= state.current_table->GetColumns().PhysicalColumnCount()) { throw DataCorruptionException("Corrupt WAL: column index for update out of bounds"); } + if (chunk.ColumnCount() != 2 || chunk.data[1].GetType() != LogicalType::ROW_TYPE) { + throw DataCorruptionException("Corrupt WAL: update must contain the updated values and the row ids"); + } + auto &column_type = state.current_table->GetColumns().GetColumn(PhysicalIndex(column_path[0])).Type(); + if (column_path.size() == 1 && chunk.data[0].GetType() != column_type) { + throw DataCorruptionException("Corrupt WAL: update type does not match the column type"); + } // remove the row id vector from the chunk auto row_ids = std::move(chunk.data.back()); diff --git a/src/duckdb/src/storage/write_ahead_log.cpp b/src/duckdb/src/storage/write_ahead_log.cpp index 32ec95c0c..580a80d5c 100644 --- a/src/duckdb/src/storage/write_ahead_log.cpp +++ b/src/duckdb/src/storage/write_ahead_log.cpp @@ -97,7 +97,7 @@ bool WriteAheadLog::Initialized() const { //===--------------------------------------------------------------------===// class ChecksumWriter : public WriteStream { public: - explicit ChecksumWriter(WriteAheadLog &wal) : wal(wal), memory_stream(Allocator::Get(wal.GetDatabase())) { + explicit ChecksumWriter(WriteAheadLog &wal) : wal(wal), memory_stream(BufferAllocator::Get(wal.GetDatabase())) { } void WriteData(const_data_ptr_t buffer, idx_t write_size) override { @@ -152,7 +152,7 @@ class ChecksumWriter : public WriteStream { // temp buffer const idx_t ciphertext_size = size + sizeof(uint64_t); - std::unique_ptr temp_buf(new uint8_t[ciphertext_size]); + auto temp_buf = BufferAllocator::Get(db).Allocate(ciphertext_size); EncryptionNonce nonce(db.GetStorageManager().GetCipher(), db.GetStorageManager().GetEncryptionVersion()); EncryptionTag tag; diff --git a/src/duckdb/src/transaction/transaction_context.cpp b/src/duckdb/src/transaction/transaction_context.cpp index 48a7300d7..dcace6909 100644 --- a/src/duckdb/src/transaction/transaction_context.cpp +++ b/src/duckdb/src/transaction/transaction_context.cpp @@ -114,9 +114,15 @@ void TransactionContext::Rollback(optional_ptr error) { } catch (std::exception &ex) { rollback_error = ErrorData(ex); } - // Notify any registered state of transaction rollback + // Notify any registered state of transaction rollback - all of them, also when one of them fails for (auto const &s : context.registered_state->States()) { - s->TransactionRollback(*transaction, context, error); + try { + s->TransactionRollback(*transaction, context, error); + } catch (std::exception &ex) { + if (!rollback_error.HasError()) { + rollback_error = ErrorData(ex); + } + } } if (rollback_error.HasError()) { rollback_error.Throw(); diff --git a/src/duckdb/third_party/fmt/include/fmt/format.h b/src/duckdb/third_party/fmt/include/fmt/format.h index b5ab10c1e..814efbcba 100644 --- a/src/duckdb/third_party/fmt/include/fmt/format.h +++ b/src/duckdb/third_party/fmt/include/fmt/format.h @@ -1697,6 +1697,8 @@ template class basic_writer { return; } int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + // DuckDB: limit the precision, formatting is not interruptible and the result size is linear in the precision + if (precision > 1000000) FMT_THROW(duckdb::InvalidInputException("Precision of %d is too large, the maximum is 1000000", precision)); if (fspecs.format == float_format::exp) ++precision; if (const_check(std::is_same())) fspecs.binary32 = true; fspecs.use_grisu = use_grisu(); diff --git a/src/duckdb/third_party/httplib/httplib.hpp b/src/duckdb/third_party/httplib/httplib.hpp index a933c6336..ae0a35db6 100644 --- a/src/duckdb/third_party/httplib/httplib.hpp +++ b/src/duckdb/third_party/httplib/httplib.hpp @@ -730,10 +730,11 @@ inline from_chars_result from_chars(const char *first, const char *last, } if (digit < 0 || digit >= base) { break; } - if (result > ((std::numeric_limits::max)() - digit) / base) { + if (result > ((std::numeric_limits::max)() - static_cast(digit)) / + static_cast(base)) { return {p, std::errc::result_out_of_range}; } - result = result * base + digit; + result = result * static_cast(base) + static_cast(digit); } if (p == first || (negative && p == first + 1)) { diff --git a/src/duckdb/third_party/miniz/miniz_wrapper.hpp b/src/duckdb/third_party/miniz/miniz_wrapper.hpp index a763bd22f..0c02a16a4 100644 --- a/src/duckdb/third_party/miniz/miniz_wrapper.hpp +++ b/src/duckdb/third_party/miniz/miniz_wrapper.hpp @@ -86,6 +86,9 @@ struct MiniZStream { mz_inflateEnd(&stream); // Update indices + if (stream.total_in > compressed_size || compressed_size - stream.total_in < GZIP_FOOTER_SIZE) { + FormatException("Failed to decompress GZIP block: incomplete GZIP member footer"); + } compressed_data += GZIP_FOOTER_SIZE + stream.total_in; compressed_size -= GZIP_FOOTER_SIZE + stream.total_in; out_data += stream.total_out; diff --git a/src/duckdb/ub_src_main_capi_v2.cpp b/src/duckdb/ub_src_main_capi_v2.cpp index 58ab54244..b94d045db 100644 --- a/src/duckdb/ub_src_main_capi_v2.cpp +++ b/src/duckdb/ub_src_main_capi_v2.cpp @@ -8,8 +8,12 @@ #include "src/main/capi/v2/capi_v2_column_data_collection.cpp" +#include "src/main/capi/v2/capi_v2_config.cpp" + #include "src/main/capi/v2/capi_v2_connection.cpp" +#include "src/main/capi/v2/capi_v2_custom_option.cpp" + #include "src/main/capi/v2/capi_v2_custom_type.cpp" #include "src/main/capi/v2/capi_v2_data_chunk.cpp" @@ -22,6 +26,8 @@ #include "src/main/capi/v2/capi_v2_extension.cpp" +#include "src/main/capi/v2/capi_v2_factory.cpp" + #include "src/main/capi/v2/capi_v2_file_system.cpp" #include "src/main/capi/v2/capi_v2_func.cpp" @@ -32,6 +38,12 @@ #include "src/main/capi/v2/capi_v2_func_copy.cpp" +#include "src/main/capi/v2/capi_v2_func_docs.cpp" + +#include "src/main/capi/v2/capi_v2_func_macro.cpp" + +#include "src/main/capi/v2/capi_v2_func_pragma.cpp" + #include "src/main/capi/v2/capi_v2_func_properties.cpp" #include "src/main/capi/v2/capi_v2_func_scalar.cpp" @@ -44,6 +56,8 @@ #include "src/main/capi/v2/capi_v2_instance.cpp" +#include "src/main/capi/v2/capi_v2_log_sink.cpp" + #include "src/main/capi/v2/capi_v2_logging.cpp" #include "src/main/capi/v2/capi_v2_logical_type.cpp" diff --git a/src/duckdb/ub_src_planner_sql_export.cpp b/src/duckdb/ub_src_planner_sql_export.cpp index f95d7eefa..c0bf60e21 100644 --- a/src/duckdb/ub_src_planner_sql_export.cpp +++ b/src/duckdb/ub_src_planner_sql_export.cpp @@ -8,6 +8,8 @@ #include "src/planner/sql_export/sql_export_cte.cpp" +#include "src/planner/sql_export/sql_export_dml.cpp" + #include "src/planner/sql_export/sql_export_functions.cpp" #include "src/planner/sql_export/sql_export_joins.cpp" From 512db2d361cd203be4a602b60a1b98411583e366 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Thu, 15 Oct 2026 16:18:37 +0000 Subject: [PATCH 02/13] Update vendored DuckDB sources to 8588363bf15 --- .../aggregate/algebraic/avg.cpp | 5 +- .../aggregate/distributive/bitstring_agg.cpp | 11 +- .../aggregate/distributive/kurtosis.cpp | 95 ++- .../aggregate/distributive/skew.cpp | 78 +- .../aggregate/distributive/sum.cpp | 36 +- .../holistic/approximate_quantile.cpp | 61 +- .../aggregate/algebraic/covar.hpp | 7 +- .../aggregate/algebraic/stddev.hpp | 7 +- .../aggregate/regression/regr_count.hpp | 8 +- .../scalar/array/array_value.cpp | 2 + .../core_functions/scalar/date/date_part.cpp | 14 +- .../core_functions/scalar/date/date_trunc.cpp | 12 +- .../scalar/generic/current_setting.cpp | 2 + .../scalar/generic/type_functions.cpp | 6 +- .../core_functions/scalar/list/list_sort.cpp | 21 +- .../core_functions/scalar/map/switch.cpp | 2 + .../core_functions/scalar/math/numeric.cpp | 7 +- .../scalar/string/parse_path.cpp | 186 ++--- .../core_functions/scalar/string/replace.cpp | 13 +- .../icu/collation/collation_collator.cpp | 6 + .../icu/collation/collation_normalizer.cpp | 2 +- .../collation/include/collation_collator.hpp | 8 + src/duckdb/extension/icu/icu-datefunc.cpp | 11 + src/duckdb/extension/icu/icu-datepart.cpp | 10 +- src/duckdb/extension/icu/icu-datesub.cpp | 14 + src/duckdb/extension/icu/icu-datetrunc.cpp | 15 + src/duckdb/extension/icu/icu-makedate.cpp | 6 +- src/duckdb/extension/icu/icu-strptime.cpp | 41 +- src/duckdb/extension/icu/icu-timebucket.cpp | 33 +- src/duckdb/extension/icu/icu-timezone.cpp | 4 + .../extension/icu/include/icu-datefunc.hpp | 4 + .../json/json_functions/json_create.cpp | 19 +- .../json/json_functions/json_transform.cpp | 39 +- src/duckdb/extension/json/json_reader.cpp | 10 +- .../extension/parquet/column_reader.cpp | 58 +- .../delta_length_byte_array_decoder.cpp | 45 +- .../parquet/include/parquet_dbp_decoder.hpp | 5 +- .../parquet/include/parquet_reader.hpp | 3 + .../extension/parquet/parquet_extension.cpp | 3 + .../extension/parquet/parquet_metadata.cpp | 30 +- .../extension/parquet/parquet_reader.cpp | 113 ++- .../extension/parquet/parquet_shredding.cpp | 5 + .../parquet/read_single_parquet_file.cpp | 21 + .../reader/row_number_column_reader.cpp | 9 + ...nerated_extension_loader_package_build.cpp | 8 +- .../catalog_entry/duck_table_entry.cpp | 36 +- .../catalog_entry/sequence_catalog_entry.cpp | 17 + src/duckdb/src/common/adbc/adbc.cpp | 10 +- .../common/allocator/allocator_jemalloc.cpp | 17 + .../common/allocator/allocator_standard.cpp | 4 + src/duckdb/src/common/bind_helpers.cpp | 7 +- src/duckdb/src/common/enum_util.cpp | 40 +- src/duckdb/src/common/exception.cpp | 1 - src/duckdb/src/common/file_buffer.cpp | 6 + src/duckdb/src/common/gzip_file_system.cpp | 8 +- src/duckdb/src/common/hive_partitioning.cpp | 4 +- src/duckdb/src/common/local_file_system.cpp | 7 +- .../multi_file/multi_file_column_mapper.cpp | 54 +- .../src/common/multi_file/multi_file_list.cpp | 3 +- src/duckdb/src/common/opener_file_system.cpp | 3 +- .../src/common/operator/cast_operators.cpp | 37 +- src/duckdb/src/common/sort/sort.cpp | 6 +- src/duckdb/src/common/sort/sorted_run.cpp | 18 +- .../src/common/sort/sorted_run_merger.cpp | 7 +- .../tree_renderer/html_tree_renderer.cpp | 2 +- src/duckdb/src/common/types/bignum.cpp | 32 +- src/duckdb/src/common/types/geometry.cpp | 3 + src/duckdb/src/common/types/hugeint.cpp | 259 +----- src/duckdb/src/common/types/interval.cpp | 4 +- .../types/row/tuple_data_collection.cpp | 9 +- src/duckdb/src/common/types/time.cpp | 29 +- .../variant/parquet_variant_iterator.cpp | 7 + src/duckdb/src/common/types/vector_buffer.cpp | 17 +- .../src/common/vector/constant_vector.cpp | 60 +- src/duckdb/src/common/vector/list_vector.cpp | 2 +- .../expression_executor/execute_function.cpp | 43 +- .../src/execution/index/fixed_size_buffer.cpp | 6 +- src/duckdb/src/execution/join_hashtable.cpp | 48 +- .../operator/aggregate/physical_window.cpp | 43 +- .../scanner/string_value_scanner.cpp | 36 +- .../table_function/read_single_csv_file.cpp | 3 + .../csv_scanner/util/csv_reader_options.cpp | 21 + .../csv_scanner/util/csv_validator.cpp | 2 +- .../operator/join/physical_asof_join.cpp | 16 +- .../operator/join/physical_hash_join.cpp | 155 +++- .../operator/order/physical_top_n.cpp | 19 +- .../persistent/physical_batch_insert.cpp | 4 +- .../persistent/physical_copy_to_file.cpp | 19 +- .../operator/persistent/physical_insert.cpp | 5 +- .../projection/physical_projection.cpp | 10 + .../execution/operator/set/physical_union.cpp | 13 +- .../physical_plan/plan_asof_join.cpp | 6 + .../physical_plan/plan_comparison_join.cpp | 4 +- .../physical_plan/plan_set_operation.cpp | 20 +- .../function/aggregate/distributive/count.cpp | 1 + .../aggregate/sorted_aggregate_function.cpp | 47 +- src/duckdb/src/function/cast/array_casts.cpp | 8 +- src/duckdb/src/function/cast/bignum_casts.cpp | 14 +- .../src/function/cast/cast_statistics.cpp | 4 + src/duckdb/src/function/cast/string_cast.cpp | 30 +- src/duckdb/src/function/cast/union_casts.cpp | 5 + .../function/cast/variant/from_variant.cpp | 19 +- src/duckdb/src/function/function_binder.cpp | 15 + .../src/function/pragma/pragma_functions.cpp | 34 +- .../function/scalar/generic/getvariable.cpp | 2 + .../src/function/scalar/map/map_contains.cpp | 41 + .../function/scalar/operator/arithmetic.cpp | 29 +- .../src/function/scalar/sequence/nextval.cpp | 11 + .../src/function/scalar/string/contains.cpp | 69 +- .../src/function/scalar/string/length.cpp | 23 +- .../src/function/scalar/string/regexp.cpp | 9 + .../scalar/system/aggregate_export.cpp | 20 +- .../src/function/table/arrow_conversion.cpp | 235 +++--- src/duckdb/src/function/table/checkpoint.cpp | 4 +- src/duckdb/src/function/table/range.cpp | 3 +- .../table/system/create_external_resource.cpp | 5 + .../table/system/enable_profiling.cpp | 17 + .../table/system/external_resources.cpp | 3 + .../function/table/system/logging_utils.cpp | 10 + .../function/table/version/pragma_version.cpp | 6 +- .../function/variant/variant_shredding.cpp | 6 +- .../src/function/window/window_aggregator.cpp | 4 +- .../function/window/window_value_function.cpp | 30 +- .../catalog_entry/sequence_catalog_entry.hpp | 2 + .../src/include/duckdb/common/allocator.hpp | 2 + .../src/include/duckdb/common/enum_util.hpp | 8 + .../enums/current_transaction_state.hpp | 15 - .../common/exception/http_exception.hpp | 5 + .../duckdb/common/hive_partitioning.hpp | 4 +- .../src/include/duckdb/common/hugeint.hpp | 343 +++++++- .../src/include/duckdb/common/mutex.hpp | 70 ++ .../operator/decimal_cast_operators.hpp | 15 +- .../common/operator/integer_cast_operator.hpp | 23 +- .../duckdb/common/sorting/buffered_merge.hpp | 192 +++++ .../duckdb/common/sorting/sorted_run.hpp | 2 + .../duckdb/common/thread_annotation/mutex.hpp | 16 + .../common/types/arrow_string_view_type.hpp | 4 +- .../src/include/duckdb/common/types/time.hpp | 5 +- .../duckdb/common/types/validity_mask.hpp | 7 + .../execution/expression_executor_state.hpp | 2 + .../duckdb/execution/join_hashtable.hpp | 25 +- .../duckdb/execution/merge_sort_tree.hpp | 4 +- .../persistent/physical_batch_insert.hpp | 4 +- .../operator/persistent/physical_insert.hpp | 4 +- .../projection/physical_projection.hpp | 1 + .../duckdb/execution/physical_operator.hpp | 5 + .../function/cast/variant/json_to_variant.hpp | 9 +- .../cast/variant/primitive_to_variant.hpp | 24 +- .../function/cast/variant/to_variant_fwd.hpp | 5 +- .../duckdb/function/compression_function.hpp | 6 +- .../src/include/duckdb/function/function.hpp | 6 +- .../duckdb/function/scalar/generic_common.hpp | 3 + .../function/window/window_aggregator.hpp | 2 + .../src/include/duckdb/logging/log_type.hpp | 4 +- .../duckdb/main/active_query_context.hpp | 71 +- .../duckdb/main/capi_v2/capi_v2_internal.hpp | 22 +- .../main/capi_v2/capi_v2_result_internal.hpp | 111 +-- .../include/duckdb/main/client_context.hpp | 84 +- src/duckdb/src/include/duckdb/main/config.hpp | 52 +- .../src/include/duckdb/main/connection.hpp | 2 +- .../include/duckdb/main/extension_entries.hpp | 8 +- .../include/duckdb/main/extension_manager.hpp | 10 +- .../duckdb/main/http/http_retry_budget.hpp | 162 +++- .../include/duckdb/main/http/http_util.hpp | 23 +- .../duckdb/main/prepared_statement.hpp | 13 +- .../duckdb/main/profiler/gathered_metrics.hpp | 38 +- .../duckdb/main/profiler/profiling_utils.hpp | 21 +- .../include/duckdb/main/query_profiler.hpp | 2 +- .../src/include/duckdb/main/query_result.hpp | 32 +- .../duckdb/main/query_result_stream.hpp | 17 +- .../duckdb/main/secret/default_secrets.hpp | 3 + .../src/include/duckdb/main/secret/secret.hpp | 11 +- .../duckdb/main/statement_iterator.hpp | 46 +- .../join_order/join_order_optimizer.hpp | 6 + .../relation_statistics.hpp | 15 + .../optimizer/remove_unused_columns.hpp | 2 + .../duckdb/parallel/scan_read_ahead.hpp | 24 + .../parser/peg/ast/expression_chain.hpp | 5 +- .../duckdb/parser/peg/compiled_grammar.hpp | 13 +- .../duckdb/parser/peg/inlined_grammar.hpp | 435 +++++++++- .../keyword_helper/default_keyword_maps.hpp | 2 + .../src/include/duckdb/parser/peg/matcher.hpp | 3 +- .../parser/peg/matcher/identifier_matcher.hpp | 4 + .../peg/matcher/number_literal_matcher.hpp | 8 + .../peg/transformer/peg_transformer.hpp | 187 +++-- .../duckdb/planner/expression_binder.hpp | 5 + .../duckdb/planner/filter/bloom_filter.hpp | 6 +- .../planner/filter/table_filter_functions.hpp | 43 +- .../duckdb/planner/operator/logical_join.hpp | 4 + ...bound_expression_sql_exporter_internal.hpp | 5 +- .../duckdb/planner/sql_export_helpers.hpp | 7 + .../duckdb/planner/statement_preprocessor.hpp | 23 +- .../duckdb/storage/block_allocator.hpp | 12 +- .../compression/dict_fsst/decompression.hpp | 2 +- .../storage/compression/empty_validity.hpp | 2 +- .../duckdb/storage/string_uncompressed.hpp | 2 +- .../duckdb/storage/table/column_data.hpp | 2 +- .../duckdb/storage/table/column_segment.hpp | 3 +- src/duckdb/src/include/duckdb_v2.h | 45 +- src/duckdb/src/logging/log_types.cpp | 8 +- src/duckdb/src/main/capi/v1/arrow-c.cpp | 11 +- src/duckdb/src/main/capi/v1/pending-c.cpp | 3 +- src/duckdb/src/main/capi/v1/prepared-c.cpp | 4 +- src/duckdb/src/main/capi/v2/capi_v2.cpp | 5 + .../capi/v2/capi_v2_prepared_statement.cpp | 9 +- .../src/main/capi/v2/capi_v2_result.cpp | 216 ++--- .../src/main/capi/v2/capi_v2_statement.cpp | 22 +- .../src/main/capi/v2/capi_v2_vector.cpp | 1 + src/duckdb/src/main/client_context.cpp | 205 +++-- .../src/main/client_context_execution.cpp | 217 ++++- src/duckdb/src/main/client_verify.cpp | 6 +- src/duckdb/src/main/config.cpp | 23 +- src/duckdb/src/main/connection.cpp | 6 +- .../src/main/extension/extension_helper.cpp | 3 + src/duckdb/src/main/extension_manager.cpp | 14 +- .../src/main/http/http_retry_budget.cpp | 333 +++++++- src/duckdb/src/main/http/http_util.cpp | 177 +++- src/duckdb/src/main/parser_context.cpp | 44 +- src/duckdb/src/main/prepared_statement.cpp | 37 +- .../src/main/profiler/gathered_metrics.cpp | 7 +- .../src/main/profiler/profiling_utils.cpp | 4 +- src/duckdb/src/main/query_profiler.cpp | 6 +- src/duckdb/src/main/query_result.cpp | 129 ++- src/duckdb/src/main/query_result_stream.cpp | 108 ++- .../src/main/secret/default_secrets.cpp | 8 +- src/duckdb/src/main/secret/secret.cpp | 35 + src/duckdb/src/main/secret/secret_manager.cpp | 17 +- .../src/main/settings/custom_settings.cpp | 13 +- src/duckdb/src/main/statement_iterator.cpp | 61 +- .../optimizer/common_subplan_optimizer.cpp | 86 +- src/duckdb/src/optimizer/filter_combiner.cpp | 56 +- src/duckdb/src/optimizer/filter_pushdown.cpp | 46 +- .../src/optimizer/grouping_sets_optimizer.cpp | 7 + .../join_order/join_order_optimizer.cpp | 16 +- .../optimizer/join_order/relation_manager.cpp | 12 +- .../multi_stage_aggregate_rewriter.cpp | 93 ++- src/duckdb/src/optimizer/optimizer.cpp | 6 +- .../pushdown/pushdown_cross_product.cpp | 54 ++ .../pushdown/pushdown_inner_join.cpp | 16 +- .../pushdown/pushdown_projection.cpp | 24 +- .../pushdown/pushdown_semi_anti_join.cpp | 4 +- .../relation_statistics.cpp | 18 + .../relation_statistics_helper.cpp | 27 +- .../relation_statistics_operator.cpp | 21 +- .../src/optimizer/remove_duplicate_groups.cpp | 6 +- .../src/optimizer/remove_unused_columns.cpp | 30 +- .../rule/comparison_simplification.cpp | 6 + .../rule/date_trunc_simplification.cpp | 28 + .../src/optimizer/rule/monotone_preimage.cpp | 51 +- .../statistics/operator/propagate_join.cpp | 59 +- .../src/optimizer/topn_window_elimination.cpp | 22 +- src/duckdb/src/parallel/pipeline_executor.cpp | 4 +- src/duckdb/src/parallel/scan_read_ahead.cpp | 108 ++- .../parser/expression/value_expression.cpp | 9 + .../src/parser/peg/autocomplete_core.cpp | 1 + .../src/parser/peg/compiled_grammar.cpp | 2 + .../keyword_helper/default_keyword_maps.cpp | 6 + .../parsed_grammar_keyword_helper.cpp | 30 +- src/duckdb/src/parser/peg/keyword_map.cpp | 361 ++++++++ src/duckdb/src/parser/peg/matcher_factory.cpp | 1 + .../parser/peg/tokenizer/base_tokenizer.cpp | 5 +- .../transformer/peg_transformer_factory.cpp | 3 +- .../peg/transformer/transform_common.cpp | 70 +- .../parser/peg/transformer/transform_copy.cpp | 21 +- .../transformer/transform_create_sequence.cpp | 10 + .../transformer/transform_create_table.cpp | 3 + .../peg/transformer/transform_create_type.cpp | 7 +- .../peg/transformer/transform_create_view.cpp | 3 + .../peg/transformer/transform_delete.cpp | 16 +- .../peg/transformer/transform_describe.cpp | 3 + .../parser/peg/transformer/transform_drop.cpp | 10 +- .../peg/transformer/transform_expression.cpp | 174 ++-- .../transform_generated_trampoline.cpp | 775 ++++++++++++++---- .../transform_generic_copy_option.cpp | 3 +- .../peg/transformer/transform_insert.cpp | 6 +- .../peg/transformer/transform_pivot.cpp | 12 +- .../peg/transformer/transform_select.cpp | 36 +- .../peg/transformer/transform_update.cpp | 24 +- .../parser/peg/transformer/transform_use.cpp | 16 +- .../peg/transformer/transform_vacuum.cpp | 2 + src/duckdb/src/planner/bind_context.cpp | 17 +- src/duckdb/src/planner/binder.cpp | 5 + .../expression/bind_aggregate_expression.cpp | 15 + .../expression/bind_columnref_expression.cpp | 3 + .../expression/bind_function_expression.cpp | 40 +- .../expression/bind_macro_expression.cpp | 3 + .../expression/bind_type_expression.cpp | 1 + .../expression/bind_window_expression.cpp | 4 +- .../binder/query_node/bind_cte_node.cpp | 3 + .../query_node/bind_recursive_cte_node.cpp | 3 + .../binder/query_node/bind_select_node.cpp | 7 +- .../query_node/bind_table_macro_node.cpp | 3 + .../planner/binder/statement/bind_attach.cpp | 4 +- .../planner/binder/statement/bind_connect.cpp | 2 +- .../planner/binder/statement/bind_copy.cpp | 2 +- .../binder/statement/bind_copy_database.cpp | 11 +- .../binder/statement/bind_create_table.cpp | 5 + .../planner/binder/statement/bind_export.cpp | 7 +- .../statement/bind_external_resource.cpp | 2 +- .../binder/statement/bind_merge_into.cpp | 48 +- .../binder/tableref/bind_basetableref.cpp | 6 +- .../planner/binder/tableref/bind_pivot.cpp | 43 +- .../planner/binder/tableref/bind_showref.cpp | 3 + .../binder/tableref/bind_table_function.cpp | 9 + .../expression/bound_aggregate_expression.cpp | 6 + .../expression/bound_cast_expression.cpp | 68 +- src/duckdb/src/planner/expression_binder.cpp | 4 + .../expression_binder/check_binder.cpp | 5 +- .../expression_binder/index_binder.cpp | 23 +- .../src/planner/filter/dynamic_filter.cpp | 4 +- .../filter/table_filter_bloom_function.cpp | 128 ++- .../filter/table_filter_dynamic_function.cpp | 5 +- .../planner/filter/table_filter_functions.cpp | 3 +- .../src/planner/operator/logical_join.cpp | 16 + src/duckdb/src/planner/planner.cpp | 9 +- .../bound_expression_sql_exporter.cpp | 20 +- .../sql_export/sql_export_composition.cpp | 3 + .../sql_export/sql_export_functions.cpp | 10 +- .../planner/sql_export/sql_export_joins.cpp | 16 - .../sql_export/sql_export_relations.cpp | 11 +- .../planner/sql_export/sql_export_window.cpp | 28 +- .../src/planner/statement_preprocessor.cpp | 183 +---- .../subquery/delim_join_cte_rewriter.cpp | 5 +- .../subquery/flatten_dependent_join.cpp | 14 +- src/duckdb/src/planner/table_filter_state.cpp | 2 +- src/duckdb/src/storage/block_allocator.cpp | 29 +- src/duckdb/src/storage/buffer/buffer_pool.cpp | 4 +- src/duckdb/src/storage/checkpoint_manager.cpp | 7 +- .../src/storage/compression/dict_fsst.cpp | 14 +- .../compression/dict_fsst/decompression.cpp | 37 +- src/duckdb/src/storage/compression/fsst.cpp | 6 +- .../storage/compression/numeric_constant.cpp | 17 +- src/duckdb/src/storage/compression/rle.cpp | 22 +- .../compression/string_uncompressed.cpp | 5 +- .../compression/validity_uncompressed.cpp | 19 +- src/duckdb/src/storage/data_table.cpp | 2 + .../caching_file_system.cpp | 6 + .../src/storage/statistics/numeric_stats.cpp | 27 +- .../src/storage/statistics/variant_stats.cpp | 4 +- src/duckdb/src/storage/table/column_data.cpp | 77 +- .../src/storage/table/column_segment.cpp | 9 +- .../storage/table/standard_column_data.cpp | 9 +- src/duckdb/src/storage/wal_replay.cpp | 5 + .../src/transaction/meta_transaction.cpp | 7 +- src/duckdb/third_party/httplib/httplib.hpp | 16 +- src/duckdb/third_party/ska_sort/ska_sort.hpp | 64 +- src/duckdb/third_party/vergesort/vergesort.h | 30 +- 347 files changed, 7844 insertions(+), 2913 deletions(-) delete mode 100644 src/duckdb/src/include/duckdb/common/enums/current_transaction_state.hpp create mode 100644 src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp diff --git a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp index bbe22c410..2b7768041 100644 --- a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp @@ -239,8 +239,9 @@ struct IntervalAverageOperation : public BaseSumOperation(shifted_days / hugeint_t(count)); target.micros += micros_remainder; } } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp index 3f1b8affc..343915caa 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp @@ -123,15 +123,12 @@ struct BitStringAggOperation { if (min > max) { throw InvalidInputException("Invalid explicit bitstring range: Minimum (%d) > maximum (%d)", min, max); } - INPUT_TYPE result; - if (!TrySubtractOperator::Operation(max, min, result)) { + // the difference of two (at most 64-bit) values does not always fit in their own type, but it fits in a hugeint + const auto range = Hugeint::Convert(max) - Hugeint::Convert(min); + if (range >= Hugeint::Convert(NumericLimits::Maximum())) { return NumericLimits::Maximum(); } - auto val = NumericCast(result); - if (val == NumericLimits::Maximum()) { - return val; - } - return val + 1; + return Hugeint::Cast(range) + 1; } template diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp index c7b4853a5..a5e0a5a39 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp @@ -1,3 +1,4 @@ +#include "duckdb/common/operator/add.hpp" #include "core_functions/aggregate/distributive_functions.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" @@ -8,15 +9,17 @@ namespace duckdb { namespace { +//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's +//! method used by var_samp: M2..M4 are exactly zero for constant input instead of rounding noise of power sums struct KurtosisState { - static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub", "sum_four"}; + static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3", "m4"}; using STATE_TYPE = StructStateType; idx_t n; - double sum; - double sum_sqr; - double sum_cub; - double sum_four; + double mean; // M1 + double m2; // M2 = sum((x - mean)^2) + double m3; // M3 = sum((x - mean)^3) + double m4; // M4 = sum((x - mean)^4) }; struct KurtosisFlagBiasCorrection {}; @@ -28,18 +31,29 @@ struct KurtosisOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - for (idx_t i = 0; i < count; i++) { - Operation(state, input, unary_input); - } + // a block of identical values has mean "input" and zero central moments + STATE constant; + constant.n = count; + constant.mean = static_cast(input); + constant.m2 = 0; + constant.m3 = 0; + constant.m4 = 0; + Combine(constant, state, unary_input.input); } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { + const double n1 = static_cast(state.n); state.n++; - state.sum += input; - state.sum_sqr += pow(input, 2); - state.sum_cub += pow(input, 3); - state.sum_four += pow(input, 4); + const double n = static_cast(state.n); + const double delta = static_cast(input) - state.mean; + const double delta_n = delta / n; + const double delta_n2 = delta_n * delta_n; + const double term1 = delta * delta_n * n1; + state.mean += delta_n; + state.m4 += term1 * delta_n2 * (n * n - 3 * n + 3) + 6 * delta_n2 * state.m2 - 4 * delta_n * state.m3; + state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; + state.m2 += term1; } template @@ -47,45 +61,52 @@ struct KurtosisOperation { if (source.n == 0) { return; } - target.n += source.n; - target.sum += source.sum; - target.sum_sqr += source.sum_sqr; - target.sum_cub += source.sum_cub; - target.sum_four += source.sum_four; + if (target.n == 0) { + target = source; + return; + } + idx_t count; + if (!TryAddOperator::Operation(target.n, source.n, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } + const double na = static_cast(target.n); + const double nb = static_cast(source.n); + const double n = static_cast(count); + const double delta = source.mean - target.mean; + const double delta_n = delta / n; + const double delta_n2 = delta_n * delta_n; + target.m4 += source.m4 + delta * delta_n * delta_n2 * na * nb * (na * na - na * nb + nb * nb) + + 6 * delta_n2 * (na * na * source.m2 + nb * nb * target.m2) + + 4 * delta_n * (na * source.m3 - nb * target.m3); + target.m3 += + source.m3 + delta * delta_n2 * na * nb * (na - nb) + 3 * delta_n * (na * source.m2 - nb * target.m2); + target.m2 += source.m2 + delta * delta_n * na * nb; + target.mean += delta_n * nb; + target.n = count; } template static void Finalize(STATE &state, TARGET_TYPE &target, AggregateFinalizeData &finalize_data) { - auto n = (double)state.n; - if (n <= 1) { + if (state.n <= 1) { finalize_data.ReturnNull(); return; } - if (std::is_same::value && n <= 3) { + if (std::is_same::value && state.n <= 3) { finalize_data.ReturnNull(); return; } - double temp = 1 / n; - //! This is necessary due to linux 32 bits - long double temp_aux = 1 / n; - if (state.sum_sqr - state.sum * state.sum * temp == 0 || - state.sum_sqr - state.sum * state.sum * temp_aux == 0) { - finalize_data.ReturnNull(); - return; - } - double m4 = - temp * (state.sum_four - 4 * state.sum_cub * state.sum * temp + - 6 * state.sum_sqr * state.sum * state.sum * temp * temp - 3 * pow(state.sum, 4) * pow(temp, 3)); - - double m2 = temp * (state.sum_sqr - state.sum * state.sum * temp); - if (m2 <= 0) { // m2 shouldn't be below 0 but floating points are weird + // kurtosis is undefined for zero variance (M2 is exactly 0 for constant input) + if (state.m2 <= 0) { finalize_data.ReturnNull(); return; } + const double n = static_cast(state.n); + // m4 / m2^2 with the population moments m4 = M4 / n and m2 = M2 / n + const double m4_over_m2_sqr = n * (state.m4 / state.m2) / state.m2; if (std::is_same::value) { - target = m4 / (m2 * m2) - 3; + target = m4_over_m2_sqr - 3; } else { - target = (n - 1) * ((n + 1) * m4 / (m2 * m2) - 3 * (n - 1)) / ((n - 2) * (n - 3)); + target = (n - 1) * ((n + 1) * m4_over_m2_sqr - 3 * (n - 1)) / ((n - 2) * (n - 3)); } if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("Kurtosis is out of range!"); diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp index 8443c7e55..f354c98bf 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp @@ -3,37 +3,47 @@ #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/common/algorithm.hpp" -#include namespace duckdb { namespace { +//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's +//! method used by var_samp: M2 and M3 are exactly zero for constant input instead of rounding noise of power sums struct SkewState { - static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub"}; + static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3"}; using STATE_TYPE = StructStateType; idx_t n; - double sum; - double sum_sqr; - double sum_cub; + double mean; // M1 + double m2; // M2 = sum((x - mean)^2) + double m3; // M3 = sum((x - mean)^3) }; struct SkewnessOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - for (idx_t i = 0; i < count; i++) { - Operation(state, input, unary_input); - } + // a block of identical values has mean "input" and zero central moments + STATE constant; + constant.n = count; + constant.mean = static_cast(input); + constant.m2 = 0; + constant.m3 = 0; + Combine(constant, state, unary_input.input); } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { + const double n1 = static_cast(state.n); state.n++; - state.sum += input; - state.sum_sqr += pow(input, 2); - state.sum_cub += pow(input, 3); + const double n = static_cast(state.n); + const double delta = static_cast(input) - state.mean; + const double delta_n = delta / n; + const double term1 = delta * delta_n * n1; + state.mean += delta_n; + state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; + state.m2 += term1; } template @@ -41,11 +51,22 @@ struct SkewnessOperation { if (source.n == 0) { return; } - - target.n += source.n; - target.sum += source.sum; - target.sum_sqr += source.sum_sqr; - target.sum_cub += source.sum_cub; + if (target.n == 0) { + target = source; + return; + } + const auto count = target.n + source.n; + D_ASSERT(count >= target.n); // This is a check that we are not overflowing + const double na = static_cast(target.n); + const double nb = static_cast(source.n); + const double n = static_cast(count); + const double delta = source.mean - target.mean; + const double delta_n = delta / n; + target.m3 += source.m3 + delta * delta_n * delta_n * na * nb * (na - nb) + + 3 * delta_n * (na * source.m2 - nb * target.m2); + target.m2 += source.m2 + delta * delta_n * na * nb; + target.mean += delta_n * nb; + target.n = count; } template @@ -54,26 +75,15 @@ struct SkewnessOperation { finalize_data.ReturnNull(); return; } - double n = state.n; - double temp = 1 / n; - double raw_m2 = state.sum_sqr - state.sum * state.sum * temp; - // Only treat finite second-moment noise as zero; overflow should still surface as out-of-range. - if (Value::DoubleIsFinite(raw_m2) && Value::DoubleIsFinite(state.sum_sqr) && - // Scale the tolerance by the accumulated squared magnitude instead of using a fixed epsilon. - std::abs(raw_m2) <= std::numeric_limits::epsilon() * std::max(1.0, std::abs(state.sum_sqr))) { - finalize_data.ReturnNull(); - return; - } - double variance = temp * raw_m2; - if (variance <= 0) { + // skewness is undefined for zero variance (M2 is exactly 0 for constant input) + if (state.m2 <= 0) { finalize_data.ReturnNull(); return; } - auto p = std::pow(variance, 3); - double div = std::sqrt(p); - double temp1 = std::sqrt(n * (n - 1)) / (n - 2); - target = temp1 * temp * - (state.sum_cub - 3 * state.sum_sqr * state.sum * temp + 2 * pow(state.sum, 3) * temp * temp) / div; + const double n = static_cast(state.n); + // g1 = (M3 / n) / (M2 / n)^1.5 = sqrt(n) * M3 / M2^1.5, with the bias correction sqrt(n (n - 1)) / (n - 2) + const double g1 = std::sqrt(n) * (state.m3 / state.m2) / std::sqrt(state.m2); + target = std::sqrt(n * (n - 1)) / (n - 2) * g1; if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("SKEW is out of range!"); } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp index 55a67fb58..bcb7594c2 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp @@ -247,8 +247,17 @@ struct DecimalSumOperation : public OP { } }; -unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { - throw BinderException("sum_no_overflow is for internal use only!"); +//! Binds the regular sum that sum_no_overflow is a specialized implementation of +pair> BindLogicalSum(ClientContext &context, + vector> &arguments) { + vector argument_types; + for (auto &argument : arguments) { + argument_types.push_back(argument->GetReturnType()); + } + auto &entry = Catalog::GetEntry( + context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); + FunctionBinder binder(context); + return binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); } void SumNoOverflowSerialize(Serializer &serializer, const optional_ptr bind_data, @@ -261,15 +270,10 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo auto &return_type = deserializer.Get(); auto &children = deserializer.Get(); vector> arguments; - vector argument_types; for (auto &child : children) { arguments.push_back(child.get().Copy()); - argument_types.push_back(child.get().GetReturnType()); } - auto &entry = Catalog::GetEntry( - context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); - FunctionBinder binder(context); - auto logical = binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); + auto logical = BindLogicalSum(context, arguments); if (logical.second || (!return_type.IsAggregateState() && logical.first.GetReturnType() != return_type)) { throw SerializationException("Cannot reconstruct the logical sum signature"); } @@ -279,6 +283,8 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo return nullptr; } +unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input); + AggregateFunction GetSumAggregateNoOverflow(PhysicalType type) { switch (type) { case PhysicalType::INT32: { @@ -318,6 +324,20 @@ AggregateFunction GetSumAggregateNoOverflowDecimal() { return aggr; } +//! Binding sum_no_overflow by name (e.g. when re-binding an exported aggregate state) delegates to the regular sum, +//! with the no-overflow implementation swapped in as statistics propagation does +unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { + auto &function = input.GetBoundFunction(); + auto &arguments = input.GetArguments(); + const auto parameter_type = function.GetArguments()[0]; + auto logical = BindLogicalSum(input.GetClientContext(), arguments); + if (parameter_type.id() != LogicalTypeId::DECIMAL) { + logical.first.ReplaceImplementation(GetSumAggregateNoOverflow(parameter_type.InternalType())); + } + function = std::move(logical.first); + return std::move(logical.second); +} + unique_ptr SumPropagateStats(ClientContext &context, BoundAggregateExpression &expr, AggregateStatisticsInput &input) { if (!input.node_stats || !input.node_stats->has_max_cardinality) { diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp index 008c81d94..b545e937a 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp @@ -1,3 +1,4 @@ +#include "duckdb/common/operator/add.hpp" #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/common/vector/list_vector.hpp" @@ -38,6 +39,22 @@ struct ApproxQuantileCoding { } return false; } + + template + static void Decode(const SAVE_TYPE &source, TARGET_TYPE &target, const LogicalType &type) { + Decode(source, target); + if (type.id() != LogicalTypeId::DECIMAL) { + return; + } + // The unscaled decimal range can be narrower than its physical integer range. + const auto limit = Hugeint::POWERS_OF_TEN[DecimalType::GetWidth(type)] - 1; + const auto value = Cast::Operation(target); + if (value > limit) { + target = Cast::Operation(limit); + } else if (value < -limit) { + target = Cast::Operation(-limit); + } + } }; template <> @@ -64,6 +81,26 @@ bool ApproxQuantileCoding::Decode(const double &source, dtime_tz_t &target) { return decoded; } +template <> +double ApproxQuantileCoding::Encode(const timestamp_t &input) { + return Encode(input.value); +} + +template <> +bool ApproxQuantileCoding::Decode(const double &source, timestamp_t &target) { + int64_t value; + const auto decoded = Decode(source, value); + // the infinities are the largest and smallest timestamps - do not return anything beyond them + if (value >= timestamp_t::infinity().value) { + target = timestamp_t::infinity(); + } else if (value <= timestamp_t::ninfinity().value) { + target = timestamp_t::ninfinity(); + } else { + target = timestamp_t(value); + } + return decoded; +} + struct ApproximateQuantileBindData : public FunctionData { ApproximateQuantileBindData() { } @@ -137,8 +174,10 @@ struct ApproxQuantileOperation { auto &allocator = aggr_input_data.allocator; target.h = allocator.Make(allocator, 100); } + if (!TryAddOperator::Operation(target.pos, source.pos, target.pos)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } target.h->merge(source.h); - target.pos += source.pos; } static bool IgnoreNull() { @@ -159,7 +198,7 @@ struct ApproxQuantileScalarOperation : public ApproxQuantileOperation { auto &bind_data = finalize_data.input.bind_data->template Cast(); D_ASSERT(bind_data.quantiles.size() == 1); const auto source = state.h->quantile(bind_data.quantiles[0]); - ApproxQuantileCoding::Decode(source, target); + ApproxQuantileCoding::Decode(source, target, finalize_data.result.GetType()); } }; @@ -309,6 +348,10 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ return AggregateFunction::UnaryAggregate(type, type); } + if (type.id() == LogicalTypeId::TIMESTAMP || type.id() == LogicalTypeId::TIMESTAMP_TZ) { + return AggregateFunction::UnaryAggregate(type, type); + } switch (type.InternalType()) { case PhysicalType::INT8: return AggregateFunction::UnaryAggregate( @@ -339,15 +382,12 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ AggregateFunction GetApproximateQuantileDecimalAggregateFunction(const LogicalType &type) { switch (type.InternalType()) { case PhysicalType::INT8: - return GetApproximateQuantileAggregateFunction(LogicalType::TINYINT); case PhysicalType::INT16: - return GetApproximateQuantileAggregateFunction(LogicalType::SMALLINT); case PhysicalType::INT32: - return GetApproximateQuantileAggregateFunction(LogicalType::INTEGER); case PhysicalType::INT64: - return GetApproximateQuantileAggregateFunction(LogicalType::BIGINT); case PhysicalType::INT128: - return GetApproximateQuantileAggregateFunction(LogicalType::HUGEINT); + // keep the DECIMAL type as the input and return type so that the width and scale survive the bind + return GetApproximateQuantileAggregateFunction(type); default: throw InternalException("Unimplemented quantile decimal aggregate"); } @@ -461,7 +501,7 @@ struct ApproxQuantileListOperation : public ApproxQuantileOperation { const auto &quantile = bind_data.quantiles[q]; const auto &source = state.h->quantile(quantile); auto &target = rdata[ridx + q]; - ApproxQuantileCoding::Decode(source, target); + ApproxQuantileCoding::Decode(source, target, result.GetType()); } ListVector::SetListSize(finalize_data.result, entry.offset + entry.length); @@ -498,10 +538,11 @@ AggregateFunction GetApproxQuantileListAggregateFunction(const LogicalType &type case LogicalTypeId::DATE: return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::BIGINT: - case LogicalTypeId::TIMESTAMP: - case LogicalTypeId::TIMESTAMP_TZ: case LogicalTypeId::TIME: return GetTypedApproxQuantileListAggregateFunction(type); + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::TIME_TZ: // Not binary comparable return GetTypedApproxQuantileListAggregateFunction(type); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp index 719736066..34dcb6f6d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp @@ -9,6 +9,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" namespace duckdb { @@ -48,8 +49,10 @@ struct CovarOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - const auto count = target.count + source.count; - D_ASSERT(count >= target.count); // This is a check that we are not overflowing + uint64_t count; + if (!TryAddOperator::Operation(target.count, source.count, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } const auto target_count = static_cast(target.count); const auto source_count = static_cast(source.count); const auto total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp index 0150248c1..0068b906d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include #include @@ -57,8 +58,10 @@ struct STDDevBaseOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - const auto count = target.count + source.count; - D_ASSERT(count >= target.count); // This is a check that we are not overflowing + uint64_t count; + if (!TryAddOperator::Operation(target.count, source.count, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } const double target_count = static_cast(target.count); const double source_count = static_cast(source.count); const double total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp index ac9836499..dbe33743d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp @@ -9,6 +9,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include "core_functions/aggregate/algebraic/covar.hpp" #include "core_functions/aggregate/algebraic/stddev.hpp" @@ -18,11 +19,16 @@ namespace duckdb { struct RegrCountFunction { template static void Combine(const STATE &source, STATE &target, AggregateInputData &) { - target += source; + if (!TryAddOperator::Operation(target, source, target)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } } template static void Finalize(STATE &state, T &target, AggregateFinalizeData &finalize_data) { + if (state > NumericLimits::Maximum()) { + throw OutOfRangeException("regr_count result %llu does not fit in the result type", state); + } target = static_cast(state); } static bool IgnoreNull() { diff --git a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp index 91105869d..08e1a0abf 100644 --- a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp +++ b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp @@ -95,6 +95,8 @@ ScalarFunction ArrayValueFun::GetFunction() { fun.SetResolveTypesCallback(ArrayValueResolveTypes); fun.GetSignature().AddArgs("args", LogicalType::ANY); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // casting the arguments to the common child type can fail, e.g. for arrays of different sizes + fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp index 3b8f8ae2e..5663e2957 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp @@ -440,7 +440,19 @@ struct DatePart { template static unique_ptr PropagateStatistics(ClientContext &context, FunctionStatisticsInput &input) { - return PropagateDatePartStatistics(input.child_stats); + auto result = PropagateDatePartStatistics(input.child_stats); + if (!result) { + return nullptr; + } + const auto min_year = NumericStats::GetMin(*result); + const auto max_year = NumericStats::GetMax(*result); + if (min_year > 0) { + return PropagateDatePartStatistics(input.child_stats); + } + // the week is subtracted for years before the common era, so only bound the result by the ISO year + NumericStats::SetMin(*result, Value::BIGINT(min_year * 100 - 53)); + NumericStats::SetMax(*result, Value::BIGINT(max_year * 100 + 53)); + return result; } }; diff --git a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp index e6bd79669..9998eb3d5 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp @@ -494,8 +494,16 @@ unique_ptr DateTruncStatistics(vector &child_sta } // Infinite values are unmodified - auto min_part = DateTrunc::UnaryFunction(min); - auto max_part = DateTrunc::UnaryFunction(max); + TR min_part; + TR max_part; + try { + min_part = DateTrunc::UnaryFunction(min); + max_part = DateTrunc::UnaryFunction(max); + } catch (const ConversionException &) { + // the bounds are not in the range of the result type (e.g. a DATE beyond the TIMESTAMP range) - the rows + // holding them may not even reach the function, so do not propagate statistics + return nullptr; + } auto min_value = Value::CreateValue(min_part); auto max_value = Value::CreateValue(max_part); diff --git a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp index 48b0adbea..c1da6d140 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp @@ -59,6 +59,8 @@ ScalarFunction CurrentSettingFun::GetFunction() { auto fun = ScalarFunction({}, LogicalType::ANY, CurrentSettingFunction, CurrentSettingBind); fun.GetSignature().AddParameter("setting_name", LogicalType::VARCHAR); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // the value of the setting can change between queries + fun.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp index 685dd73b1..03fd01d40 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp @@ -111,7 +111,11 @@ static unique_ptr BindMakeTypeFunctionExpression(FunctionBindExpress } auto type_name = args.front().second.GetValue(); - auto qualified_name = QualifiedName::Parse(type_name); + QualifiedName qualified_name; + string error; + if (!QualifiedName::TryParse(type_name, qualified_name, error)) { + throw ParserException(error); + } auto unbound_type = LogicalType::UNBOUND(make_uniq(std::move(qualified_name), std::move(type_args))); diff --git a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp index 5e498ea96..a2281bdca 100644 --- a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp +++ b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp @@ -459,6 +459,16 @@ static unique_ptr ListSortUnbind(FunctionUnbindInput &input) { return make_uniq(QualifiedName("system", "main", "cast_to_type"), std::move(result)); } +static void ListSortResolveTypes(ResolveScalarFunctionTypesInput &input) { + auto list_type = input.GetArgumentType(0); + if (list_type.id() == LogicalTypeId::ARRAY) { + list_type = LogicalType::LIST(ArrayType::GetChildType(list_type)); + } + if (list_type.id() == LogicalTypeId::LIST) { + input.GetBoundFunction().SetReturnType(list_type); + } +} + static void SetListSortSerialization(ScalarFunction &function) { function.SetUnbindCallback(ListSortUnbind); function.SetSerializeCallback(ListSortBindData::Serialize); @@ -483,8 +493,11 @@ ScalarFunctionSet ListSortFun::GetFunctions() { .AddParameter("sort_order", LogicalType::VARCHAR) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort); + sort.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_order); + sort_order.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_orders); + sort_orders.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_sort; list_sort.AddFunction(sort); @@ -495,17 +508,17 @@ ScalarFunctionSet ListSortFun::GetFunctions() { ScalarFunctionSet ListGradeUpFun::GetFunctions() { // one parameter: list - ScalarFunction sort({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort.GetSignature().AddParameter("list", LogicalType::LIST(LogicalType::ANY)); // two parameters: list, order - ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort_order.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR); // three parameters: list, order, null order - ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort_orders.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR) @@ -533,7 +546,9 @@ ScalarFunctionSet ListReverseSortFun::GetFunctions() { .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort_reverse); + sort_reverse.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_reverse_null_order); + sort_reverse_null_order.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_reverse_sort; list_reverse_sort.AddFunction(sort_reverse); diff --git a/src/duckdb/extension/core_functions/scalar/map/switch.cpp b/src/duckdb/extension/core_functions/scalar/map/switch.cpp index c060299bd..1eec73243 100644 --- a/src/duckdb/extension/core_functions/scalar/map/switch.cpp +++ b/src/duckdb/extension/core_functions/scalar/map/switch.cpp @@ -122,6 +122,8 @@ ScalarFunctionSet SwitchFun::GetFunctions() { switch_expression.GetSignature().AddParameter(param.first, param.second); } switch_expression.SetBindExpressionCallback(SwitchBindExpression); + // switch is rewritten into a CASE expression, so NULL arguments do not make the result NULL + switch_expression.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); func_set.AddFunction(std::move(switch_expression)); } diff --git a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp index f71b80a0e..a69ceeede 100644 --- a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp +++ b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp @@ -1005,9 +1005,14 @@ struct RoundOperatorPrecision { double rounded_value; if (precision < 0) { double modifier = std::pow(10, -TA(precision)); + if (std::isinf(modifier)) { + // every finite value rounds to zero + return Value::IsFinite(input) ? 0 : input; + } rounded_value = ROUND_POLICY::Nearest(input / modifier) * modifier; if (std::isinf(rounded_value) || std::isnan(rounded_value)) { - return 0; + // the input is too large to be rounded to this precision + return input; } } else { double modifier = std::pow(10, TA(precision)); diff --git a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp index a4c40b066..4abc7d07c 100644 --- a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp @@ -1,5 +1,6 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" +#include "duckdb/common/vector/vector_writer.hpp" #include "core_functions/scalar/string_functions.hpp" #include "duckdb/function/scalar/string_common.hpp" #include "duckdb/common/local_file_system.hpp" @@ -105,126 +106,111 @@ static idx_t SplitPath(string_t input, const string &sep, CB &&emit) { return list_idx; } -static void ReadOptionalArgs(DataChunk &args, Vector &sep, Vector &trim, const bool &front_trim) { +//! The optional separator and trim_extension arguments - NULL values use the default options +struct PathOptionArguments { + optional_ptr separator; + optional_ptr trim_extension; +}; + +static PathOptionArguments GetPathOptionArguments(DataChunk &args, const bool front_trim) { + PathOptionArguments result; switch (args.ColumnCount()) { - case 1: { + case 1: // use default values break; - } - case 2: { - UnifiedVectorFormat sec_arg; - args.data[1].ToUnifiedFormat(sec_arg); - if (sec_arg.validity.RowIsValid(0)) { // if not NULL - switch (args.data[1].GetType().id()) { - case LogicalTypeId::VARCHAR: { - sep.Reinterpret(args.data[1]); - break; - } - case LogicalTypeId::BOOLEAN: { // parse_path and parse_driname won't get in here - trim.Reinterpret(args.data[1]); - break; - } - default: - throw InvalidInputException("Invalid argument type"); - } + case 2: + switch (args.data[1].GetType().id()) { + case LogicalTypeId::VARCHAR: + result.separator = args.data[1]; + break; + case LogicalTypeId::BOOLEAN: // parse_path and parse_dirname won't get in here + result.trim_extension = args.data[1]; + break; + default: + throw InvalidInputException("Invalid argument type"); } break; - } - case 3: { - if (!front_trim) { - // set trim_extension - UnifiedVectorFormat sec_arg; - args.data[1].ToUnifiedFormat(sec_arg); - if (sec_arg.validity.RowIsValid(0)) { - trim.Reinterpret(args.data[1]); - } - UnifiedVectorFormat third_arg; - args.data[2].ToUnifiedFormat(third_arg); - if (third_arg.validity.RowIsValid(0)) { - sep.Reinterpret(args.data[2]); - } - } else { + case 3: + if (front_trim) { throw InvalidInputException("Invalid number of arguments"); } + result.trim_extension = args.data[1]; + result.separator = args.data[2]; break; - } default: throw InvalidInputException("Invalid number of arguments"); } + return result; } -template -static void TrimPathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - // set default values - const Vector &path = args.data[0]; - Vector separator(string_t("default"), count_t(args.size())); - Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); - ReadOptionalArgs(args, separator, trim_extension, FRONT_TRIM); +template +static void ExecutePathFunction(DataChunk &args, Vector &result, const bool front_trim, OP &&op) { + const auto count = args.size(); + auto options = GetPathOptionArguments(args, front_trim); + Vector default_separator(string_t("default"), count_t(count)); + Vector default_trim_extension(Value::BOOLEAN(false), count_t(count)); + auto paths = args.data[0].Values(); + auto separators = (options.separator ? *options.separator : default_separator).Values(); + auto trim_extensions = (options.trim_extension ? *options.trim_extension : default_trim_extension).Values(); - TernaryExecutor::Execute( - path, separator, trim_extension, result, [&](string_t &inputs, string_t input_sep, bool trim_extension) { - auto data = inputs.GetData(); - auto input_size = inputs.GetSize(); - auto sep = GetSeparator(input_sep.GetString()); + auto writer = FlatVector::Writer(result, count); + for (idx_t i = 0; i < count; i++) { + auto path = paths[i]; + if (!path.IsValid()) { + writer.WriteNull(); + continue; + } + auto separator = separators[i]; + auto sep = GetSeparator(separator.IsValid() ? separator.GetValue() : string_t("default")); + auto trim_extension = trim_extensions[i]; + writer.WriteValue(op(path.GetValue(), sep, trim_extension.IsValid() && trim_extension.GetValue())); + } +} - // find the beginning idx and the size of the result string - idx_t begin = 0; - idx_t new_size = input_size; - if (FRONT_TRIM) { // left trim - auto pos = Find(data, input_size, sep); - if (pos == 0) { // path starts with separator - pos = 1; - } - new_size = (IsIdxValid(pos, input_size)) ? pos : 0; - } else { // right trim - auto idx_last_sep = FindLast(data, input_size, sep); - if (IsIdxValid(idx_last_sep, input_size)) { - begin = idx_last_sep + 1; - } - if (trim_extension) { - auto idx_extension_sep = FindLast(data, input_size, "."); - if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { - new_size = idx_extension_sep; - } - } - } - // copy the trimmed string - D_ASSERT(begin <= new_size); - auto target = StringVector::EmptyString(result, new_size - begin); - auto output = target.GetDataWriteable(); - memcpy(output, data + begin, new_size - begin); +template +static void TrimPathFunction(DataChunk &args, ExpressionState &state, Vector &result) { + ExecutePathFunction(args, result, FRONT_TRIM, [&](const string_t &input, const string &sep, bool trim_extension) { + auto data = input.GetData(); + auto input_size = input.GetSize(); - target.Finalize(); - return target; - }); + // find the beginning idx and the size of the result string + idx_t begin = 0; + idx_t new_size = input_size; + if (FRONT_TRIM) { // left trim + auto pos = Find(data, input_size, sep); + if (pos == 0) { // path starts with separator + pos = 1; + } + new_size = (IsIdxValid(pos, input_size)) ? pos : 0; + } else { // right trim + auto idx_last_sep = FindLast(data, input_size, sep); + if (IsIdxValid(idx_last_sep, input_size)) { + begin = idx_last_sep + 1; + } + if (trim_extension) { + auto idx_extension_sep = FindLast(data, input_size, "."); + if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { + new_size = idx_extension_sep; + } + } + } + D_ASSERT(begin <= new_size); + return string_t(data + begin, UnsafeNumericCast(new_size - begin)); + }); } static void ParseDirpathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - // set default values - const Vector &path = args.data[0]; - Vector separator(string_t("default"), count_t(args.size())); - Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); - ReadOptionalArgs(args, separator, trim_extension, true); + ExecutePathFunction(args, result, true, [&](const string_t &input, const string &sep, bool) { + auto path = input.GetData(); + auto path_size = input.GetSize(); - auto &heap = StringVector::GetStringHeap(result); - BinaryExecutor::Execute( - path, separator, result, [&](string_t input_path, string_t input_sep) { - auto path = input_path.GetData(); - auto path_size = input_path.GetSize(); - auto sep = GetSeparator(input_sep.GetString()); - - auto last_sep = FindLast(path, path_size, sep); - if (last_sep == 0 && path_size == 1) { - last_sep = 1; - } - idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; - - auto target = heap.EmptyString(new_size); - auto output = target.GetDataWriteable(); - memcpy(output, path, new_size); - target.Finalize(); - return target; - }); + auto last_sep = FindLast(path, path_size, sep); + if (last_sep == 0 && path_size == 1) { + last_sep = 1; + } + idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; + return string_t(path, UnsafeNumericCast(new_size)); + }); } static void ParsePathFunction(DataChunk &args, ExpressionState &state, Vector &result) { diff --git a/src/duckdb/extension/core_functions/scalar/string/replace.cpp b/src/duckdb/extension/core_functions/scalar/string/replace.cpp index e2d70be15..91f72baf2 100644 --- a/src/duckdb/extension/core_functions/scalar/string/replace.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/replace.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/common/vector_operations/ternary_executor.hpp" +#include "duckdb/function/scalar/string_common.hpp" #include #include @@ -13,13 +14,11 @@ namespace duckdb { static idx_t NextNeedle(const char *input_haystack, idx_t size_haystack, const char *input_needle, const idx_t size_needle) { // Needle needs something to proceed - if (size_needle > 0) { - // Haystack should be bigger or equal size to the needle - for (idx_t string_position = 0; (size_haystack - string_position) >= size_needle; ++string_position) { - // Compare Needle to the Haystack - if ((memcmp(input_haystack + string_position, input_needle, size_needle) == 0)) { - return string_position; - } + if (size_needle > 0 && size_haystack > 0) { + auto position = FindStrInStr(const_uchar_ptr_cast(input_haystack), size_haystack, + const_uchar_ptr_cast(input_needle), size_needle); + if (position != DConstants::INVALID_INDEX) { + return position; } } // Did not find the needle diff --git a/src/duckdb/extension/icu/collation/collation_collator.cpp b/src/duckdb/extension/icu/collation/collation_collator.cpp index 307eb7a3e..d52e5e530 100644 --- a/src/duckdb/extension/icu/collation/collation_collator.cpp +++ b/src/duckdb/extension/icu/collation/collation_collator.cpp @@ -85,6 +85,9 @@ static collation_element_t MakeElement(uint32_t primary, uint32_t secondary, uin //! Returns the position of a Han character in radical/stroke order, or false if it is not a Han character static bool GetHanIndex(const CollationRoot &root, uint32_t codepoint, uint32_t &result) { // only the ranges of the block the code point is in have to be searched + if (codepoint >= 0x110000) { + return false; + } uint32_t lower = root.han_block_lower[codepoint >> HAN_BLOCK_SHIFT]; uint32_t upper = root.han_block_upper[codepoint >> HAN_BLOCK_SHIFT]; while (lower < upper) { @@ -124,6 +127,9 @@ static collation_element_t ImplicitElement(const CollationRoot &root, uint32_t c } static uint32_t LookupValue(const CollationTable &table, uint32_t codepoint) { + if (codepoint >= 0x110000) { + return 0; + } auto block = table.trie_stage1[codepoint >> COLLATION_TRIE_SHIFT]; return table.trie_stage2[block + (codepoint & COLLATION_TRIE_MASK)]; } diff --git a/src/duckdb/extension/icu/collation/collation_normalizer.cpp b/src/duckdb/extension/icu/collation/collation_normalizer.cpp index 8a9412909..e354a96cd 100644 --- a/src/duckdb/extension/icu/collation/collation_normalizer.cpp +++ b/src/duckdb/extension/icu/collation/collation_normalizer.cpp @@ -66,7 +66,7 @@ uint32_t Normalizer::Decode(const char *data, idx_t size, vector &resu } codepoint = (codepoint << 6) | (trail & 0x3F); } - if (!valid) { + if (!valid || codepoint > 0x10FFFF) { result.push_back(REPLACEMENT_CHARACTER); pos++; continue; diff --git a/src/duckdb/extension/icu/collation/include/collation_collator.hpp b/src/duckdb/extension/icu/collation/include/collation_collator.hpp index c3639dc58..53ed16bc2 100644 --- a/src/duckdb/extension/icu/collation/include/collation_collator.hpp +++ b/src/duckdb/extension/icu/collation/include/collation_collator.hpp @@ -48,11 +48,19 @@ struct TailoredBlocks { void Build(const uint32_t *codepoints, uint32_t count); bool Contains(uint32_t codepoint) const { + if (codepoint >= 0x110000) { + return false; + } auto block = codepoint >> BITMAP_SHIFT; return (bitmap[block / 64] & (1ULL << (block % 64))) != 0; } //! The part of the tailoring that can hold the code point void GetRange(uint32_t codepoint, uint32_t &lower, uint32_t &upper) const { + if (codepoint >= 0x110000) { + lower = 0; + upper = 0; + return; + } lower = bounds[codepoint >> BOUND_SHIFT]; upper = bounds[(codepoint >> BOUND_SHIFT) + 1]; } diff --git a/src/duckdb/extension/icu/icu-datefunc.cpp b/src/duckdb/extension/icu/icu-datefunc.cpp index d839b22db..ab10a86c7 100644 --- a/src/duckdb/extension/icu/icu-datefunc.cpp +++ b/src/duckdb/extension/icu/icu-datefunc.cpp @@ -78,6 +78,17 @@ Calendar *ICUDateFunc::CalendarCacheState::GetCalendar(const string_t &tz_id) { return entry.calendar.get(); } +Calendar *ICUDateFunc::CalendarCacheState::TryGetCalendar(const string_t &tz_id, string *error_message) { + auto &entry = GetEntry(tz_id); + if (!entry.calendar) { + if (error_message) { + *error_message = entry.error; + } + return nullptr; + } + return entry.calendar.get(); +} + void ICUDateFunc::CalendarCacheState::SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message) { auto &entry = GetEntry(tz_id); if (!entry.tz) { diff --git a/src/duckdb/extension/icu/icu-datepart.cpp b/src/duckdb/extension/icu/icu-datepart.cpp index 3b1caae00..8ac85ecae 100644 --- a/src/duckdb/extension/icu/icu-datepart.cpp +++ b/src/duckdb/extension/icu/icu-datepart.cpp @@ -677,7 +677,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { // register the individual operators // year/decade use CAL_YEAR (year-of-era, positive in both BC and AD), which is non-monotonic - // across the BC/AD flip; leave them unannotated. era/century/millennium/isoyear are signed. + // across the BC/AD flip; leave them unannotated. century/millennium are negated in era 0, which is the only era + // of some calendars, and yearweek decreases within negative years, so these are unannotated as well. // BIGINTs ICUDatePart::AddUnaryPartCodeFunctions("era", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); @@ -685,8 +686,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("month", loader); ICUDatePart::AddUnaryPartCodeFunctions("day", loader); ICUDatePart::AddUnaryPartCodeFunctions("decade", loader); - ICUDatePart::AddUnaryPartCodeFunctions("century", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); - ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); + ICUDatePart::AddUnaryPartCodeFunctions("century", loader); + ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader); ICUDatePart::AddUnaryPartCodeFunctions("microsecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("millisecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("second", loader); @@ -707,8 +708,7 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("julian", loader, LogicalType::DOUBLE); // register combinations - ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader, LogicalType::BIGINT, - ArgProperties().NonDecreasing()); // ISO year and week + ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader); // ISO year and week // register various aliases ICUDatePart::AddUnaryPartCodeFunctions("dayofmonth", loader); diff --git a/src/duckdb/extension/icu/icu-datesub.cpp b/src/duckdb/extension/icu/icu-datesub.cpp index d80be2ce8..1c8d48541 100644 --- a/src/duckdb/extension/icu/icu-datesub.cpp +++ b/src/duckdb/extension/icu/icu-datesub.cpp @@ -209,6 +209,20 @@ ICUDateFunc::part_sub_t ICUDateFunc::SubtractFactory(DatePartSpecifier type) { // MS-SQL differences can be computed using ICU by truncating both arguments // to the desired part precision and then applying ICU subtraction/difference struct ICUCalendarDiff : public ICUDateFunc { + static unique_ptr Bind(BindScalarFunctionInput &input) { + auto part_value = input.TryGetConstant(0); + if (part_value && !part_value->IsNull()) { + DatePartSpecifier part; + if (TryGetDatePartSpecifier(part_value->GetValue(), part) && + (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM)) { + // date_diff is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary + input.GetBoundFunction().SetArgProperties({}); + } + } + return ICUDateFunc::Bind(input); + } + static int64_t DifferenceEra(Calendar *calendar, timestamp_tz_t start_date, timestamp_tz_t end_date) { SetTime(calendar, start_date); const auto start_era = ExtractField(calendar, CAL_ERA); diff --git a/src/duckdb/extension/icu/icu-datetrunc.cpp b/src/duckdb/extension/icu/icu-datetrunc.cpp index 13562cc85..2e980012f 100644 --- a/src/duckdb/extension/icu/icu-datetrunc.cpp +++ b/src/duckdb/extension/icu/icu-datetrunc.cpp @@ -1,6 +1,7 @@ #include "include/icu-datetrunc.hpp" #include "include/icu-datefunc.hpp" +#include "duckdb/common/enums/date_part_specifier.hpp" #include "duckdb/common/vector_operations/binary_executor.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/main/extension/extension_loader.hpp" @@ -9,6 +10,20 @@ namespace duckdb { struct ICUDateTrunc : public ICUDateFunc { + static unique_ptr Bind(BindScalarFunctionInput &input) { + auto part_value = input.TryGetConstant(0); + if (part_value && !part_value->IsNull()) { + DatePartSpecifier part; + if (TryGetDatePartSpecifier(part_value->GetValue(), part) && + (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM)) { + // date_trunc is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary + input.GetBoundFunction().SetArgProperties({}); + } + } + return ICUDateFunc::Bind(input); + } + static void PreserveOffsets(Calendar *calendar) { // We have to extract _everything_ before setting anything // Otherwise ICU will clear the fStamp fields diff --git a/src/duckdb/extension/icu/icu-makedate.cpp b/src/duckdb/extension/icu/icu-makedate.cpp index 058355bc2..b41f54893 100644 --- a/src/duckdb/extension/icu/icu-makedate.cpp +++ b/src/duckdb/extension/icu/icu-makedate.cpp @@ -62,8 +62,10 @@ bool ICUMakeDate::CastNsToDate(Vector &source, Vector &result, idx_t count, Cast } // the date only needs the microsecond instant: floor the nanoseconds so no instant moves past midnight const auto nanos = input.value; - const auto micros = nanos >= 0 ? nanos / Interval::NANOS_PER_MICRO - : (nanos - (Interval::NANOS_PER_MICRO - 1)) / Interval::NANOS_PER_MICRO; + auto micros = nanos / Interval::NANOS_PER_MICRO; + if (nanos % Interval::NANOS_PER_MICRO < 0) { + micros--; + } return Operation(calendar.get(), timestamp_tz_t(micros)); }); return true; diff --git a/src/duckdb/extension/icu/icu-strptime.cpp b/src/duckdb/extension/icu/icu-strptime.cpp index 60c2b8b91..e53514780 100644 --- a/src/duckdb/extension/icu/icu-strptime.cpp +++ b/src/duckdb/extension/icu/icu-strptime.cpp @@ -143,12 +143,9 @@ struct ICUStrptime : public ICUDateFunc { result = timestamp_tz_t(parsed.ToTimestamp()); return; } else { - // Set TZ first, if any. - if (!parsed.tz.empty()) { - cache.SetTimeZone(calendar, parsed.tz); - } - - result = GetTime(calendar, ToMicros(calendar, parsed, format)); + // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone + auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); + result = GetTime(row_calendar, ToMicros(row_calendar, parsed, format)); return; } } @@ -166,11 +163,9 @@ struct ICUStrptime : public ICUDateFunc { result.value = parsed.ToTimestamp().value; return; } else { - // Set TZ first, if any. - if (!parsed.tz.empty()) { - cache.SetTimeZone(calendar, parsed.tz); - } - result = GetTimeNS(calendar, ToNanos(calendar, parsed, format)); + // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone + auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); + result = GetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format)); return; } } @@ -207,8 +202,9 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { - if (TryGetTime(calendar, ToMicros(calendar, parsed, format), result)) { + } else { + auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); + if (row_calendar && TryGetTime(row_calendar, ToMicros(row_calendar, parsed, format), result)) { return true; } } @@ -226,8 +222,9 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { - if (TryGetTimeNS(calendar, ToNanos(calendar, parsed, format), result)) { + } else { + auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); + if (row_calendar && TryGetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format), result)) { return true; } } @@ -403,18 +400,24 @@ struct ICUStrptime : public ICUDateFunc { // Convert parts to a TZ (default or parsed) if no offset was provided auto calendar = cal.get(); - // Change TZ if one was provided. + // Use the calendar of the time zone if one was provided - the shared calendar keeps the session zone if (tz.GetSize()) { string error_msg; - cache.SetTimeZone(calendar, tz, &error_msg); - if (!error_msg.empty()) { + calendar = cache.TryGetCalendar(tz, &error_msg); + if (!calendar) { HandleCastError::AssignError(error_msg, parameters); return nullopt; } } // Now get the parts in the given time zone - return FromNaive(calendar, us); + timestamp_tz_t result; + string error_msg; + if (!TryFromNaive(calendar, us, result, error_msg)) { + HandleCastError::AssignError(error_msg, parameters); + return nullopt; + } + return result; } return timestamp_tz_t(us); diff --git a/src/duckdb/extension/icu/icu-timebucket.cpp b/src/duckdb/extension/icu/icu-timebucket.cpp index 0a9586ab1..495ead083 100644 --- a/src/duckdb/extension/icu/icu-timebucket.cpp +++ b/src/duckdb/extension/icu/icu-timebucket.cpp @@ -73,6 +73,25 @@ struct ICUTimeBucket : public ICUDateFunc { return Add(calendar, origin, interval_t {0, 0, result_micros}); } + //! Add resolves a local time that occurs twice (e.g. when the clocks are turned back) to its latter occurrence. + //! If that is after ts, ts lies between the two occurrences, and its bucket starts at the former occurrence. + static timestamp_tz_t FormerOccurrence(Calendar *calendar, const timestamp_tz_t bucket, const timestamp_tz_t ts) { + auto get_offset = [&](const timestamp_tz_t instant) { + SetTime(calendar, instant); + return int64_t(ExtractField(calendar, CAL_ZONE_OFFSET)) + ExtractField(calendar, CAL_DST_OFFSET); + }; + const auto ts_offset = get_offset(ts); + const auto bucket_offset = get_offset(bucket); + if (ts_offset <= bucket_offset) { + return bucket; + } + const timestamp_tz_t former(bucket.value - (ts_offset - bucket_offset) * Interval::MICROS_PER_MSEC); + if (ts < former || get_offset(former) != ts_offset) { + return bucket; + } + return former; + } + static inline timestamp_tz_t WidthConvertibleToDaysCommon(int32_t bucket_width_days, const timestamp_tz_t ts, const timestamp_tz_t origin, TZCalendar &calendar) { if (!bucket_width_days) { @@ -87,9 +106,10 @@ struct ICUTimeBucket : public ICUDateFunc { } auto bucket = Add(calendar, origin, interval_t {0, static_cast(result_days), 0}); if (ts < bucket) { - D_ASSERT(ts < origin); + bucket = FormerOccurrence(calendar.GetCalendar(), bucket, ts); + } + while (ts < bucket) { bucket = Add(calendar, bucket, interval_t {0, -bucket_width_days, 0}); - D_ASSERT(ts > bucket); } return bucket; @@ -123,7 +143,14 @@ struct ICUTimeBucket : public ICUDateFunc { SubtractOperatorOverflowCheck::Operation(result_months, bucket_width_months); } - return Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); + auto bucket = Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); + if (ts < bucket) { + bucket = FormerOccurrence(calendar, bucket, ts); + } + while (ts < bucket) { + bucket = Add(calendar_p, bucket, interval_t {-bucket_width_months, 0, 0}); + } + return bucket; } template diff --git a/src/duckdb/extension/icu/icu-timezone.cpp b/src/duckdb/extension/icu/icu-timezone.cpp index 2caa2ebcb..23dd0195e 100644 --- a/src/duckdb/extension/icu/icu-timezone.cpp +++ b/src/duckdb/extension/icu/icu-timezone.cpp @@ -708,6 +708,10 @@ timestamp_tz_t ICUDateFunc::FromNaive(Calendar *calendar, timestamp_t naive) { return ICUFromNaiveTimestamp::Operation(calendar, naive); } +bool ICUDateFunc::TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error) { + return ICUFromNaiveTimestamp::TryOperation(calendar, naive, result, error); +} + void RegisterICUTimeZoneFunctions(ExtensionLoader &loader) { // Table functions TableFunction tz_names("pg_timezone_names", {}, ICUTimeZoneFunction, ICUTimeZoneBind, ICUTimeZoneInit); diff --git a/src/duckdb/extension/icu/include/icu-datefunc.hpp b/src/duckdb/extension/icu/include/icu-datefunc.hpp index 04ad57f5b..c47777f8e 100644 --- a/src/duckdb/extension/icu/include/icu-datefunc.hpp +++ b/src/duckdb/extension/icu/include/icu-datefunc.hpp @@ -62,6 +62,8 @@ struct ICUDateFunc { //! Returns a calendar for the time zone. Throws for unknown zones. Calendar *GetCalendar(const string_t &tz_id); + //! Returns a calendar for the time zone, or nullptr for unknown zones (filling error_message if provided). + Calendar *TryGetCalendar(const string_t &tz_id, string *error_message = nullptr); //! Sets the time zone for the calendar. //! For unknown zones, fills error_message if provided and throws otherwise. void SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message = nullptr); @@ -122,6 +124,8 @@ struct ICUDateFunc { //! constructs an ICU timestamp, and then converts that back to a DuckDB instant //! Adding offset doesn't really work around DST because the bin values are ambiguous static timestamp_tz_t FromNaive(Calendar *calendar, timestamp_t naive); + //! Non-throwing form of FromNaive - sets the error and returns false if the result is out of range + static bool TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error); //! Truncates the calendar time to the given part precision typedef void (*part_trunc_t)(Calendar *calendar, uint64_t µs); diff --git a/src/duckdb/extension/json/json_functions/json_create.cpp b/src/duckdb/extension/json/json_functions/json_create.cpp index 4eb1fa82c..6117f0b89 100644 --- a/src/duckdb/extension/json/json_functions/json_create.cpp +++ b/src/duckdb/extension/json/json_functions/json_create.cpp @@ -80,9 +80,9 @@ enum class JSONGeometryFormat : uint8_t { WKT, GEOJSON }; struct JSONCopyFormatOptions { JSONGeometryFormat geometry_format = JSONGeometryFormat::WKT; - optional_ptr date_format; - optional_ptr timestamp_format; - optional_ptr context; + mutable optional_ptr date_format; + mutable optional_ptr timestamp_format; + mutable optional_ptr context; optional_ptr timestamptz_format_expression; optional_ptr timestamptz_ns_format_expression; }; @@ -862,7 +862,7 @@ static void CreateValuesFormatted(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V static void CreateValuesDate(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.date_format), [&](Vector &string_vector) { - options.date_format.get_mutable()->ConvertDateVector(value_v, string_vector); + options.date_format->ConvertDateVector(value_v, string_vector); }); } @@ -875,14 +875,14 @@ static void CreateValuesTimestamp(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V return; } CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format.get_mutable()->ConvertTimestampVector(value_v, string_vector); + options.timestamp_format->ConvertTimestampVector(value_v, string_vector); }); } static void CreateValuesTimestampNS(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format.get_mutable()->ConvertTimestampNSVector(value_v, string_vector); + options.timestamp_format->ConvertTimestampNSVector(value_v, string_vector); }); } @@ -892,7 +892,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], // no COPY format was given - use the full cast set so that the session time zone is applied Vector string_vector(LogicalType::VARCHAR, count); if (options.context) { - VectorOperations::Cast(*options.context.get_mutable(), value_v, string_vector, count, true); + VectorOperations::Cast(*options.context, value_v, string_vector, count, true); } else { VectorOperations::DefaultCast(value_v, string_vector, count); } @@ -910,7 +910,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], input.InitializeEmpty({value_v.GetType()}); input.data[0].Reference(value_v); input.CheckCardinality(count); - ExpressionExecutor executor(*options.context.get_mutable(), *format_expression); + ExpressionExecutor executor(*options.context, *format_expression); executor.ExecuteExpression(input, string_vector); }); } @@ -1516,6 +1516,8 @@ ScalarFunction JSONFunctions::GetJSONCopyToJSONFunction() { fun.GetSignature().GetParameter(1).SetName("date_format"); fun.GetSignature().GetParameter(2).SetName("timestamp_format"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // formatting dates and timestamps can fail + fun.SetFallible(); fun.SetSerializeCallback(JSONCopyToJSONSerialize); fun.SetDeserializeCallback(JSONCopyToJSONDeserialize); return fun; @@ -1534,6 +1536,7 @@ ScalarFunction JSONFunctions::GetJSONCopyToGeoJSONFunction() { fun.GetSignature().GetParameter(4).SetName("id_column"); fun.GetSignature().GetParameter(5).SetName("write_bbox"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/json/json_functions/json_transform.cpp b/src/duckdb/extension/json/json_functions/json_transform.cpp index 4c08be33d..ffa43977e 100644 --- a/src/duckdb/extension/json/json_functions/json_transform.cpp +++ b/src/duckdb/extension/json/json_functions/json_transform.cpp @@ -11,6 +11,7 @@ #include "duckdb/common/error_data.hpp" #include "duckdb/common/serializer/deserializer.hpp" #include "duckdb/common/types/geometry.hpp" +#include "duckdb/common/types/value_map.hpp" #include "json_geojson.hpp" #include "duckdb/common/serializer/serializer.hpp" #include "duckdb/common/types.hpp" @@ -44,7 +45,8 @@ static LogicalType StructureStringToTypeArray(yyjson_val *arr, ClientContext &co } static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context) { - unordered_set names; + // STRUCT field names are case-insensitive + identifier_set_t names; child_list_t child_types; size_t idx, max; yyjson_val *key, *val; @@ -54,10 +56,9 @@ static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context if (key_str.empty()) { throw BinderException("Empty key in object in JSON structure"); } - if (names.find(key_str) != names.end()) { + if (!names.insert(Identifier(key_str)).second) { JSONCommon::ThrowValFormatError("Duplicate keys in object in JSON structure: %s", val); } - names.insert(key_str); child_types.emplace_back(key_str, StructureStringToType(val, context)); } D_ASSERT(yyjson_obj_size(obj) == names.size()); @@ -922,6 +923,38 @@ static bool TransformObjectToMap(yyjson_val *objects[], yyjson_alc *alc, Vector success = false; } + // objects can have duplicate keys, and keys that fail to transform in non-strict mode are NULL + auto &map_keys = MapVector::GetKeys(result); + auto key_validity = map_keys.Validity(); + value_set_t unique_keys; + for (idx_t i = 0; i < count; i++) { + if (!list_validity.RowIsValid(i)) { + continue; + } + const auto &list_entry = list_entries[i]; + const char *invalid_reason = nullptr; + unique_keys.clear(); + for (idx_t key_idx = list_entry.offset; key_idx < list_entry.offset + list_entry.length; key_idx++) { + if (!key_validity.IsValid(key_idx)) { + invalid_reason = "Map keys can not be NULL"; + break; + } + if (!unique_keys.insert(map_keys.GetValue(key_idx)).second) { + invalid_reason = "Map keys must be unique"; + break; + } + } + if (!invalid_reason) { + continue; + } + list_validity.SetInvalid(i); + if (success && options.strict_cast) { + options.error_message = invalid_reason; + options.object_index = i; + success = false; + } + } + if (!options.delay_error && !success) { throw InvalidInputException(options.error_message); } diff --git a/src/duckdb/extension/json/json_reader.cpp b/src/duckdb/extension/json/json_reader.cpp index 8e32310db..1ecd229b1 100644 --- a/src/duckdb/extension/json/json_reader.cpp +++ b/src/duckdb/extension/json/json_reader.cpp @@ -771,7 +771,15 @@ bool JSONReader::ParseJSON(JSONReaderScanState &scan_state, char *const json_sta // If we return strings, we cannot parse INSITU. If we ignore errors, we cannot parse INSITU either: a malformed // document could extend into (and modify) the next one const bool ignorable_errors = options.ignore_errors && GetFormat() == JSONFormat::NEWLINE_DELIMITED; - if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors) { + // A number that does not fit is read as a raw string, which INSITU parsing null-terminates in place - at the root + // that overwrites the delimiter that follows the document + idx_t first_char = 0; + while (first_char < json_size && StringUtil::CharacterIsSpace(json_start[first_char])) { + first_char++; + } + const bool number_root = first_char < json_size && + (StringUtil::CharacterIsDigit(json_start[first_char]) || json_start[first_char] == '-'); + if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors || number_root) { doc = JSONCommon::ReadDocumentUnsafe(json_start, json_size, JSONCommon::READ_STOP_FLAG, scan_state.allocator.GetYYAlc(), &err); } else { diff --git a/src/duckdb/extension/parquet/column_reader.cpp b/src/duckdb/extension/parquet/column_reader.cpp index 453be1314..54519dfc2 100644 --- a/src/duckdb/extension/parquet/column_reader.cpp +++ b/src/duckdb/extension/parquet/column_reader.cpp @@ -250,6 +250,9 @@ void ColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_row chunk = &columns[ColumnIndex()]; protocol = &protocol_p; D_ASSERT(chunk); + if (chunk->__isset.crypto_metadata && !reader.metadata->crypto_metadata) { + throw InvalidInputException("File is encrypted but no file crypto metadata is set"); + } D_ASSERT(chunk->__isset.meta_data); if (chunk->__isset.file_path) { @@ -516,9 +519,6 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { uint32_t compressed_page_size = page_hdr.compressed_page_size; if (chunk->__isset.crypto_metadata) { - if (!reader.metadata->crypto_metadata) { - throw InvalidInputException("File is encrypted but no file crypto metadata is set"); - } auto const file_aad = reader.GetUniqueFileIdentifier(reader.metadata->crypto_metadata->encryption_algorithm); if (!file_aad.empty()) { // If there is a file aad (identifier), this means that the Encrypted file is written by Arrow @@ -550,6 +550,50 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { page_hdr.uncompressed_page_size); } +static bool TryDecompressLZ4Hadoop(const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { + // Hadoop framing stores a big-endian uncompressed block size, followed by one or more + // chunks, each prefixed by its big-endian compressed size. + while (src_size > 0) { + if (src_size < sizeof(uint32_t)) { + return false; + } + auto block_size = BSwapIfLE(Load(src)); + src += sizeof(uint32_t); + src_size -= sizeof(uint32_t); + if (block_size > dst_size) { + return false; + } + if (block_size == 0) { + // Hadoop uses a zero-sized block to terminate an empty stream. + return src_size == 0 && dst_size == 0; + } + idx_t remaining = block_size; + while (remaining > 0) { + if (src_size < sizeof(uint32_t)) { + return false; + } + auto compressed_size = BSwapIfLE(Load(src)); + src += sizeof(uint32_t); + src_size -= sizeof(uint32_t); + if (compressed_size == 0 || compressed_size > src_size) { + return false; + } + auto decompressed_size = + duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), + NumericCast(compressed_size), NumericCast(remaining)); + if (decompressed_size <= 0) { + return false; + } + src += compressed_size; + src_size -= compressed_size; + dst += decompressed_size; + dst_size -= decompressed_size; + remaining -= decompressed_size; + } + } + return dst_size == 0; +} + void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { switch (codec) { @@ -560,6 +604,12 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p s.Decompress(const_char_ptr_cast(src), src_size, char_ptr_cast(dst), dst_size); break; } + case CompressionCodec::LZ4: + if (TryDecompressLZ4Hadoop(src, src_size, dst, dst_size)) { + break; + } + // Early parquet-cpp writers used raw LZ4 under the legacy codec identifier. + DUCKDB_EXPLICIT_FALLTHROUGH; case CompressionCodec::LZ4_RAW: { auto res = duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), @@ -619,7 +669,7 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p duckdb::stringstream codec_name; codec_name << codec; throw InvalidInputException("Failed to read file \"%s\": Unsupported compression codec \"%s\". Supported " - "options are uncompressed, brotli, gzip, lz4_raw, snappy or zstd", + "options are uncompressed, brotli, gzip, lz4, lz4_raw, snappy or zstd", Reader().GetFileName(), codec_name.str()); } // LCOV_EXCL_STOP } diff --git a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp index 8c54b9243..74da3df82 100644 --- a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp +++ b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp @@ -45,35 +45,6 @@ bool IsContinuationByte(uint8_t b) { return (b & 0xC0) == 0x80; } -//! Whether a string starts or ends in the middle of a multi-byte character. These are the only strings that can be -//! invalid on their own when the batch they are part of is valid UTF-8 -bool CutsUtf8Character(const char *data, uint32_t len) { - if (len == 0) { - return false; - } - const auto bytes = const_data_ptr_cast(data); - if (IsContinuationByte(bytes[0])) { - return true; - } - // find the first byte of the last character - a character is at most 4 bytes - idx_t last_start = len - 1; - while (last_start > 0 && len - last_start < 4 && IsContinuationByte(bytes[last_start])) { - last_start--; - } - const auto first_byte = bytes[last_start]; - idx_t char_length; - if (first_byte < 0x80) { - char_length = 1; - } else if (first_byte >= 0xF0) { - char_length = 4; - } else if (first_byte >= 0xE0) { - char_length = 3; - } else { - char_length = 2; - } - return len - last_start != char_length; -} - void DeltaLengthByteArrayDecoder::Read(shared_ptr &block_ref, uint8_t *defines, idx_t read_count, Vector &result, idx_t result_offset) { if (defines) { @@ -109,6 +80,7 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo StringColumnReader::Utf8ValidationOption::STRICT_UTF8; const auto start_ptr = block.ptr; + auto current_ptr = start_ptr; auto result_data = FlatVector::Writer(result, read_count, result_offset); for (idx_t row_idx = 0; row_idx < read_count; row_idx++) { @@ -125,20 +97,23 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo length_idx, byte_array_count); } } - const auto &str_len = length_data[length_idx++]; + const auto str_len = length_data[length_idx++]; if (needs_full_individual_validation) { - auto verified = string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); + auto verified = string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); result_data.WriteValue(verified); } else { - if (CutsUtf8Character(char_ptr_cast(block.ptr), str_len)) { - string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); + // Batch validation below catches incomplete trailing characters. Within a valid batch, a character + // can only cross a string boundary if the next nonempty string starts with a continuation byte. + if (str_len > 0 && IsContinuationByte(current_ptr[0])) { + string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); } - result_data.WriteStringRef(string_t(char_ptr_cast(block.ptr), str_len)); + result_data.WriteStringRef(string_t(char_ptr_cast(current_ptr), str_len)); } - block.unsafe_inc(str_len); + current_ptr += str_len; } + block.unsafe_inc(current_ptr - start_ptr); if (!needs_full_individual_validation) { string_column_reader.VerifyString(char_ptr_cast(start_ptr), NumericCast(block.ptr - start_ptr)); } diff --git a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp index 775160215..6f71072e3 100644 --- a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp +++ b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp @@ -27,7 +27,7 @@ class DbpDecoder { miniblock_index(number_of_miniblocks_per_block - 1), list_of_bitwidths_of_miniblocks(nullptr), miniblock_offset(number_of_values_in_a_miniblock), unpacked_data_offset(BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE) { - if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && + if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && number_of_values_in_a_miniblock > 0 && number_of_values_in_a_miniblock % BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE == 0)) { throw InvalidInputException("Parquet file has invalid block sizes for DELTA_BINARY_PACKED"); } @@ -63,6 +63,9 @@ class DbpDecoder { if (miniblock_offset == number_of_values_in_a_miniblock) { return; } + if (miniblock_offset > number_of_values_in_a_miniblock) { + throw InvalidInputException("Parquet file has invalid miniblocks for DELTA_BINARY_PACKED"); + } auto data = make_unsafe_uniq_array(number_of_values_in_a_miniblock); GetBatchInternal(data_ptr_cast(data.get()), number_of_values_in_a_miniblock - miniblock_offset); } diff --git a/src/duckdb/extension/parquet/include/parquet_reader.hpp b/src/duckdb/extension/parquet/include/parquet_reader.hpp index 479ce7fb6..b7af194e4 100644 --- a/src/duckdb/extension/parquet/include/parquet_reader.hpp +++ b/src/duckdb/extension/parquet/include/parquet_reader.hpp @@ -475,6 +475,9 @@ class ParquetReader : public BaseFileReader { idx_t GetGroupOffset(ParquetReaderScanState &state); //! Group span is the distance between the min page offset and the max page offset plus the max page compressed size uint64_t GetGroupSpan(ParquetReaderScanState &state); + FilterPropagateResult CheckRowGroupFilterStatistics(ClientContext &context, ParquetReaderScanState &state, + ColumnReader &column_reader, const TableFilter &filter); + bool DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state); void PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t out_col_idx); //! Whole-group prefetch strategy. ParquetPrefetchStrategy WholeGroupPrefetch(ParquetReaderScanState &state, ThriftFileTransport &trans, diff --git a/src/duckdb/extension/parquet/parquet_extension.cpp b/src/duckdb/extension/parquet/parquet_extension.cpp index f14943469..fc01e0def 100644 --- a/src/duckdb/extension/parquet/parquet_extension.cpp +++ b/src/duckdb/extension/parquet/parquet_extension.cpp @@ -283,6 +283,9 @@ static unique_ptr ParquetWriteBind(ClientContext &context, CopyFun for (idx_t i = 0; i < values.size(); i++) { auto &value = values[i]; auto key = StructType::GetChildName(kv_struct_type, i); + if (value.IsNull()) { + throw BinderException("kv_metadata value for key %s cannot be NULL", key); + } // If the value is a blob, write the raw blob bytes // otherwise, cast to string if (value.type().id() == LogicalTypeId::BLOB) { diff --git a/src/duckdb/extension/parquet/parquet_metadata.cpp b/src/duckdb/extension/parquet/parquet_metadata.cpp index cb1da7910..3d8b00473 100644 --- a/src/duckdb/extension/parquet/parquet_metadata.cpp +++ b/src/duckdb/extension/parquet/parquet_metadata.cpp @@ -566,8 +566,12 @@ void ParquetRowGroupMetadataProcessor::ReadRow(vector> &output class ParquetSchemaProcessor : public ParquetMetadataFileProcessor { public: + void InitializeInternal(ClientContext &context, ParquetReader &reader) override; idx_t TotalRowCount(ParquetReader &reader) override; void ReadRow(vector> &output, idx_t row_idx, ParquetReader &reader) override; + +private: + vector element_types; }; template <> @@ -668,6 +672,25 @@ static Value ParquetLogicalTypeToString(const duckdb_parquet::LogicalType &type, return Value(); } +static void CollectElementTypes(const ParquetColumnSchema &schema, vector &element_types) { + // a group collapsed into its only child has the type of that child + for (auto &index : {schema.parent_schema_index, schema.schema_index}) { + if (index.IsValid() && index.GetIndex() < element_types.size() && element_types[index.GetIndex()].IsNull()) { + element_types[index.GetIndex()] = Value(schema.type.ToString()); + } + } + for (auto &child : schema.children) { + CollectElementTypes(child, element_types); + } +} + +void ParquetSchemaProcessor::InitializeInternal(ClientContext &context, ParquetReader &reader) { + element_types.assign(reader.GetFileMetadata()->schema.size(), Value()); + for (auto &column : reader.root_schema->children) { + CollectElementTypes(column, element_types); + } +} + idx_t ParquetSchemaProcessor::TotalRowCount(ParquetReader &reader) { return reader.GetFileMetadata()->schema.size(); } @@ -699,12 +722,7 @@ void ParquetSchemaProcessor::ReadRow(vector> &output, idx_t ro // logical_type output[10].get().Append(ParquetLogicalTypeToString(column.logicalType, column.__isset.logicalType)); // duckdb_type - ParquetColumnSchema column_schema; - Value duckdb_type; - if (column.__isset.type) { - duckdb_type = reader.DeriveLogicalType(column, column_schema).ToString(); - } - output[11].get().Append(duckdb_type); + output[11].get().Append(element_types[row_idx]); // column_id output[12].get().Append(Value::BIGINT(UnsafeNumericCast(row_idx))); } diff --git a/src/duckdb/extension/parquet/parquet_reader.cpp b/src/duckdb/extension/parquet/parquet_reader.cpp index 808bce83c..ef68bc96d 100644 --- a/src/duckdb/extension/parquet/parquet_reader.cpp +++ b/src/duckdb/extension/parquet/parquet_reader.cpp @@ -593,6 +593,10 @@ LogicalType ParquetReader::DeriveLogicalType(const SchemaElement &s_ele, const P throw IOException("TIME_MICROS converted type can only be set for value of Type::INT64"); } case ConvertedType::INTERVAL: + if (s_ele.type != Type::FIXED_LEN_BYTE_ARRAY || s_ele.type_length != 12) { + throw DataCorruptionException( + "INTERVAL converted type can only be set for value of Type::FIXED_LEN_BYTE_ARRAY of length 12"); + } return LogicalType::INTERVAL; case ConvertedType::JSON: return LogicalType::JSON(); @@ -1041,11 +1045,20 @@ static bool IsVariantType(const SchemaElement &root, const vector MAX_PARQUET_SCHEMA_DEPTH) { + throw InvalidInputException( + "Malformed Parquet schema in file \"%s\": the schema is nested more than %llu levels " + "deep", + file.path, MAX_PARQUET_SCHEMA_DEPTH); + } if (next_schema_idx >= file_meta_data->schema.size()) { throw InvalidInputException("Malformed Parquet schema in file \"%s\": invalid schema index %d", file.path, next_schema_idx); @@ -1145,6 +1158,24 @@ ParquetColumnSchema ParquetReader::ParseSchemaRecursive(idx_t depth, idx_t max_d if (!is_variant && parquet_options.variant_legacy_encoding && IsVariantType(s_ele, child_schemas)) { is_variant = true; } + if (is_variant) { + // the VARIANT reader reads the 'metadata' and 'value' columns as binary + for (auto &child_schema : child_schemas) { + if (child_schema.name != "metadata" && child_schema.name != "value") { + continue; + } + // a logical type annotation (e.g. GEOMETRY) would make the column reader produce another type + const auto is_binary = + child_schema.type.id() == LogicalTypeId::BLOB || child_schema.type.id() == LogicalTypeId::VARCHAR; + if (!child_schema.children.empty() || child_schema.parquet_type != duckdb_parquet::Type::BYTE_ARRAY || + !is_binary || child_schema.schema_type != ParquetColumnSchemaType::COLUMN) { + throw DataCorruptionException( + "Failed to read Parquet file \"%s\": the '%s' column of VARIANT column \"%s\" " + "must be a binary BYTE_ARRAY column", + file.path, child_schema.name, s_ele.name); + } + } + } if (!is_map_kv && this_idx > 0) { // check if the parent node of this is a map @@ -1770,6 +1801,55 @@ ColumnReader &ParquetReaderScanState::GetColumnReader(idx_t i) { return *column_readers[i]; } +FilterPropagateResult ParquetReader::CheckRowGroupFilterStatistics(ClientContext &context, + ParquetReaderScanState &state, + ColumnReader &column_reader, + const TableFilter &filter) { + auto &group = GetGroup(state); + auto schema_column_index = column_reader.ColumnIndex(); + auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; + bool is_generated_column = schema_column_index >= group.columns.size(); + bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; + auto stats = column_reader.Stats(state.group_index, group.columns); + if (stats) { + bool has_min_max = false; + if (!is_generated_column) { + has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && + group.columns[schema_column_index].meta_data.statistics.__isset.max_value; + } + if (!is_expression && !is_generated_column && has_min_max && + (column_reader.Type().id() == LogicalTypeId::FLOAT || column_reader.Type().id() == LogicalTypeId::DOUBLE) && + ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, + parquet_options.can_have_nan)) { + // floating point columns can have NaN values in addition to the min/max bounds defined in the file + // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning + // based on nan + prune_result = CheckParquetFloatFilter(context, column_reader, + group.columns[schema_column_index].meta_data.statistics, filter); + } else { + auto &expr_filter = + ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::CheckRowGroupFilterStatistics"); + prune_result = expr_filter.CheckStatistics(context, *stats); + } + } + return prune_result; +} + +bool ParquetReader::DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state) { + for (auto &scan_filter : state.scan_filters) { + if (!ExpressionFilter::GetOptionalDynamicFilterData(scan_filter.filter)) { + continue; + } + auto &column_reader = state.GetColumnReader(MultiFileLocalIndex(scan_filter.filter_idx)); + auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, scan_filter.filter); + if (prune_result == FilterPropagateResult::FILTER_ALWAYS_FALSE || + prune_result == FilterPropagateResult::FILTER_FALSE_OR_NULL) { + return true; + } + } + return false; +} + void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t i) { auto &group = GetGroup(state); auto col_idx = MultiFileLocalIndex(i); @@ -1794,33 +1874,7 @@ void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderS if (filter_entry) { auto &filter = *filter_entry; - auto schema_column_index = column_reader.ColumnIndex(); - auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; - bool is_generated_column = schema_column_index >= group.columns.size(); - bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; - auto stats = column_reader.Stats(state.group_index, group.columns); - if (stats) { - bool has_min_max = false; - if (!is_generated_column) { - has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && - group.columns[schema_column_index].meta_data.statistics.__isset.max_value; - } - if (!is_expression && !is_generated_column && has_min_max && - (column_reader.Type().id() == LogicalTypeId::FLOAT || - column_reader.Type().id() == LogicalTypeId::DOUBLE) && - ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, - parquet_options.can_have_nan)) { - // floating point columns can have NaN values in addition to the min/max bounds defined in the file - // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning - // based on nan - prune_result = CheckParquetFloatFilter( - context, column_reader, group.columns[schema_column_index].meta_data.statistics, filter); - } else { - auto &expr_filter = - ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::PrepareRowGroupBuffer"); - prune_result = expr_filter.CheckStatistics(context, *stats); - } - } + auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, filter); // check the bloom filter if present auto bloom_source = TryGetBloomFilterSource(column_reader); if (bloom_source) { @@ -2445,6 +2499,11 @@ AsyncResult ParquetReader::Process(ClientContext &context, ParquetReaderScanStat const bool log_prefetch = Logger::Get(context).ShouldLog(ParquetPrefetchLogType::NAME, ParquetPrefetchLogType::LEVEL); const idx_t group_num_rows = GetGroup(state).num_rows; + // Queued row groups may predate the current dynamic filter. Do not revisit a partially decoded chunk. + if (state.offset_in_group == 0 && !state.resuming_payload && DynamicFiltersExcludeRowGroup(context, state)) { + rows_read += group_num_rows; + state.offset_in_group = group_num_rows; + } auto scan_count = MinValue(STANDARD_VECTOR_SIZE, group_num_rows - state.offset_in_group); if (!state.resuming_payload) { result.SetChildCardinality(scan_count); diff --git a/src/duckdb/extension/parquet/parquet_shredding.cpp b/src/duckdb/extension/parquet/parquet_shredding.cpp index aacbacab7..903a03020 100644 --- a/src/duckdb/extension/parquet/parquet_shredding.cpp +++ b/src/duckdb/extension/parquet/parquet_shredding.cpp @@ -72,6 +72,11 @@ static ShreddingType ConvertShreddingTypeRecursive(const LogicalType &type) { return ShreddingType(LogicalType(LogicalTypeId::ANY)); } if (!type.IsNested()) { + // the values are written without further conversion or validation, so types with an alias (e.g., JSON) and + // types without a Parquet typed_value representation cannot be shredded on + if (type.HasAlias() || type.id() == LogicalTypeId::BIT || type.id() == LogicalTypeId::BIGNUM) { + throw BinderException("VARIANT cannot be shredded on type %s in Parquet", type.ToString()); + } return ShreddingType(type); } diff --git a/src/duckdb/extension/parquet/read_single_parquet_file.cpp b/src/duckdb/extension/parquet/read_single_parquet_file.cpp index 2739185de..d88450a5c 100644 --- a/src/duckdb/extension/parquet/read_single_parquet_file.cpp +++ b/src/duckdb/extension/parquet/read_single_parquet_file.cpp @@ -37,6 +37,20 @@ struct ReadSingleParquetFileData : public TableFunctionData { } } + unique_ptr Copy() const override { + // the copy does not take over the reader the bind opened - its scans open the file themselves + auto result = make_uniq(); + result->file = file; + result->options = options; + result->parquet_names = parquet_names; + result->parquet_types = parquet_types; + result->columns = columns; + result->metadata = metadata; + result->cardinality = cardinality; + result->projection_expressions = projection_expressions; + return std::move(result); + } + //! The reader the bind opened for this file, handed to the first scan of this bind data so that the file does //! not have to be opened again. Only set when the bind data is used to read the file shared_ptr TakeBindReader() const { @@ -397,6 +411,12 @@ static virtual_column_map_t ReadSingleParquetFileVirtualColumns(ClientContext &c return result; } +//! The row number within the file identifies a row of a single file +static vector ReadSingleParquetFileRowIdColumns(ClientContext &context, + optional_ptr bind_data) { + return {MultiFileReader::COLUMN_IDENTIFIER_FILE_ROW_NUMBER}; +} + //! The statistics of a column of this file, read from its metadata static unique_ptr ReadSingleParquetFileStatistics(ClientContext &context, const FunctionData *bind_data_p, column_t column_index) { @@ -530,6 +550,7 @@ TableFunction ParquetScanFunction::GetSingleFileFunction() { read_parquet.get_metrics = ReadSingleParquetFileGetMetrics; read_parquet.projection_pushdown = true; read_parquet.late_materialization = true; + read_parquet.get_row_id_columns = ReadSingleParquetFileRowIdColumns; read_parquet.filter_pushdown = true; read_parquet.filter_prune = true; ParquetScanFunction::AddNamedParameters(read_parquet); diff --git a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp index 3679ee03b..e7579b4ff 100644 --- a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp @@ -31,6 +31,15 @@ RowNumberColumnReader::RowNumberColumnReader(const ParquetReader &reader, const void RowNumberColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_rows, const vector &columns, TProtocol &protocol_p) { + // the row numbers are produced from the row count without reading a column chunk - every column chunk has a value + // (or NULL) per row, so it cannot have fewer values than the row group has rows + for (auto &column : columns) { + if (column.__isset.meta_data && column.meta_data.num_values < NumericCast(row_group_num_rows)) { + throw DataCorruptionException( + "Failed to read file \"%s\": metadata is corrupt. Column has %lld values but row group has %llu rows", + reader.GetFileName(), column.meta_data.num_values, row_group_num_rows); + } + } row_group_offset = 0; auto &row_groups = reader.GetFileMetadata()->row_groups; for (idx_t i = 0; i < row_group_idx_p; i++) { diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index 68959c2a5..b2956eb82 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha47600"; + descriptor->api_version = "v2.0.0-alpha48256"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha47600"; + descriptor->api_version = "v2.0.0-alpha48256"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha47600"; + descriptor->api_version = "v2.0.0-alpha48256"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha47600"; + descriptor->api_version = "v2.0.0-alpha48256"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp index fbccd7aea..a3233f0e5 100644 --- a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp @@ -281,6 +281,13 @@ unique_ptr DuckTableEntry::GetSample() { return storage->GetSample(); } +//! The index of the column name in a column reference of a table expression - the names before it qualify the table, +//! the names after it are struct fields +static idx_t ColumnNameIndex(const ColumnRefExpression &colref, const Identifier &table_name) { + auto &names = colref.ColumnNames(); + return names.size() > 1 && names[0] == table_name ? 1 : 0; +} + unique_ptr DuckTableEntry::AlterEntry(CatalogTransaction transaction, AlterInfo &info) { if (transaction.HasContext()) { return AlterEntry(transaction.GetContext(), info); @@ -329,6 +336,19 @@ unique_ptr DuckTableEntry::AlterEntry(ClientContext &context, Alte auto &rename_info = table_info.Cast(); auto copied_table = Copy(context); copied_table->name = rename_info.new_table_name; + // CHECK constraints can qualify columns with the table name + for (auto &constraint : copied_table->Cast().constraints) { + if (constraint->type != ConstraintType::CHECK) { + continue; + } + auto &check = constraint->Cast(); + ParsedExpressionIterator::VisitExpressionMutable( + *check.expression, [&](ColumnRefExpression &colref) { + if (ColumnNameIndex(colref, name) == 1) { + colref.ColumnNamesMutable()[0] = rename_info.new_table_name; + } + }); + } return copied_table; } case AlterTableType::ADD_COLUMN: { @@ -403,10 +423,11 @@ void DuckTableEntry::UndoAlter(ClientContext &context, AlterInfo &info) { } } -static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info) { +static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info, const Identifier &table_name) { ParsedExpressionIterator::VisitExpressionMutable(root_expr, [&](ColumnRefExpression &colref) { - if (colref.ColumnNames().back() == info.old_name) { - colref.ColumnNamesMutable().back() = info.new_name; + auto column_idx = ColumnNameIndex(colref, table_name); + if (colref.ColumnNames()[column_idx] == info.old_name) { + colref.ColumnNamesMutable()[column_idx] = info.new_name; } }); } @@ -449,7 +470,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re copy.SetName(info.new_name); } if (col.Generated() && column_dependency_manager.IsDependencyOf(col.Logical(), rename_idx)) { - RenameExpression(copy.GeneratedExpressionMutable(), info); + RenameExpression(copy.GeneratedExpressionMutable(), info, name); } create_info->columns.AddColumn(std::move(copy)); } @@ -462,7 +483,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re case ConstraintType::CHECK: { // CHECK constraint: need to rename column references that refer to the renamed column auto &check = copy->Cast(); - RenameExpression(*check.expression, info); + RenameExpression(*check.expression, info, name); break; } case ConstraintType::UNIQUE: { @@ -860,12 +881,9 @@ unique_ptr DuckTableEntry::RemoveColumn(ClientContext &context, Re if (!removed_columns.empty() && !info.cascade) { throw CatalogException("Cannot drop column: column is a dependency of 1 or more generated column(s)"); } - bool dropped_column_is_generated = false; + bool dropped_column_is_generated = columns.GetColumn(LogicalIndex(removed_index)).Generated(); for (auto &col : columns.Logical()) { if (col.Logical() == removed_index || removed_columns.count(col.Logical())) { - if (col.Generated()) { - dropped_column_is_generated = true; - } continue; } create_info->columns.AddColumn(col.Copy()); diff --git a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp index fec16d5d2..95c870b57 100644 --- a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp @@ -6,6 +6,7 @@ #include "duckdb/parser/parsed_data/create_sequence_info.hpp" #include "duckdb/catalog/dependency_manager.hpp" #include "duckdb/common/operator/add.hpp" +#include "duckdb/common/operator/subtract.hpp" #include "duckdb/transaction/duck_transaction.hpp" #include @@ -113,6 +114,22 @@ void SequenceCatalogEntry::ReplayValue(uint64_t v_usage_count, int64_t v_counter } } +void SequenceCatalogEntry::DeriveLastValue() { + lock_guard seqlock(lock); + if (data.last_value || data.usage_count == 0) { + return; + } + int64_t last_value; + if (!TrySubtractOperator::Operation(data.counter, data.increment, last_value)) { + return; + } + // a counter that wrapped around in a cycle does not tell us the last value + if (last_value < data.min_value || last_value > data.max_value) { + return; + } + data.last_value = last_value; +} + unique_ptr SequenceCatalogEntry::GetInfo() const { auto seq_data = GetData(); diff --git a/src/duckdb/src/common/adbc/adbc.cpp b/src/duckdb/src/common/adbc/adbc.cpp index babfa9002..fd05fc8f3 100644 --- a/src/duckdb/src/common/adbc/adbc.cpp +++ b/src/duckdb/src/common/adbc/adbc.cpp @@ -249,8 +249,7 @@ static AdbcStatusCode ExecuteArrow(duckdb::PreparedStatementWrapper &prepared, D duckdb::QueryParameters parameters(duckdb::make_shared_ptr(STANDARD_VECTOR_SIZE)); auto result = prepared.statement->Submit(prepared.values, parameters); if (!result->HasError() && !eager && - duckdb::StreamsByDefault(prepared.statement->GetStatementType(), result->GetStatementProperties(), - reads_to_the_end)) { + duckdb::StreamsByDefault(prepared.statement->GetStatementType(), *result, reads_to_the_end)) { out.stream = duckdb::make_uniq>(std::move(result)); CopySchema(out.stream->FormatState().Schema(), out.schema); return ADBC_STATUS_OK; @@ -1981,6 +1980,13 @@ AdbcStatusCode StatementExecuteSchema(struct AdbcStatement *statement, struct Ar wrapper->conn_wrapper->MaterializeStreams(); } + auto &prepared = *reinterpret_cast(wrapper->statement); + if (prepared.statement && !prepared.statement->HasError() && !prepared.statement->MetadataAvailable()) { + SetError(error, "The result schema of this statement is known only once it is executed: it expands into " + "several statements, or the types of its parameters are not known yet"); + return ADBC_STATUS_NOT_IMPLEMENTED; + } + auto count = duckdb_prepared_statement_column_count(wrapper->statement); std::vector types(count); std::vector owned_names; diff --git a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp index 74e25516b..c146cfb41 100644 --- a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp +++ b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp @@ -75,6 +75,23 @@ optional_idx Allocator::DecayDelay() { return NumericCast(DUCKDB_JEMALLOC_DECAY); } +optional_idx Allocator::GetMappedMemory() const { + if (allocate_function != DefaultAllocate || free_function != DefaultFree || + reallocate_function != DefaultReallocate) { + return optional_idx(); + } + uint64_t epoch = 1; + if (duckdb_je_mallctl("epoch", nullptr, nullptr, &epoch, sizeof(epoch)) != 0) { + return optional_idx(); + } + size_t mapped = 0; + size_t mapped_size = sizeof(mapped); + if (duckdb_je_mallctl("stats.mapped", &mapped, &mapped_size, nullptr, 0) != 0) { + return optional_idx(); + } + return optional_idx(mapped); +} + bool Allocator::SupportsFlush() { return true; } diff --git a/src/duckdb/src/common/allocator/allocator_standard.cpp b/src/duckdb/src/common/allocator/allocator_standard.cpp index da6a959c8..d74816ed1 100644 --- a/src/duckdb/src/common/allocator/allocator_standard.cpp +++ b/src/duckdb/src/common/allocator/allocator_standard.cpp @@ -37,6 +37,10 @@ optional_idx Allocator::DecayDelay() { return optional_idx(); } +optional_idx Allocator::GetMappedMemory() const { + return optional_idx(); +} + bool Allocator::SupportsFlush() { #if defined(__GLIBC__) return true; diff --git a/src/duckdb/src/common/bind_helpers.cpp b/src/duckdb/src/common/bind_helpers.cpp index c20b8bbe2..74bfde8da 100644 --- a/src/duckdb/src/common/bind_helpers.cpp +++ b/src/duckdb/src/common/bind_helpers.cpp @@ -9,6 +9,7 @@ #include "duckdb/planner/binder.hpp" #include "duckdb/planner/bound_result_modifier.hpp" #include "duckdb/planner/expression_iterator.hpp" +#include "duckdb/planner/expression_binder/relation_binder.hpp" #include "duckdb/planner/expression/bound_reference_expression.hpp" #include "duckdb/main/config.hpp" @@ -139,10 +140,12 @@ vector BindOrderByNodes(Binder &binder, TableIndex table_index vector &orders) { auto child_binder = Binder::CreateBinder(binder.context, &binder); child_binder->bind_context.AddGenericBinding(table_index, alias, names, types); - ExpressionBinder expr_binder(*child_binder, binder.context); + RelationBinder expr_binder(*child_binder, binder.context, "ORDER_BY"); vector bound_orders; for (auto &order : orders) { - bound_orders.emplace_back(order.type, order.null_order, expr_binder.Bind(order.expression)); + auto expression = expr_binder.Bind(order.expression); + ExpressionBinder::PushCollation(binder.context, expression, expression->GetReturnType()); + bound_orders.emplace_back(order.type, order.null_order, std::move(expression)); } return bound_orders; } diff --git a/src/duckdb/src/common/enum_util.cpp b/src/duckdb/src/common/enum_util.cpp index 588815540..3c57cc018 100644 --- a/src/duckdb/src/common/enum_util.cpp +++ b/src/duckdb/src/common/enum_util.cpp @@ -163,6 +163,7 @@ #include "duckdb/main/extension.hpp" #include "duckdb/main/extension_helper.hpp" #include "duckdb/main/extension_install_info.hpp" +#include "duckdb/main/http/http_retry_budget.hpp" #include "duckdb/main/http/http_util.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/secret/secret.hpp" @@ -2279,19 +2280,20 @@ const StringUtil::EnumStringLiteral *GetExpressionTailTypeValues() { { static_cast(ExpressionTailType::IS_TEST), "IS_TEST" }, { static_cast(ExpressionTailType::DISTINCT), "DISTINCT" }, { static_cast(ExpressionTailType::COMPARISON), "COMPARISON" }, - { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" } + { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" }, + { static_cast(ExpressionTailType::CAST), "CAST" } }; return values; } template<> const char* EnumUtil::ToChars(ExpressionTailType value) { - return StringUtil::EnumToString(GetExpressionTailTypeValues(), 4, "ExpressionTailType", static_cast(value)); + return StringUtil::EnumToString(GetExpressionTailTypeValues(), 5, "ExpressionTailType", static_cast(value)); } template<> ExpressionTailType EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 4, "ExpressionTailType", value)); + return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 5, "ExpressionTailType", value)); } const StringUtil::EnumStringLiteral *GetExpressionTypeValues() { @@ -2781,19 +2783,20 @@ const StringUtil::EnumStringLiteral *GetFunctionCollationHandlingValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(FunctionCollationHandling::PROPAGATE_COLLATIONS), "PROPAGATE_COLLATIONS" }, { static_cast(FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS), "PUSH_COMBINABLE_COLLATIONS" }, - { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" } + { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" }, + { static_cast(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS), "PUSH_DISTINCT_COLLATIONS" } }; return values; } template<> const char* EnumUtil::ToChars(FunctionCollationHandling value) { - return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", static_cast(value)); + return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", static_cast(value)); } template<> FunctionCollationHandling EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", value)); + return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", value)); } const StringUtil::EnumStringLiteral *GetFunctionErrorsValues() { @@ -3037,6 +3040,24 @@ HTTPClientCachePolicy EnumUtil::FromString(const char *va return static_cast(StringUtil::StringToEnum(GetHTTPClientCachePolicyValues(), 2, "HTTPClientCachePolicy", value)); } +const StringUtil::EnumStringLiteral *GetHTTPRetryOutcomeOwnerValues() { + static constexpr StringUtil::EnumStringLiteral values[] { + { static_cast(HTTPRetryOutcomeOwner::STATUS), "STATUS" }, + { static_cast(HTTPRetryOutcomeOwner::COORDINATOR), "COORDINATOR" } + }; + return values; +} + +template<> +const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value) { + return StringUtil::EnumToString(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", static_cast(value)); +} + +template<> +HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value) { + return static_cast(StringUtil::StringToEnum(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", value)); +} + const StringUtil::EnumStringLiteral *GetHTTPStatusCodeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(HTTPStatusCode::INVALID), "INVALID" }, @@ -6492,19 +6513,20 @@ const StringUtil::EnumStringLiteral *GetSuggestionStateValues() { { static_cast(SuggestionState::SUGGEST_TABLE_FUNCTION_NAME), "SUGGEST_TABLE_FUNCTION_NAME" }, { static_cast(SuggestionState::SUGGEST_PRAGMA_NAME), "SUGGEST_PRAGMA_NAME" }, { static_cast(SuggestionState::SUGGEST_SETTING_NAME), "SUGGEST_SETTING_NAME" }, - { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" } + { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" }, + { static_cast(SuggestionState::SUGGEST_BARE_LABEL), "SUGGEST_BARE_LABEL" } }; return values; } template<> const char* EnumUtil::ToChars(SuggestionState value) { - return StringUtil::EnumToString(GetSuggestionStateValues(), 14, "SuggestionState", static_cast(value)); + return StringUtil::EnumToString(GetSuggestionStateValues(), 15, "SuggestionState", static_cast(value)); } template<> SuggestionState EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 14, "SuggestionState", value)); + return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 15, "SuggestionState", value)); } const StringUtil::EnumStringLiteral *GetTableColumnTypeValues() { diff --git a/src/duckdb/src/common/exception.cpp b/src/duckdb/src/common/exception.cpp index 185bfe4f3..3ca99bdfb 100644 --- a/src/duckdb/src/common/exception.cpp +++ b/src/duckdb/src/common/exception.cpp @@ -74,7 +74,6 @@ bool Exception::InvalidatesTransaction(ExceptionType exception_type) { bool Exception::InvalidatesDatabase(ExceptionType exception_type) { switch (exception_type) { - case ExceptionType::DATA_CORRUPTION: case ExceptionType::FATAL: return true; default: diff --git a/src/duckdb/src/common/file_buffer.cpp b/src/duckdb/src/common/file_buffer.cpp index f86e3fb40..642adcb5a 100644 --- a/src/duckdb/src/common/file_buffer.cpp +++ b/src/duckdb/src/common/file_buffer.cpp @@ -113,6 +113,12 @@ void FileBuffer::Resize(BlockManager &block_manager) { void FileBuffer::Read(QueryContext context, FileHandle &handle, uint64_t location) { D_ASSERT(type != FileBufferType::TINY_BUFFER); + if (handle.file_system.IsLocalFileSystem()) { + // Pre-fault local read buffers before the kernel fills them. + for (idx_t offset = 0; offset < internal_size; offset += 4096) { + internal_buffer[offset] = 0; + } + } handle.Read(context, internal_buffer, internal_size, location); } diff --git a/src/duckdb/src/common/gzip_file_system.cpp b/src/duckdb/src/common/gzip_file_system.cpp index cfd6eae5b..a154be2a1 100644 --- a/src/duckdb/src/common/gzip_file_system.cpp +++ b/src/duckdb/src/common/gzip_file_system.cpp @@ -238,8 +238,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { } state_bytes_remaining = NumericCast((uint8_t)gzip_xlen[0] | (uint8_t)gzip_xlen[1] << 8); if (gzip_header_size + state_bytes_remaining >= GZIP_HEADER_MAXSIZE) { - throw InternalException("Extra field resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw DataCorruptionException("Extra field resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } read_state = GZipReadState::EXTRA_DATA; break; @@ -265,8 +265,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { auto c = *sd.in_buff_start++; gzip_header_size++; if (gzip_header_size >= GZIP_HEADER_MAXSIZE) { - throw InternalException("Filename resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw DataCorruptionException("Filename resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } if (c == '\0') { read_state = GZipReadState::DEFLATE; diff --git a/src/duckdb/src/common/hive_partitioning.cpp b/src/duckdb/src/common/hive_partitioning.cpp index b52f1ddf6..c103ab2e9 100644 --- a/src/duckdb/src/common/hive_partitioning.cpp +++ b/src/duckdb/src/common/hive_partitioning.cpp @@ -29,7 +29,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f auto &column_map = filter_info.column_map; if (filter_info.filename_enabled) { - auto lookup_column_id = column_map.find("filename"); + auto lookup_column_id = column_map.find(filter_info.filename_column); if (lookup_column_id != column_map.end()) { result.insert(make_pair(lookup_column_id->second, PartitioningColumnValue(filename))); } @@ -38,7 +38,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f if (filter_info.hive_enabled) { auto partitions = HivePartitioning::Parse(filename); for (auto &partition : partitions) { - auto lookup_column_id = column_map.find(partition.first); + auto lookup_column_id = column_map.find(Identifier(partition.first)); if (lookup_column_id != column_map.end()) { result.insert( make_pair(lookup_column_id->second, PartitioningColumnValue(partition.first, partition.second))); diff --git a/src/duckdb/src/common/local_file_system.cpp b/src/duckdb/src/common/local_file_system.cpp index 87ba23c52..664659e76 100644 --- a/src/duckdb/src/common/local_file_system.cpp +++ b/src/duckdb/src/common/local_file_system.cpp @@ -1285,7 +1285,12 @@ unique_ptr LocalFileSystem::OpenFile(const string &path_p, FileOpenF HANDLE hFile = CreateFileW(unicode_path.c_str(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL); if (hFile == INVALID_HANDLE_VALUE) { - if (flags.ReturnNullIfNotExists() && GetLastError() == ERROR_FILE_NOT_FOUND) { + auto last_error = GetLastError(); + if (flags.ReturnNullIfNotExists() && last_error == ERROR_FILE_NOT_FOUND) { + return nullptr; + } + // Match POSIX EEXIST handling for exclusive creation. + if (flags.ReturnNullIfExists() && (last_error == ERROR_FILE_EXISTS || last_error == ERROR_ALREADY_EXISTS)) { return nullptr; } auto error = LocalFileSystem::GetLastErrorAsString(); diff --git a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp index 589923fcd..af5b3c272 100644 --- a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp @@ -934,16 +934,51 @@ static unique_ptr CreateStructExtractExpression(unique_ptrsecond->index.GetIndex() != i || + !(source_children[i].first == target_children[i].first) || + !MappedCastIsInvertible(*entry->second, source_children[i].second, target_children[i].second)) { + return false; + } + } + return true; +} + +static bool TryCastConstant(Value &constant, const MultiFileIndexMapping &mapping, const LogicalType &target_type) { + // this filter replaces the global one, so both casts must preserve which rows match + if (!BoundCastExpression::CastIsInvertible(constant.type(), target_type) || + !MappedCastIsInvertible(mapping, target_type, constant.type())) { return false; } auto cast = constant.DefaultTryCastAs(target_type); if (!cast) { return false; } + auto roundtrip = cast->DefaultTryCastAs(constant.type()); + if (!roundtrip || *roundtrip != constant) { + return false; + } constant = std::move(*cast); return true; } @@ -992,13 +1027,14 @@ static RewrittenMappedExpression RewriteMappedValueExpression(const Expression & } static unique_ptr RewriteDynamicFilterExpression(const shared_ptr &filter_data, + const MultiFileIndexMapping &mapping, const LogicalType &target_type) { if (!filter_data || !filter_data->initialized) { return nullptr; } - lock_guard lock(filter_data->lock); + annotated_shared_lock lock(filter_data->lock); auto new_constant = filter_data->constant; - if (!TryCastConstant(new_constant, target_type)) { + if (!TryCastConstant(new_constant, mapping, target_type)) { return nullptr; } return BoundComparisonExpression::Create(filter_data->comparison_type, CreateReferenceExpression(target_type), @@ -1031,7 +1067,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto &data = func.BindInfo()->Cast(); - return RewriteDynamicFilterExpression(data.filter_data, target_type); + return RewriteDynamicFilterExpression(data.filter_data, mapping, target_type); } if (!BoundComparisonExpression::IsComparison(expr)) { return nullptr; @@ -1047,7 +1083,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = right.Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { return nullptr; } return BoundComparisonExpression::Create(comparison.GetExpressionType(), std::move(lhs.expr), @@ -1096,7 +1132,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = op.GetChildren()[i]->Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { return nullptr; } result->GetChildrenMutable().push_back(make_uniq(std::move(constant))); diff --git a/src/duckdb/src/common/multi_file/multi_file_list.cpp b/src/duckdb/src/common/multi_file/multi_file_list.cpp index cbabe9939..38ecf87cb 100644 --- a/src/duckdb/src/common/multi_file/multi_file_list.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_list.cpp @@ -30,10 +30,11 @@ bool PushdownInternal(ClientContext &context, const MultiFileOptions &options, M if (IsVirtualColumn(info.column_ids[i])) { continue; } - filter_info.column_map.insert({info.column_names[info.column_ids[i]].GetIdentifierName(), i}); + filter_info.column_map.insert({info.column_names[info.column_ids[i]], i}); } filter_info.hive_enabled = options.hive_partitioning; filter_info.filename_enabled = options.filename; + filter_info.filename_column = Identifier(options.filename_column); auto start_files = expanded_files.size(); HivePartitioning::ApplyFiltersToFileList(context, expanded_files, filters, filter_info, info); diff --git a/src/duckdb/src/common/opener_file_system.cpp b/src/duckdb/src/common/opener_file_system.cpp index 5c3508a52..617947828 100644 --- a/src/duckdb/src/common/opener_file_system.cpp +++ b/src/duckdb/src/common/opener_file_system.cpp @@ -45,8 +45,7 @@ void OpenerFileSystem::VerifyCanAccessFileInternal(const string &path, FileType return; } auto &config = db->config; - auto canonical_path = config.file_system->CanonicalizePath(path, opener); - if (!config.CanAccessFile(canonical_path, type)) { + if (!config.CanAccessFile(path, type, opener)) { throw PermissionException("Cannot access %s \"%s\" - file system operations are disabled by configuration", type == FileType::FILE_TYPE_DIR ? "directory" : "file", path); } diff --git a/src/duckdb/src/common/operator/cast_operators.cpp b/src/duckdb/src/common/operator/cast_operators.cpp index c4282dc0c..95d7828e9 100644 --- a/src/duckdb/src/common/operator/cast_operators.cpp +++ b/src/duckdb/src/common/operator/cast_operators.cpp @@ -2187,9 +2187,9 @@ struct HugeIntCastData { idx_t digits; ResultType decimal; - uint16_t decimal_total_digits; + idx_t decimal_total_digits; ResultType decimal_intermediate; - uint16_t decimal_intermediate_digits; + idx_t decimal_intermediate_digits; bool Flush() { if (digits == 0 && intermediate == 0) { @@ -2319,7 +2319,7 @@ struct HugeIntegerCastOperation { remainder = negate_result; } state.decimal = remainder; - state.decimal_total_digits = static_cast(-e); + state.decimal_total_digits = static_cast(-e); state.decimal_intermediate = 0; state.decimal_intermediate_digits = 0; return Finalize(state); @@ -2338,16 +2338,19 @@ struct HugeIntegerCastOperation { return Finalize(state); } - e = exponent - state.decimal_total_digits; - if (e < 0) { - if (e < -38) { - return false; + const auto shift = static_cast(exponent) - static_cast(state.decimal_total_digits); + if (shift < 0) { + if (shift < -38) { + // all of decimal is fractional + remainder = state.decimal; + state.decimal = 0; + } else { + state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-shift], remainder); } - state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-e], remainder); - state.decimal_total_digits -= (exponent); + state.decimal_total_digits -= static_cast(exponent); } else { - if (e > 38 || - !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[e], state.decimal)) { + if (shift > 38 || + !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[shift], state.decimal)) { return false; } } @@ -2368,6 +2371,11 @@ struct HugeIntegerCastOperation { if (!state.Flush()) { return false; } + // digits past this position cannot affect the result for any int16_t exponent. Ignore them! + static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); + if (DUCKDB_UNLIKELY(state.decimal_total_digits + state.decimal_intermediate_digits == MAX_DECIMAL_DIGITS)) { + return true; + } if (DUCKDB_UNLIKELY(state.decimal_intermediate > (NumericLimits::Maximum() - digit) / 10)) { if (!state.FlushDecimal()) { return false; @@ -2395,7 +2403,7 @@ struct HugeIntegerCastOperation { state.decimal /= T::Operation::POWERS_OF_TEN[MAX_DIGITS]; state.decimal_total_digits -= MAX_DIGITS; } - D_ASSERT((state.decimal_total_digits - 1) >= 0 && (state.decimal_total_digits - 1) <= MAX_DIGITS); + D_ASSERT(state.decimal_total_digits >= 1 && state.decimal_total_digits - 1 <= MAX_DIGITS); state.decimal /= T::Operation::POWERS_OF_TEN[state.decimal_total_digits - 1]; if (state.decimal >= 5) { @@ -3315,6 +3323,7 @@ static void FillDecimalDigits(SRC input, duckdb_fast_float::decimal &decimal, bo } static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decimal, bool &negative) { + negative = false; if (input == 0) { return; } @@ -3322,8 +3331,6 @@ static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decim if (input < 0) { negative = true; Hugeint::NegateInPlace(input); - } else { - negative = false; } // a hugeint can have one more digit than the maximum decimal width @@ -3346,7 +3353,7 @@ bool TryCastDecimalToFloatingPoint(SRC input, DST &result, uint8_t width, uint8_ } duckdb_fast_float::decimal decimal; - bool negative; + bool negative = false; FillDecimalDigits(input, decimal, negative); decimal.decimal_point = UnsafeNumericCast(decimal.num_digits) - UnsafeNumericCast(scale); while (decimal.num_digits > 0 && decimal.digits[decimal.num_digits - 1] == 0) { diff --git a/src/duckdb/src/common/sort/sort.cpp b/src/duckdb/src/common/sort/sort.cpp index 234e0d94f..5bb566837 100644 --- a/src/duckdb/src/common/sort/sort.cpp +++ b/src/duckdb/src/common/sort/sort.cpp @@ -164,7 +164,7 @@ class SortGlobalSinkState : public GlobalSinkState { } // Double until it fits - auto required = num_threads * lstate.sorted_run->SizeInBytes(); + auto required = num_threads * lstate.sorted_run->SizeInBytesForSorting(); if (is_index_sort) { required *= 4; // Index creation is pretty intense, so we are very conservative here } @@ -225,8 +225,8 @@ unique_ptr Sort::GetGlobalSinkState(ClientContext &context) con //! Returns true if the Sink call is done (either because run size is small or because run was finalized) static bool TryFinishSink(SortGlobalSinkState &gstate, SortLocalSinkState &lstate, unique_lock &guard) { - // Check if we exceed the limit - const auto sorted_run_size = lstate.sorted_run->SizeInBytes(); + // Check if we exceed the limit (sorting the run needs a merge buffer on top of the run itself) + const auto sorted_run_size = lstate.sorted_run->SizeInBytesForSorting(); if (sorted_run_size < lstate.maximum_run_size) { return true; // Sink is done } diff --git a/src/duckdb/src/common/sort/sorted_run.cpp b/src/duckdb/src/common/sort/sorted_run.cpp index d72cc9328..f7eab31cf 100644 --- a/src/duckdb/src/common/sort/sorted_run.cpp +++ b/src/duckdb/src/common/sort/sorted_run.cpp @@ -8,6 +8,7 @@ #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/common/sorting/sort.hpp" #include "duckdb/common/sorting/sort_key.hpp" +#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "vergesort.h" @@ -247,10 +248,16 @@ static void TemplatedSort(ClientContext &context, TupleDataCollection &key_data, auto ska_extract_key = SkaExtractKey(requires_next_sort, ska_sort_width, sort_skippable_bytes, context.interrupt_state); - const auto fallback = [ska_extract_key](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { - duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key); + // vergesort merges the sorted runs it finds with a temporary buffer of up to half the keys - allocate that + // buffer through the buffer allocator instead of letting std::inplace_merge take it from operator new. The same + // buffer serves the merges of the nested vergesort that ska_sort runs over the buckets its radix passes cannot + // order, so that no merge anywhere in this sort allocates on its own (BufferedMerge never does) + SortMergeBuffer merge_buffer(BufferAllocator::Get(context), key_data.Count(), sizeof(SORT_KEY)); + const BufferedMergePolicy merge(merge_buffer); + const auto fallback = [&ska_extract_key, &merge](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { + duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key, merge); }; - duckdb_vergesort::vergesort(begin, end, std::less(), fallback); + duckdb_vergesort::vergesort(begin, end, std::less(), fallback, merge); context.InterruptCheck(); } @@ -447,4 +454,9 @@ idx_t SortedRun::SizeInBytes() const { return size; } +idx_t SortedRun::SizeInBytesForSorting() const { + // the fixed-size sort keys are the rows of the key layout, so the row width is the key size the merge buffer uses + return SizeInBytes() + SortMergeBuffer::MaximumSize(key_data->Count(), key_data->GetLayout().GetRowWidth()); +} + } // namespace duckdb diff --git a/src/duckdb/src/common/sort/sorted_run_merger.cpp b/src/duckdb/src/common/sort/sorted_run_merger.cpp index 2baecd162..1ddd7c6f0 100644 --- a/src/duckdb/src/common/sort/sorted_run_merger.cpp +++ b/src/duckdb/src/common/sort/sorted_run_merger.cpp @@ -6,6 +6,7 @@ #include "duckdb/common/thread_annotation.hpp" #include "duckdb/common/sorting/sorted_run.hpp" #include "duckdb/common/sorting/sort_key.hpp" +#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/parallel/parallel_destroy_task.hpp" @@ -655,8 +656,12 @@ void SortedRunMergerLocalState::TemplatedMergePartition(SortedRunMergerGlobalSta static const auto fallback = [](SORT_KEY *begin, SORT_KEY *end) { duckdb_pdqsort::pdqsort_branchless(begin, end); }; + // the run merges need a scratch buffer of up to half the keys - take it from the buffer allocator so that it is + // counted against the memory limit instead of coming from operator new inside std::inplace_merge + SortMergeBuffer merge_buffer(BufferAllocator::Get(gstate.context), merged_partition_count, sizeof(SORT_KEY)); + const BufferedMergePolicy merge(merge_buffer); duckdb_vergesort::vergesort(merged_partition_keys, merged_partition_keys + merged_partition_count, - std::less(), fallback); + std::less(), fallback, merge); } void SortedRunMergerLocalState::ScanPartition(SortedRunMergerGlobalState &gstate, DataChunk &chunk) { diff --git a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp index f60ab52bd..b8909899a 100644 --- a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp +++ b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp @@ -300,7 +300,7 @@ void HTMLTreeRenderer::RenderProfiler(const QueryProfiler &profiler, BaseTreeRen query_sql = HighlightSQL(FormatSQL(profiler.GetQuerySQL())); query_timings.clear(); for (auto &entry : qm.GetMetricTimings()) { - query_timings.emplace_back(entry.first, static_cast(entry.second) / 1e9); + query_timings.emplace_back(entry.first, static_cast(entry.second.duration) / 1e9); } has_query_metrics = true; profiler.RenderProfilingNodeTree(*this, ss); diff --git a/src/duckdb/src/common/types/bignum.cpp b/src/duckdb/src/common/types/bignum.cpp index 9f4da3623..862e959ae 100644 --- a/src/duckdb/src/common/types/bignum.cpp +++ b/src/duckdb/src/common/types/bignum.cpp @@ -101,29 +101,8 @@ char Bignum::DigitToChar(int digit) { } static bool ShouldRoundDecimalDigits(const char *data, idx_t decimal_start, idx_t decimal_end) { - if (decimal_start >= decimal_end) { - return false; - } - uint64_t decimal = 0; - uint16_t decimal_digits = 0; - for (idx_t pos = decimal_start; pos < decimal_end; pos++) { - auto digit = UnsafeNumericCast(data[pos] - '0'); - if (decimal > (NumericLimits::Maximum() - digit) / 10) { - for (; pos < decimal_end; pos++) { - if (data[pos] != '0') { - return true; - } - } - break; - } - decimal_digits++; - decimal = decimal * 10 + digit; - } - while (decimal > 10) { - decimal /= 10; - decimal_digits--; - } - return decimal_digits == 1 && decimal >= 5; + // the fraction is at least 0.5 exactly when its first digit is at least 5 + return decimal_start < decimal_end && data[decimal_start] >= '5'; } static void IncrementDecimalString(string &digits) { @@ -558,11 +537,8 @@ bool Bignum::BignumToDouble(const bignum_t &blob, double &result, bool &strict) if (is_negative) { result *= -1; } - if (!std::isfinite(result)) { - // We throw an error - throw ConversionException("Could not convert bignum '%s' to Double", BignumToVarchar(blob)); - } - return true; + // the value does not fit in a double + return std::isfinite(result); } } // namespace duckdb diff --git a/src/duckdb/src/common/types/geometry.cpp b/src/duckdb/src/common/types/geometry.cpp index 6c366b727..2e7539f77 100644 --- a/src/duckdb/src/common/types/geometry.cpp +++ b/src/duckdb/src/common/types/geometry.cpp @@ -1018,6 +1018,9 @@ WKBAnalysis AnalyzeWKB(BlobReader &reader) { const auto meta = reader.Read(le); const auto type_id = (meta & 0x0000FFFF) % 1000; const auto flag_id = (meta & 0x0000FFFF) / 1000; + if (flag_id > 3) { + throw InvalidInputException("Unsupported geometry flag %d in WKB", flag_id); + } // Extended WKB detection const auto has_extz = (meta & 0x80000000) != 0; diff --git a/src/duckdb/src/common/types/hugeint.cpp b/src/duckdb/src/common/types/hugeint.cpp index 79a353a6e..b98611ddb 100644 --- a/src/duckdb/src/common/types/hugeint.cpp +++ b/src/duckdb/src/common/types/hugeint.cpp @@ -89,42 +89,6 @@ hugeint_t Hugeint::Abs(hugeint_t n) { // Divide //===--------------------------------------------------------------------===// -static uint8_t PositiveHugeintHighestBit(hugeint_t bits) { - uint8_t out = 0; - if (bits.upper) { - out = 64; - uint64_t up = static_cast(bits.upper); - while (up) { - up >>= 1; - out++; - } - } else { - uint64_t low = bits.lower; - while (low) { - low >>= 1; - out++; - } - } - return out; -} - -static bool PositiveHugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { - if (bit_position < 64) { - return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); - } else { - return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); - } -} - -static hugeint_t PositiveHugeintLeftShift(hugeint_t lhs, uint32_t amount) { - D_ASSERT(amount > 0 && amount < 64); - hugeint_t result; - result.lower = lhs.lower << amount; - result.upper = - UnsafeNumericCast((UnsafeNumericCast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); - return result; -} - hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remainder) { D_ASSERT(lhs.upper >= 0); // DivMod code adapted from: @@ -136,14 +100,14 @@ hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remaind div_result.upper = 0; remainder = 0; - uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); + uint8_t highest_bit_set = HugeintHighestBit(lhs); // now iterate over the amount of bits that are set in the LHS for (uint8_t x = highest_bit_set; x > 0; x--) { // left-shift the current result and remainder by 1 - div_result = PositiveHugeintLeftShift(div_result, 1); + div_result = HugeintLeftShift(div_result, 1); remainder <<= 1; // we get the value of the bit at position X, where position 0 is the least-significant bit - if (PositiveHugeintIsBitSet(lhs, x - 1)) { + if (HugeintIsBitSet(lhs, x - 1)) { // increment the remainder remainder++; } @@ -303,172 +267,18 @@ bool Hugeint::TryMultiply(hugeint_t lhs, hugeint_t rhs, hugeint_t &result) { // Multiply without overflow check template <> hugeint_t Hugeint::Multiply(hugeint_t lhs, hugeint_t rhs) { - hugeint_t result; - bool lhs_negative = lhs.upper < 0; - bool rhs_negative = rhs.upper < 0; - if (lhs_negative) { - NegateInPlace(lhs); - } - if (rhs_negative) { - NegateInPlace(rhs); - } - -#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) - __uint128_t left = __uint128_t(lhs.lower) + (__uint128_t(lhs.upper) << 64); - __uint128_t right = __uint128_t(rhs.lower) + (__uint128_t(rhs.upper) << 64); - __uint128_t result_i128; - result_i128 = left * right; - uint64_t upper = uint64_t(result_i128 >> 64); - result.upper = int64_t(upper); - result.lower = uint64_t(result_i128 & 0xffffffffffffffff); -#else - // Multiply code adapted from: - // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - - // split values into 4 32-bit parts - uint64_t top[4] = {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, - lhs.lower & 0xffffffff}; - uint64_t bottom[4] = {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, - rhs.lower & 0xffffffff}; - uint64_t products[4][4]; - - // multiply each component of the values - for (auto x = 0; x < 4; x++) { - for (auto y = 0; y < 4; y++) { - products[x][y] = top[x] * bottom[y]; - } - } - - // first row - uint64_t fourth32 = (products[3][3] & 0xffffffff); - uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); - uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); - uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); - - // second row - third32 += (products[2][3] & 0xffffffff); - second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); - first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); - - // third row - second32 += (products[1][3] & 0xffffffff); - first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); - - // fourth row - first32 += (products[0][3] & 0xffffffff); - - // move carry to next digit - third32 += fourth32 >> 32; - second32 += third32 >> 32; - first32 += second32 >> 32; - - // remove carry from current digit - fourth32 &= 0xffffffff; - third32 &= 0xffffffff; - second32 &= 0xffffffff; - first32 &= 0xffffffff; - - // combine components - result.lower = (third32 << 32) | fourth32; - result.upper = (first32 << 32) | second32; -#endif - if (lhs_negative ^ rhs_negative) { - NegateInPlace(result); - } - return result; + return HugeintMultiplyUnchecked(lhs, rhs); } //===--------------------------------------------------------------------===// // Divide //===--------------------------------------------------------------------===// -int Sign(hugeint_t n) { - return ((n > 0) - (n < 0)); -} - -hugeint_t Abs(hugeint_t n) { - D_ASSERT(n != NumericLimits::Minimum()); - return (n * Sign(n)); -} - -static hugeint_t DivModMinimum(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { - D_ASSERT(lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()); - if (rhs == NumericLimits::Minimum()) { - if (lhs == NumericLimits::Minimum()) { - remainder = 0; - return 1; - } - remainder = lhs; - return 0; - } - - // Add 1 to minimum and run through DivMod again - hugeint_t result = Hugeint::DivMod(NumericLimits::Minimum() + 1, rhs, remainder); - - // If the 1 mattered we need to adjust the result, otherwise the remainder - if (Abs(remainder) + 1 == Abs(rhs)) { - result -= Sign(rhs); - remainder = 0; - } else { - remainder -= 1; - } - return result; -} - // No overflow checks hugeint_t Hugeint::DivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { - if (rhs == 0) { - remainder = lhs; - return hugeint_t(0); - } - - // Check if one of the sides is hugeint_t minimum, as that can't be negated. - if (lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()) { - return DivModMinimum(lhs, rhs, remainder); - } - - bool lhs_negative = lhs.upper < 0; - bool rhs_negative = rhs.upper < 0; - if (lhs_negative) { - Hugeint::NegateInPlace(lhs); - } - if (rhs_negative) { - Hugeint::NegateInPlace(rhs); - } - // DivMod code adapted from: - // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - - // initialize the result and remainder to 0 - hugeint_t div_result; - div_result.lower = 0; - div_result.upper = 0; - remainder.lower = 0; - remainder.upper = 0; - - uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); - // now iterate over the amount of bits that are set in the LHS - for (uint8_t x = highest_bit_set; x > 0; x--) { - // left-shift the current result and remainder by 1 - div_result = PositiveHugeintLeftShift(div_result, 1); - remainder = PositiveHugeintLeftShift(remainder, 1); - - // we get the value of the bit at position X, where position 0 is the least-significant bit - if (PositiveHugeintIsBitSet(lhs, x - 1)) { - remainder += 1; - } - if (Hugeint::GreaterThanEquals(remainder, rhs)) { - // the remainder has passed the division multiplier: add one to the divide result - remainder -= rhs; - div_result += 1; - } - } - if (lhs_negative ^ rhs_negative) { - Hugeint::NegateInPlace(div_result); - } - if (lhs_negative) { - Hugeint::NegateInPlace(remainder); - } - return div_result; + auto result = HugeintDivMod(lhs, rhs); + remainder = result.remainder; + return result.quotient; } bool Hugeint::TryDivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &result, hugeint_t &remainder) { @@ -494,9 +304,7 @@ hugeint_t Hugeint::Divide(hugeint_t lhs, hugeint_t rhs) { template <> hugeint_t Hugeint::Modulo(hugeint_t lhs, hugeint_t rhs) { - hugeint_t remainder; - (void)Hugeint::DivMod(lhs, rhs, remainder); - return remainder; + return HugeintDivMod(lhs, rhs).remainder; } //===--------------------------------------------------------------------===// @@ -773,62 +581,11 @@ bool Hugeint::TryConvert(long double value, hugeint_t &result) { //===--------------------------------------------------------------------===// // hugeint_t operators //===--------------------------------------------------------------------===// -hugeint_t::hugeint_t(int64_t value) { - auto result = Hugeint::Convert(value); - this->lower = result.lower; - this->upper = result.upper; -} - -bool hugeint_t::operator==(const hugeint_t &rhs) const { - return Hugeint::Equals(*this, rhs); -} - -bool hugeint_t::operator!=(const hugeint_t &rhs) const { - return Hugeint::NotEquals(*this, rhs); -} - -bool hugeint_t::operator<(const hugeint_t &rhs) const { - return Hugeint::LessThan(*this, rhs); -} - -bool hugeint_t::operator<=(const hugeint_t &rhs) const { - return Hugeint::LessThanEquals(*this, rhs); -} - -bool hugeint_t::operator>(const hugeint_t &rhs) const { - return Hugeint::GreaterThan(*this, rhs); -} - -bool hugeint_t::operator>=(const hugeint_t &rhs) const { - return Hugeint::GreaterThanEquals(*this, rhs); -} - -hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { - return hugeint_t(upper + rhs.upper + ((lower + rhs.lower) < lower), lower + rhs.lower); -} - -hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { - return hugeint_t(upper - rhs.upper - ((lower - rhs.lower) > lower), lower - rhs.lower); -} - -hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { - hugeint_t result = *this; - result *= rhs; - return result; -} - -hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { - return Hugeint::Divide(*this, rhs); -} hugeint_t hugeint_t::operator%(const hugeint_t &rhs) const { return Hugeint::Modulo(*this, rhs); } -hugeint_t hugeint_t::operator-() const { - return Hugeint::Negate(*this); -} - hugeint_t hugeint_t::operator>>(const hugeint_t &rhs) const { hugeint_t result; uint64_t shift = rhs.lower; diff --git a/src/duckdb/src/common/types/interval.cpp b/src/duckdb/src/common/types/interval.cpp index be943a4de..f208bf137 100644 --- a/src/duckdb/src/common/types/interval.cpp +++ b/src/duckdb/src/common/types/interval.cpp @@ -167,10 +167,10 @@ interval_parse_time : { // parse the remainder of the time as a Time type dtime_t time; idx_t pos; - if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time)) { + if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time, false, nullptr, negative)) { return false; } - if (!IntervalTryAddition(result.micros, negative ? -time.value : time.value, 1, error_message)) { + if (!IntervalTryAddition(result.micros, time.value, 1, error_message)) { return false; } found_any = true; diff --git a/src/duckdb/src/common/types/row/tuple_data_collection.cpp b/src/duckdb/src/common/types/row/tuple_data_collection.cpp index 3a34bcf12..19a682334 100644 --- a/src/duckdb/src/common/types/row/tuple_data_collection.cpp +++ b/src/duckdb/src/common/types/row/tuple_data_collection.cpp @@ -757,7 +757,14 @@ void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state, TupleDa } void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state) { - D_ASSERT(!segments.empty()); + if (segments.empty()) { + // The collection was Reset() while an append state still referred to it, which happens when an exception + // (e.g. out-of-memory) unwinds a Repartition after it moved this partition's rows and before the owner + // re-initialised its append states. There is no segment left to store the handles in, so release them. + pin_state.row_handles.clear(); + pin_state.heap_handles.clear(); + return; + } FinalizePinState(pin_state, *segments.back()); } diff --git a/src/duckdb/src/common/types/time.cpp b/src/duckdb/src/common/types/time.cpp index 34ebec740..04396b7a1 100644 --- a/src/duckdb/src/common/types/time.cpp +++ b/src/duckdb/src/common/types/time.cpp @@ -5,7 +5,9 @@ #include "duckdb/common/types/date.hpp" #include "duckdb/common/types/interval.hpp" #include "duckdb/common/types/timestamp.hpp" +#include "duckdb/common/operator/add.hpp" #include "duckdb/common/operator/multiply.hpp" +#include "duckdb/common/operator/subtract.hpp" #include "duckdb/common/exception/conversion_exception.hpp" #include @@ -19,8 +21,9 @@ static_assert(sizeof(dtime_t) == sizeof(int64_t), "dtime_t was padded"); // ISO 8601 bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos) { - int32_t hour = -1, min = -1, sec = -1, micros = -1; + optional_ptr nanos, bool negative) { + int64_t hour = -1; + int32_t min = -1, sec = -1, micros = -1; pos = 0; if (len == 0) { @@ -42,9 +45,9 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r return false; } - // Allow up to 9 digit hours to support intervals + // Allow up to 10 digit interval hours hour = 0; - for (int32_t digits = 9; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { + for (int32_t digits = 10; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { if (digits-- > 0) { hour = hour * 10 + (buf[pos] - '0'); } else { @@ -133,13 +136,25 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r } } - result = Time::FromTime(hour, min, sec, micros); + // a negative interval accumulates below zero, so its minimum fits + int64_t time_micros; + auto sub_hour_micros = Time::FromTime(0, min, sec, micros).value; + if (negative) { + if (!TryMultiplyOperator::Operation(hour, -Interval::MICROS_PER_HOUR, time_micros) || + !TrySubtractOperator::Operation(time_micros, sub_hour_micros, time_micros)) { + return false; + } + } else if (!TryMultiplyOperator::Operation(hour, Interval::MICROS_PER_HOUR, time_micros) || + !TryAddOperator::Operation(time_micros, sub_hour_micros, time_micros)) { + return false; + } + result = dtime_t(time_micros); return true; } bool Time::TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos) { - return Time::TryConvertInternal(buf, len, pos, result, strict, nanos); + optional_ptr nanos, bool negative) { + return Time::TryConvertInternal(buf, len, pos, result, strict, nanos, negative); } bool Time::TryConvertTime(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, diff --git a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp index 7a372334d..d85fc87dd 100644 --- a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp +++ b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp @@ -623,6 +623,13 @@ ParquetArrayIterator ParquetVariantNode::GetArrayChildren() const { void ParquetObjectIterator::Finalize() { std::sort(ordered_entries.begin(), ordered_entries.end(), [](const ParquetObjectEntry &a, const ParquetObjectEntry &b) { return a.key < b.key; }); + // the keys of a Variant object have to be unique + for (idx_t i = 1; i < ordered_entries.size(); i++) { + if (ordered_entries[i].key == ordered_entries[i - 1].key) { + throw InvalidInputException("Invalid VARIANT: object contains the key \"%s\" more than once", + ordered_entries[i].key.GetString()); + } + } } ParquetObjectIterator::ParquetObjectIterator(const ParquetVariantIterator &state, const ShreddedGroupView &view, diff --git a/src/duckdb/src/common/types/vector_buffer.cpp b/src/duckdb/src/common/types/vector_buffer.cpp index 957712872..751027350 100644 --- a/src/duckdb/src/common/types/vector_buffer.cpp +++ b/src/duckdb/src/common/types/vector_buffer.cpp @@ -338,18 +338,17 @@ void VectorBuffer::Copy(const Vector &source_p, const SelectionVector &source_se finished = true; break; default: { - // for exotic types we flatten followed by copying - only the copied range [source_offset, source_count) - // is flattened, as e.g. list functions copy the children of every list at a different offset - D_ASSERT(source_count >= source_offset); - const auto range_count = source_count - source_offset; - SelectionVector range_sel(range_count); - for (idx_t i = 0; i < range_count; i++) { + // for exotic types we flatten followed by copying - only the copied rows are flattened, as e.g. list + // functions copy the children of every list at a different offset, and the selection vector might only + // cover the copied rows + D_ASSERT(source_count >= source_offset + copy_count); + SelectionVector range_sel(copy_count); + for (idx_t i = 0; i < copy_count; i++) { range_sel.set_index(i, sel.get_index(source_offset + i)); } Vector flattened_vector(Vector::Ref(source)); - flattened_vector.Flatten(range_sel, range_count); - Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), range_count, 0, target_offset, - copy_count); + flattened_vector.Flatten(range_sel, copy_count); + Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), copy_count, 0, target_offset, copy_count); return; } } diff --git a/src/duckdb/src/common/vector/constant_vector.cpp b/src/duckdb/src/common/vector/constant_vector.cpp index a55430e52..706d3830e 100644 --- a/src/duckdb/src/common/vector/constant_vector.cpp +++ b/src/duckdb/src/common/vector/constant_vector.cpp @@ -34,22 +34,60 @@ void ConstantVector::Reference(Vector &vector, const Value &value, count_t count vector.SetBuffer(CreateConstantBuffer(value, count)); } -void ConstantVector::SetNull(Vector &vector, count_t count) { - // try to re-use the buffer if possible +static bool IsReusableBufferType(VectorBufferType buffer_type) { + switch (buffer_type) { + case VectorBufferType::STANDARD_BUFFER: + case VectorBufferType::STRUCT_BUFFER: + case VectorBufferType::ARRAY_BUFFER: + case VectorBufferType::LIST_BUFFER: + case VectorBufferType::STRING_BUFFER: + return true; + default: + return false; + } +} + +//! Whether the buffer of the vector can be turned into a NULL constant in place: it must not be shared with other +//! vectors (e.g. a referenced payload), and the children that are nulled as well must be flat or constant +static bool CanSetNullInPlace(const Vector &vector) { auto &buffer_ref = vector.GetBufferRef(); - bool needs_new_buffer = !buffer_ref; - if (!needs_new_buffer) { - auto buffer_type = buffer_ref->GetBufferType(); - needs_new_buffer = - (buffer_type != VectorBufferType::STANDARD_BUFFER && buffer_type != VectorBufferType::STRUCT_BUFFER && - buffer_type != VectorBufferType::ARRAY_BUFFER && buffer_type != VectorBufferType::LIST_BUFFER && - buffer_type != VectorBufferType::STRING_BUFFER); + if (!buffer_ref || buffer_ref.use_count() > 1) { + return false; } - if (needs_new_buffer) { + switch (buffer_ref->GetBufferType()) { + case VectorBufferType::STANDARD_BUFFER: + case VectorBufferType::LIST_BUFFER: + case VectorBufferType::STRING_BUFFER: + return true; + case VectorBufferType::STRUCT_BUFFER: + for (auto &entry : StructVector::GetEntries(vector)) { + if (!CanSetNullInPlace(entry)) { + return false; + } + } + return true; + case VectorBufferType::ARRAY_BUFFER: { + auto child_type = ArrayVector::GetChild(vector).GetVectorType(); + return child_type == VectorType::FLAT_VECTOR || child_type == VectorType::CONSTANT_VECTOR; + } + default: + return false; + } +} + +void ConstantVector::SetNull(Vector &vector, count_t count) { + auto &buffer_ref = vector.GetBufferRef(); + if (!buffer_ref || !IsReusableBufferType(buffer_ref->GetBufferType())) { // we cannot re-use the buffer - refer a null-value which has code necessary to create a new buffer Reference(vector, Value(vector.GetType()), count); return; } + if (!CanSetNullInPlace(vector)) { + // the buffer is shared with other vectors (or has children that cannot be nulled) - use a fresh buffer + auto capacity = MaxValue(buffer_ref->Capacity(), 1); + vector.Initialize(VectorDataInitialization::UNINITIALIZED, capacity); + } + // re-use the buffer, so that e.g. the reserved capacity of a list child is kept vector.SetVectorType(VectorType::CONSTANT_VECTOR); FlatVector::SetSize(vector, count); SetNull(vector, true); @@ -125,6 +163,8 @@ void ConstantVector::Reference(Vector &vector, count_t count, const Vector &sour ListVector::SetListSize(vector, ListVector::GetListSize(source)); vector.SetVectorType(VectorType::CONSTANT_VECTOR); + auto &validity = vector.BufferMutable().GetValidityMask(); + validity.Set(0, true); FlatVector::SetSize(vector, count); break; } diff --git a/src/duckdb/src/common/vector/list_vector.cpp b/src/duckdb/src/common/vector/list_vector.cpp index 127125e54..124911264 100644 --- a/src/duckdb/src/common/vector/list_vector.cpp +++ b/src/duckdb/src/common/vector/list_vector.cpp @@ -329,7 +329,7 @@ void ListVector::SetListSize(Vector &vec, idx_t size) { if (child_buffer) { auto vtype = child_buffer->GetVectorType(); if ((vtype == VectorType::FLAT_VECTOR || vtype == VectorType::CONSTANT_VECTOR) && - size > child_buffer->Capacity()) { + size > child_buffer->Capacity() && size > child_buffer->Size()) { list_buffer.Reserve(size); } } diff --git a/src/duckdb/src/execution/expression_executor/execute_function.cpp b/src/duckdb/src/execution/expression_executor/execute_function.cpp index 7481fe125..ef1fe0bc2 100644 --- a/src/duckdb/src/execution/expression_executor/execute_function.cpp +++ b/src/duckdb/src/execution/expression_executor/execute_function.cpp @@ -186,6 +186,29 @@ static idx_t SelectBooleanResult(Vector &result, const SelectionVector *sel, idx result, sel, count, [](bool value) { return value; }, true_sel, false_sel); } +static void SetConstantArgumentCardinality(DataChunk &arguments, vector> &buffers) { + buffers.clear(); + for (auto &arg : arguments.data) { + buffers.push_back(arg.GetBufferRef()); + } + arguments.SetChildCardinality(1ULL); +} + +static void RestoreConstantArgumentCardinality(DataChunk &arguments, vector> &buffers, + idx_t count) { + for (auto &arg : arguments.data) { + arg.SetVectorType(VectorType::CONSTANT_VECTOR); + } + arguments.SetChildCardinality(count); + // the function can have replaced an argument's buffer (e.g. by flattening it) + for (auto &buffer : buffers) { + if (buffer) { + buffer->SetVectorSize(count); + } + } + buffers.clear(); +} + static void ExecuteConstantSelectFunction(const BoundFunctionExpression &expr, DataChunk &args, ExpressionState &state, Vector &result) { D_ASSERT(args.size() == 1); @@ -222,15 +245,15 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } } + auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant temporarily set the child cardinality to 1 - arguments.SetChildCardinality(1ULL); + SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); } else { arguments.SetChildCardinality(count); } arguments.Verify(context); - auto &execute_function_state = state->Cast(); auto dictionary_executed = expr.Function().HasFunctionCallback() && !all_constant && execute_function_state.TryExecuteDictionaryExpression(expr, arguments, *state, result); if (!dictionary_executed) { @@ -243,11 +266,7 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } if (all_constant) { - // restore the input cardinality - for (auto &arg : arguments.data) { - arg.SetVectorType(VectorType::CONSTANT_VECTOR); - } - arguments.SetChildCardinality(count); + RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); // ensure the result type is constant result.FlattenAndSetConstant(); } @@ -281,9 +300,10 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression return SelectBooleanResult(result, sel, count, true_sel, false_sel); } } + auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant we only need to run the function on one value - arguments.SetChildCardinality(1ULL); + SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); } else { arguments.SetChildCardinality(count); } @@ -296,14 +316,9 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression } else { ExecuteConstantSelectFunction(expr, arguments, *state, result); } - // restore the input cardinality - for (auto &arg : arguments.data) { - arg.SetVectorType(VectorType::CONSTANT_VECTOR); - } - arguments.SetChildCardinality(count); + RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); return SelectBooleanResult(result, sel, count, true_sel, false_sel); } - auto &execute_function_state = state->Cast(); if (expr.Function().HasFunctionCallback()) { Vector result(LogicalType::BOOLEAN); auto dictionary_executed = diff --git a/src/duckdb/src/execution/index/fixed_size_buffer.cpp b/src/duckdb/src/execution/index/fixed_size_buffer.cpp index a1f4483ea..457413209 100644 --- a/src/duckdb/src/execution/index/fixed_size_buffer.cpp +++ b/src/duckdb/src/execution/index/fixed_size_buffer.cpp @@ -77,7 +77,11 @@ FixedSizeBuffer::~FixedSizeBuffer() { } if (OnDisk()) { // marking a block as modified decreases the reference count of multi-use blocks - block_manager.MarkBlockAsModified(block_pointer.block_id); + // this must not throw out of a destructor, e.g., if the block ids of a corrupt file overlap + try { + block_manager.MarkBlockAsModified(block_pointer.block_id); + } catch (...) { // NOLINT + } } } diff --git a/src/duckdb/src/execution/join_hashtable.cpp b/src/duckdb/src/execution/join_hashtable.cpp index bd90a7982..493e0f084 100644 --- a/src/duckdb/src/execution/join_hashtable.cpp +++ b/src/duckdb/src/execution/join_hashtable.cpp @@ -174,9 +174,9 @@ void JoinHashTable::Merge(JoinHashTable &other) { } } - if (bloom_filter.IsInitialized() && other.bloom_filter.IsInitialized()) { - bloom_filter.Merge(other.bloom_filter); - } + // every thread-local table shares this operator's filter - a different one here would mean its rows + // never reached the filter that gets published + D_ASSERT(!other.bloom_filter || bloom_filter.get() == other.bloom_filter.get()); sink_collection->Combine(*other.sink_collection); @@ -715,8 +715,8 @@ void JoinHashTable::Build(PartitionedTupleDataAppendState &append_state, DataChu // hash the keys and obtain an entry in the list // note that we only hash the keys used in the equality comparison Hash(keys, *current_sel, added_count, hash_values); - if (bloom_filter.IsInitialized()) { - bloom_filter.InsertHashes(hash_values); + if (bloom_filter) { + bloom_filter->InsertHashes(hash_values, *current_sel, added_count); } // Re-reference and ToUnifiedFormat the hash column after computing it @@ -1003,9 +1003,9 @@ static void InsertHashesLoop(unsafe_optional_ptr> entries, Ve void JoinHashTable::InsertHashes(Vector &hashes_v, TupleDataChunkState &chunk_state, InsertState &insert_state, bool parallel) { - // Insert Hashes into the BF - if (bloom_filter.IsInitialized()) { - bloom_filter.InsertHashes(hashes_v); + // a sealed filter was accumulated during Sink and is already complete; only a finalize-built one fills here + if (bloom_filter && !bloom_filter->IsSealed()) { + bloom_filter->InsertHashes(hashes_v); } auto atomic_entries = GetAtomicEntries(); auto &row_locations = chunk_state.row_locations; @@ -1072,9 +1072,10 @@ void JoinHashTable::AllocatePointerTable() { throw InternalException("Hashtable capacity exceeds 48-bit limit (2^48 - 1)"); } - if (should_build_bloom_filter && !bloom_filter.IsInitialized()) { - bloom_filter_init_count = MaxValue(Count(), 1); - bloom_filter.Initialize(context, bloom_filter_init_count); + if (should_build_bloom_filter && (!bloom_filter || bloom_filter->IsSealed())) { + // (re)create the finalize-built filter now that the actual build count is known + bloom_filter = make_shared_ptr(); + bloom_filter->Initialize(context, MaxValue(Count(), 1)); } if (hash_map.get()) { @@ -1100,28 +1101,6 @@ void JoinHashTable::AllocatePointerTable() { {"size", to_string(data_collection->SizeInBytes() + hash_map.GetSize())}}); } -void JoinHashTable::PrepareBloomFilterForFinalize() { - if (!should_build_bloom_filter) { - return; - } - - // Finalize scans every build tuple and inserts its hash into the bloom filter. - // Make sure any existing filter has enough sectors for the actual build count. - const auto build_count = Count(); - const auto actual_init_count = MaxValue(build_count, 1); - if (bloom_filter.IsInitialized()) { - const auto current_sectors = BloomFilter::GetNumberOfSectors(bloom_filter_init_count); - const auto required_sectors = BloomFilter::GetNumberOfSectors(actual_init_count); - if (current_sectors >= required_sectors) { - return; - } - } - - bloom_filter.Reset(); - bloom_filter_init_count = actual_init_count; - bloom_filter.Initialize(context, bloom_filter_init_count); -} - void JoinHashTable::InitializePointerTable(idx_t entry_idx_from, idx_t entry_idx_to) { // initialize HT with all-zero entries auto entries = GetEntries(); @@ -2599,9 +2578,8 @@ void JoinHashTable::ResetForNewIterationSinglePartition() { chains_longer_than_one.store(false, std::memory_order_relaxed); total_probe_matches = 0; load_factor = DEFAULT_LOAD_FACTOR; + bloom_filter.reset(); should_build_bloom_filter = false; - bloom_filter.Reset(); - bloom_filter_init_count = 0; prefix_range_filter.reset(); should_build_prefix_range_filter = false; ResetMarkJoinInfo(*this); diff --git a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp index 81b42c17f..687f3fb43 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp @@ -159,10 +159,13 @@ class WindowHashGroup { idx_t count = 0; //! The number of blocks in the group idx_t blocks = 0; - //! The partition boundary mask + //! The partition boundary mask. The masks are O(rows): their storage comes from the buffer allocator and the + //! masks point into it ValidityMask partition_mask; + AllocatedData partition_mask_data; //! The order boundary mask (maps sort column END to mask) OrderMasks order_masks; + unordered_map order_mask_data; OnceFlag masks_initialized; //! The fully materialised data collection unique_ptr collection; @@ -515,7 +518,9 @@ class WindowGlobalSourceState : public GlobalSourceState { return !stopped && finished < total_tasks; } //! Get the next task given the current state - bool TryNextTask(TaskPtr &task, Task &task_local); + bool TryNextTask(TaskPtr &task, Task &task_local) DUCKDB_EXCLUDES(lock); + //! Select a task with the lock held, after the previous task has been retired. + bool TryNextTaskLocked(TaskPtr &task, Task &task_local) DUCKDB_REQUIRES(lock); //! Context for executing computations ClientContext &client; @@ -663,23 +668,36 @@ void WindowHashGroup::UpdateScanner(ScannerPtr &scanner, idx_t begin_idx) const } } +//! Backs a mask of `count` rows (all valid) with a block from the allocator, so that it counts against the memory limit +static void AllocateMask(Allocator &allocator, idx_t count, ValidityMask &mask, AllocatedData &storage) { + storage = allocator.Allocate(ValidityMask::EntryCount(count) * sizeof(validity_t)); + memset(storage.get(), 0xFF, storage.GetSize()); + mask.Initialize(reinterpret_cast(storage.get()), count); +} + void WindowHashGroup::AllocateMasks() { CallOnce(masks_initialized, [&] { + auto &allocator = BufferAllocator::Get(gsink.client); ValidityMask new_partition_mask; - new_partition_mask.Initialize(count); + AllocatedData new_partition_mask_data; + AllocateMask(allocator, count, new_partition_mask, new_partition_mask_data); OrderMasks new_order_masks; + unordered_map new_order_mask_data; // All masks refer to the shared sort's partition columns, including duplicate expressions. const auto &sort_expr = gsink.op.select_list[gsink.op.order_idx]->Cast(); const auto order_begin = gsink.op.partition_info.RequiresPartitionColumns() ? 0 : sort_expr.Partitions().size(); for (auto &wexec : gsink.executors) { - auto &order_mask = new_order_masks[order_begin + wexec->wexpr.OrderBy().size()]; + const auto order_idx = order_begin + wexec->wexpr.OrderBy().size(); + auto &order_mask = new_order_masks[order_idx]; if (!order_mask.IsMaskSet()) { - order_mask.Initialize(count); + AllocateMask(allocator, count, order_mask, new_order_mask_data[order_idx]); } } partition_mask = std::move(new_partition_mask); + partition_mask_data = std::move(new_partition_mask_data); order_masks = std::move(new_order_masks); + order_mask_data = std::move(new_order_mask_data); }); } @@ -1033,7 +1051,10 @@ bool WindowGlobalSourceState::TryNextTask(TaskPtr &task, Task &task_local) { WindowHashGroupPtr finished_group; annotated_lock_guard guard(lock); finished_group = FinishTask(task); + return TryNextTaskLocked(task, task_local); +} +bool WindowGlobalSourceState::TryNextTaskLocked(TaskPtr &task, Task &task_local) { if (!HasMoreTasks()) { task = nullptr; return false; @@ -1278,11 +1299,15 @@ SourceResultType PhysicalWindow::GetDataInternal(ExecutionContext &context, Data // no more tasks - exit gsource.UnblockTasks(); break; - } else { - // there are more tasks available, but we can't execute them yet - // block the source - return gsource.BlockSource(source.interrupt_state); } + // TryAssignTask released the lock: another thread may have prepared the next stage and unblocked the + // blocked tasks in between, so look again before blocking, or no one would ever unblock this task. + if (gsource.TryNextTaskLocked(lsource.task, lsource.task_local)) { + continue; + } + // there are more tasks available, but we can't execute them yet + // block the source + return gsource.BlockSource(source.interrupt_state); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp index dfec932ad..96773a018 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp @@ -673,7 +673,7 @@ void StringValueResult::AddValue(StringValueResult &result, const idx_t buffer_p AddQuotedValue(result, buffer_pos - result.extra_delimiter_bytes); } else if (result.escaped) { AddPossiblyEscapedValue(result, buffer_pos, result.buffer_ptr + result.last_position.buffer_pos, - buffer_pos - result.last_position.buffer_pos, false); + result.HandleMultiDelimiter(buffer_pos), false); } else { result.AddValueToVector(result.buffer_ptr + result.last_position.buffer_pos, result.HandleMultiDelimiter(buffer_pos)); @@ -1536,12 +1536,22 @@ void StringValueScanner::ProcessOverBufferValue() { } if (!skip_value) { string_t value; + const auto &delimiter = result.state_machine.dialect_options.state_machine_options.delimiter.GetValue(); + const idx_t extra_delimiter_bytes = states.IsDelimiter() && !delimiter.empty() ? delimiter.size() - 1 : 0; + bool removed_extra_delimiter_bytes = false; if (result.quoted && !result.comment) { idx_t length = over_buffer_string.size() - 1 - result.quoted_position; while (length > 0 && result.ignore_empty_values && over_buffer_string.c_str()[result.quoted_position + length] == ' ') { length--; } + // remove the extra delimiter bytes before removing escapes, which can change the length of the value + if (extra_delimiter_bytes > length) { + throw InternalException( + "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); + } + length -= extra_delimiter_bytes; + removed_extra_delimiter_bytes = true; value = string_t(over_buffer_string.c_str() + result.quoted_position, UnsafeNumericCast(length)); if (result.escaped) { if (result.cur_col_id >= result.number_of_columns && @@ -1566,12 +1576,11 @@ void StringValueScanner::ProcessOverBufferValue() { } return; } - value = - RemoveEscape(str_ptr, over_buffer_string.size() - 2, - state_machine->dialect_options.state_machine_options.escape.GetValue(), - state_machine->dialect_options.state_machine_options.quote.GetValue(), - result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), - result.parse_chunk.data[result.chunk_col_id]); + value = RemoveEscape( + str_ptr, length, state_machine->dialect_options.state_machine_options.escape.GetValue(), + state_machine->dialect_options.state_machine_options.quote.GetValue(), + result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), + result.parse_chunk.data[result.chunk_col_id]); } } } else { @@ -1611,10 +1620,7 @@ void StringValueScanner::ProcessOverBufferValue() { result.EmptyLine(result, iterator.pos.buffer_pos); } else if (!states.IsNotSet() && (!result.comment || !value.Empty())) { idx_t value_size = value.GetSize(); - if (states.IsDelimiter() && - !result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().empty()) { - idx_t extra_delimiter_bytes = - result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().size() - 1; + if (!removed_extra_delimiter_bytes) { if (extra_delimiter_bytes > value_size) { throw InternalException( "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); @@ -1758,12 +1764,16 @@ bool StringValueScanner::FirstValueEndsOnQuote(CSVIterator iterator) const { CSVStates current_state; current_state.Initialize(CSVState::STANDARD); const idx_t to_pos = iterator.GetEndPos(); + const auto quote = state_machine->dialect_options.state_machine_options.quote.GetValue(); + const auto start_pos = iterator.pos.buffer_pos; while (iterator.pos.buffer_pos < to_pos) { state_machine->Transition(current_state, buffer_handle_ptr[iterator.pos.buffer_pos++]); if (current_state.IsState(CSVState::DELIMITER) || current_state.IsState(CSVState::CARRIAGE_RETURN) || current_state.IsState(CSVState::RECORD_SEPARATOR)) { - return buffer_handle_ptr[iterator.pos.buffer_pos - 2] == - state_machine->dialect_options.state_machine_options.quote.GetValue(); + // a value that also starts with a quote is a regular quoted value + const auto value_end = iterator.pos.buffer_pos - 2; + return value_end > start_pos && buffer_handle_ptr[value_end] == quote && + buffer_handle_ptr[start_pos] != quote; } } return false; diff --git a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp index 9330822ee..907cb14a6 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp @@ -123,6 +123,9 @@ static unique_ptr ReadSingleCSVFileBind(ClientContext &context, Ta throw BinderException("read_single_csv_file requires a non-NULL file name"); } result->file = TableFunctionFileBindInput::GetFile(input); + if (result->file.path.empty()) { + throw BinderException("read_single_csv_file requires a non-empty file name"); + } // the options of the scan this file is part of steer the sniffer - the file list is this single file MultiFileOptions file_options; diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp index 02de6895c..160f15832 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp @@ -680,6 +680,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::STRUCT) { throw BinderException("read_csv columns requires a struct as input"); } + if (val.IsNull()) { + throw BinderException("read_csv columns cannot be NULL"); + } auto &struct_children = StructValue::GetChildren(val); D_ASSERT(StructType::GetChildCount(child_type) == struct_children.size()); @@ -694,6 +697,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv requires a type specification as string"); } + if (val.IsNull()) { + throw BinderException("read_csv columns cannot have a NULL type"); + } parsed_types_per_column[name] = i; parsed_types.emplace_back(TransformStringToLogicalType(StringValue::Get(val), context)); } @@ -719,6 +725,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::LIST) { throw BinderException("read_csv auto_types requires a list as input"); } + if (val.IsNull()) { + throw BinderException("auto_type_candidates cannot be NULL"); + } auto &list_children = ListValue::GetChildren(val); if (list_children.empty()) { throw BinderException("auto_type_candidates requires at least one type"); @@ -727,6 +736,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("auto_type_candidates requires a type specification as string"); } + if (child.IsNull()) { + throw BinderException("auto_type_candidates cannot contain NULL types"); + } auto candidate_type = TransformStringToLogicalType(StringValue::Get(child), context); candidate_types[GetCandidateSpecificity(candidate_type)] = candidate_type; } @@ -772,6 +784,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (!columns_set && !sql_type_list.empty()) { throw BinderException("read_csv column_types/types/dtypes can only be supplied once"); } + if (val.IsNull()) { + throw BinderException("read_csv %s cannot be NULL", key); + } // Parse into temporary lists first vector sql_type_names; @@ -785,6 +800,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv %s requires a type specification as string", key); } + if (val.IsNull()) { + throw BinderException("read_csv %s cannot have a NULL type", key); + } sql_type_names.push_back(StringValue::Get(val)); parsed_types_per_column[name] = i; } @@ -795,6 +813,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key } auto &children = ListValue::GetChildren(val); for (auto &child : children) { + if (child.IsNull()) { + throw BinderException("read_csv %s cannot have a NULL type", key); + } sql_type_names.push_back(StringValue::Get(child)); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp index 09c2ea4c2..f2f27d572 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp @@ -30,7 +30,7 @@ void ThreadLines::Verify() const { continue; } if (last_end_pos + error_margin < line_info.second.start_pos || - line_info.second.start_pos < last_end_pos - error_margin) { + line_info.second.start_pos + error_margin < last_end_pos) { std::ostringstream error; error << "The Parallel CSV Reader currently does not support a full read on this file." << '\n'; error << "To correctly parse this file, please run with the single threaded error (i.e., parallel = " diff --git a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp index 81df617a6..286cc8b16 100644 --- a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp @@ -1089,14 +1089,16 @@ AsOfProbeBuffer::AsOfProbeBuffer(ClientContext &client, const PhysicalAsOfJoin & prefix_types.emplace_back(type); SortKeyPrefixComparisonColumn col; col.size = DConstants::INVALID_INDEX; - switch (type.id()) { - case LogicalTypeId::VARCHAR: - case LogicalTypeId::BLOB: - col.type = SortKeyPrefixComparisonType::VARCHAR; + switch (type.InternalType()) { + case PhysicalType::VARCHAR: + // only the sort keys of VARCHAR are terminated by the first null byte - the sort keys of other string + // types (e.g., BLOB or GEOMETRY) can contain null bytes, so we have to decode them to measure their width + col.type = type.id() == LogicalTypeId::VARCHAR ? SortKeyPrefixComparisonType::VARCHAR + : SortKeyPrefixComparisonType::NESTED; break; - case LogicalTypeId::STRUCT: - case LogicalTypeId::LIST: - case LogicalTypeId::ARRAY: + case PhysicalType::STRUCT: + case PhysicalType::LIST: + case PhysicalType::ARRAY: col.type = SortKeyPrefixComparisonType::NESTED; break; default: diff --git a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp index d0db40702..81ff1e45d 100644 --- a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp @@ -350,6 +350,60 @@ struct LayoutGate { static idx_t GetBuildSpaceRequirement(ClientContext &context, const JoinHashTable &ht, idx_t num_threads); +//! Building the bloom filter is costly on the build side to make probing faster, +//! so only use it if there are less build tuples than probing tuples +static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; +//! If we have a build side without a filter, only build the bloom filter if the ratio/size is small enough +static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; +static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; +//! A join is expected to stay in memory while its estimated build side is below this share of the memory budget +static constexpr idx_t LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION = 4; + +//! Whether the build side is expected to exceed the memory budget and spill +static bool JoinIsLikelyExternal(ClientContext &context, const PhysicalHashJoin &op) { + if (Settings::Get(context)) { + return true; + } + idx_t row_width = sizeof(data_ptr_t) + sizeof(hash_t); // per-row pointer table and hash overhead + for (auto &type : op.children[1].get().GetTypes()) { + row_width += GetTypeIdSize(type.InternalType()); + } + const auto build_size = op.children[1].get().estimated_cardinality * row_width; + const auto memory_budget = BufferManager::GetBufferManager(context).GetMaxMemory(); + return build_size > memory_budget / LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION; +} + +//! Whether to accumulate a bloom filter during Sink. Only an external join needs that: its finalize only ever +//! materializes some partitions, so a filter built there would be incomplete, while every row passes through +//! Sink. An in-memory join builds its filter during finalize instead, where the actual build count and the +//! competing filter choices are known. The estimate-based checks mirror CanUseBloomFilter, which decides +//! publication once the actual build count is known. +static bool ShouldBuildBloomFilterInSink(ClientContext &context, const PhysicalHashJoin &op) { + if (!op.filter_pushdown || op.filter_pushdown->probe_info.empty()) { + return false; + } + idx_t equality_count = 0; + for (auto &cond : op.conditions) { + const auto cmp = cond.GetComparisonType(); + if (cmp == ExpressionType::COMPARE_EQUAL || cmp == ExpressionType::COMPARE_NOT_DISTINCT_FROM) { + equality_count++; + } + } + if (equality_count != 1) { + return false; + } + const auto build_estimate = static_cast(op.children[1].get().estimated_cardinality); + const auto probe_estimate = static_cast(op.children[0].get().estimated_cardinality); + if (build_estimate > BUILD_TO_PROBE_RATIO_THRESHOLD * probe_estimate) { + return false; + } + if (!op.filter_pushdown->build_side_has_filter && build_estimate > NON_FILTERING_RATIO_THRESHOLD * probe_estimate && + build_estimate > NON_FILTERING_BUILD_SIDE_THRESHOLD) { + return false; + } + return JoinIsLikelyExternal(context, op); +} + class HashJoinGlobalSinkState : public GlobalSinkState { public: HashJoinGlobalSinkState(const PhysicalHashJoin &op_p, ClientContext &context_p) @@ -384,6 +438,19 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } + InitializeBloomFilter(); + } + + //! Creates the bloom filter that every thread-local hash table inserts into during Sink + void InitializeBloomFilter() { + // a perfect hash join publishes its own exact filter, so a bloom filter would never leave the sink + if (skip_filter_pushdown || can_use_perfect_hash || !ShouldBuildBloomFilterInSink(context, op)) { + hash_table->SetBloomFilter(nullptr); + return; + } + auto filter = make_shared_ptr(); + filter->Initialize(context, MaxValue(op.children[1].get().estimated_cardinality, 1)); + hash_table->SetBloomFilter(std::move(filter)); } ~HashJoinGlobalSinkState() override { @@ -431,6 +498,7 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } + InitializeBloomFilter(); // The next iteration's first chunk publishes the layout afresh: the rerun build may deliver a different // dictionary (or none), so neither a narrowed slot nor the dictionary it indexes into can be carried over. // ResetForNewIterationSinglePartition returned the global JHT to the unpublished state accordingly. @@ -457,6 +525,12 @@ class HashJoinGlobalSinkState : public GlobalSinkState { //! The number of active local states atomic active_local_states; + //! The memory available to the hash table: the reservation minus what partitioning the probe side requires + idx_t GetMaxExternalHTSize() const { + const auto reservation = temporary_memory_state->GetReservation(); + return reservation > probe_side_requirement ? reservation - probe_side_requirement : 0; + } + //! Whether we are doing an external + some sizes bool external; idx_t total_size; @@ -514,6 +588,8 @@ class HashJoinLocalSinkState : public LocalSinkState { } hash_table = op.InitializeHashTable(context, gstate.hash_table->GetRadixBits()); + // every thread inserts into this one filter - InsertOne is an atomic fetch_or, so it needs no lock + hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); // sink_collection exists only after the layout is published on the first build chunk, so // InitializeAppendState runs lazily inside Sink. keep_hash_table = gstate.keep_local_hash_tables; @@ -561,6 +637,9 @@ class HashJoinLocalSinkState : public LocalSinkState { hash_table = op.InitializeHashTable(context.client, gstate.hash_table->GetRadixBits()); append_state_initialised = false; } + // the global state resets before any local one, so the filter this picks up is always a fresh one + D_ASSERT(!gstate.hash_table->GetBloomFilter() || !gstate.hash_table->GetBloomFilter()->IsSealed()); + hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); keep_hash_table = gstate.keep_local_hash_tables; gstate.active_local_states++; if (op.filter_pushdown) { @@ -1178,6 +1257,9 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { } if (sink.global_filter_state) { + // a finalize-built bloom filter is complete once the tasks are done; a sink-built one is + // already sealed and this is a no-op + sink.hash_table->SealBloomFilter(); PublishDeferredRuntimeFilters(sink.context, *sink.hash_table, *sink.global_filter_state); } sink.hash_table->finalized = true; @@ -1229,6 +1311,24 @@ void HashJoinGlobalSinkState::InitializeProbeSpill() { } } +//! Computes and registers the runtime filters of an external join. Both external paths call this exactly once, +//! after the whole build side has been merged into the global hash table and before the finalize event is +//! scheduled. Every input is accumulated during Sink, so it does not matter which partitions are materialized. +static void FinalizeExternalRuntimeFilters(ClientContext &context, const PhysicalHashJoin &op, + HashJoinGlobalSinkState &sink) { + if (!op.filter_pushdown || sink.skip_filter_pushdown) { + return; + } + auto &ht = *sink.hash_table; + if (ht.GetSinkCollection().Count() == 0) { + return; + } + auto filter_min_max = op.filter_pushdown->FinalizeMinMax(*sink.global_filter_state); + // prefix-range filters are built from the materialized hash table, which only ever holds one round + op.filter_pushdown->FinalizeFilters(context, op, std::move(filter_min_max), &ht, true, false, + sink.global_filter_state.get()); +} + class HashJoinRepartitionTask : public ExecutorTask { public: HashJoinRepartitionTask(shared_ptr event_p, ClientContext &context, JoinHashTable &global_ht, @@ -1323,9 +1423,9 @@ class HashJoinRepartitionEvent : public BasePipelineEvent { sink.probe_side_requirement); sink.temporary_memory_state->UpdateReservation(executor.context); - D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - - sink.probe_side_requirement); + FinalizeExternalRuntimeFilters(sink.context, op, sink); + + sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); sink.ScheduleFinalize(*pipeline, *this); } }; @@ -1361,9 +1461,6 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i return false; } - // building the bloom filter is costly on the build to make probing faster, - // so only use it if there are less build tuples than probing tuples - static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; auto build_count = ht->Count(); if (build_count == 0) { build_count = ht->GetSinkCollection().Count(); @@ -1378,8 +1475,6 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i } // if we have a build side without a filter, only build the bloom filter if the ratio/size is small enough - static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; - static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; if (!build_side_has_filter && build_to_probe_ratio > NON_FILTERING_RATIO_THRESHOLD && build_count > NON_FILTERING_BUILD_SIDE_THRESHOLD) { return false; @@ -1531,13 +1626,15 @@ static unique_ptr CreateRuntimeFilterExpression(ClientContext &conte unique_ptr filter_expr; switch (deferred.type) { case DeferredRuntimeFilterType::BLOOM_FILTER: { - D_ASSERT(ht.GetBloomFilter().IsInitialized()); - if (!ht.GetBloomFilter().IsInitialized()) { + // a filter that is not sealed does not describe the complete build side - publishing it would drop + // rows - and a filter that saturated is not worth the hashing it costs the scan + if (!ht.HasPublishableBloomFilter()) { return nullptr; } + auto bloom_filter = ht.GetBloomFilter(); filter_expr = make_uniq( BoundScalarFunction(BloomFilterScalarFun::GetFunction(filter_input_type)), std::move(children), - make_uniq(ht.GetBloomFilter(), filters_null_values, key_name, key_type, + make_uniq(std::move(bloom_filter), filters_null_values, key_name, key_type, selectivity_threshold, n_vectors_to_check)); break; } @@ -1851,9 +1948,17 @@ unique_ptr JoinFilterPushdownInfo::FinalizeFilters(ClientContext &con } if (allow_bloom_filters && can_emit_runtime_filters && ht && gstate && CanUseBloomFilter(info, context, op, cmp, ht)) { - ht->SetBuildBloomFilter(true); - DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, - filter_col_idx, *gstate); + if (ht->HasPublishableBloomFilter()) { + // a complete filter was accumulated during Sink (external joins build it there) + DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, + filter_col_idx, *gstate); + } else if (allow_prefix_range_filters) { + // like a prefix-range filter, a bloom filter may be built during finalize when the + // finalize materializes the whole build side - an external finalize does not + ht->SetBuildBloomFilter(true); + DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, + filter_col_idx, *gstate); + } } } } @@ -1873,6 +1978,9 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl auto &sink = input.global_state.Cast(); auto &ht = *sink.hash_table; + // the filter covers the whole build side once Sink is done + ht.SealBloomFilter(); + sink.temporary_memory_state->UpdateReservation(context); sink.external = sink.temporary_memory_state->GetReservation() < sink.total_size; if (sink.external) { @@ -1913,8 +2021,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl (max_partition_ht_size + sink.probe_side_requirement) > sink.temporary_memory_state->GetReservation()) { // We have to repartition const auto radix_bits_before = ht.GetRadixBits(); - ht.SetRepartitionRadixBits(sink.temporary_memory_state->GetReservation(), sink.max_partition_size, - sink.max_partition_count); + ht.SetRepartitionRadixBits(sink.GetMaxExternalHTSize(), sink.max_partition_size, sink.max_partition_count); DUCKDB_LOG(context, PhysicalOperatorLogType, *this, "PhysicalHashJoin", "Repartition", {{"partitions_before", to_string(RadixPartitioning::NumberOfPartitions(radix_bits_before))}, {"partitions_after", to_string(RadixPartitioning::NumberOfPartitions(ht.GetRadixBits()))}}); @@ -1927,15 +2034,8 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl } sink.local_hash_tables.clear(); sink.owned_local_hash_tables.clear(); - if (filter_pushdown && !sink.skip_filter_pushdown && ht.GetSinkCollection().Count() > 0) { - auto filter_min_max = filter_pushdown->FinalizeMinMax(*sink.global_filter_state); - filter_pushdown->FinalizeFilters(context, *this, std::move(filter_min_max), &ht, false, false, - sink.global_filter_state.get()); - } - ht.PrepareBloomFilterForFinalize(); - D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - - sink.probe_side_requirement); + FinalizeExternalRuntimeFilters(context, *this, sink); + sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; @@ -1989,7 +2089,6 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl // In case of a large build side or duplicates, use regular hash join if (!use_perfect_hash) { - ht.PrepareBloomFilterForFinalize(); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; @@ -2436,9 +2535,7 @@ void HashJoinGlobalSourceState::PrepareBuild(HashJoinGlobalSinkState &sink) { sink.probe_side_requirement); // Try to put the next partitions in the block collection of the HT - D_ASSERT(!sink.external || sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - if (!sink.external || - !ht.PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - sink.probe_side_requirement)) { + if (!sink.external || !ht.PrepareExternalFinalize(sink.GetMaxExternalHTSize())) { global_stage = HashJoinSourceStage::DONE; sink.temporary_memory_state->SetZero(); return; diff --git a/src/duckdb/src/execution/operator/order/physical_top_n.cpp b/src/duckdb/src/execution/operator/order/physical_top_n.cpp index 41cb651eb..5dd70b9db 100644 --- a/src/duckdb/src/execution/operator/order/physical_top_n.cpp +++ b/src/duckdb/src/execution/operator/order/physical_top_n.cpp @@ -57,9 +57,13 @@ struct TopNBoundaryValue { Vector boundary_vector; OrderModifiers boundary_modifiers; - string GetBoundaryValue() { - lock_guard l(lock); - return boundary_value; + bool TryGetBoundaryValue(string ¤t_boundary) { + unique_lock l(lock, std::try_to_lock); + if (!l.owns_lock() || boundary_value == current_boundary) { + return false; + } + current_boundary = boundary_value; + return true; } void UpdateValue(string_t boundary_val) { @@ -273,15 +277,14 @@ void TopNHeap::AddLargeHeap(DataChunk &input, const Vector &sort_keys_vec) { } bool TopNHeap::CheckBoundaryValues(DataChunk &sort_chunk, DataChunk &payload, TopNBoundaryValue &global_boundary) { - // get the global boundary value - auto current_boundary_val = global_boundary.GetBoundaryValue(); - if (current_boundary_val.empty()) { + // An older local boundary remains valid if refreshing it would block. + const auto boundary_updated = global_boundary.TryGetBoundaryValue(boundary_val); + if (boundary_val.empty()) { // no boundary value (yet) - don't do anything return true; } - if (current_boundary_val != boundary_val) { + if (boundary_updated) { // new boundary value - decode - boundary_val = std::move(current_boundary_val); boundary_values.Reset(); CreateSortKeyHelpers::DecodeSortKey(string_t(boundary_val), boundary_values, 0, modifiers); for (auto &col : boundary_values.data) { diff --git a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp index ef35aa085..af65686ff 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp @@ -335,12 +335,12 @@ unique_ptr PhysicalBatchInsert::GetGlobalSinkState(ClientContex // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info); + auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info); table = &created_table->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table.get_mutable(); + table = insert_table; } // heuristic - we start off by allocating 4MB of cache space per column static constexpr const idx_t MINIMUM_MEMORY_PER_COLUMN = 4ULL * 1024ULL * 1024ULL; diff --git a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp index ebbb8a93c..7487684a4 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp @@ -634,7 +634,7 @@ class PartitionWriteLease { PartitionWriteInfo &Get() const { D_ASSERT(write_info); - return *write_info.get_mutable(); + return *write_info; } PartitionWriteInfo &operator*() const { @@ -649,7 +649,7 @@ class PartitionWriteLease { private: optional_ptr manager; - optional_ptr write_info; + mutable optional_ptr write_info; }; class PartitionWriteManager { @@ -766,8 +766,21 @@ struct PartitionedCopyFlushAction { struct PartitionedCopyBatchState { private: + //! Partition values can be NULL, so they are compared with IS NOT DISTINCT FROM semantics + static bool PartitionValuesEqual(const vector &left, const vector &right) { + if (left.size() != right.size()) { + return false; + } + for (idx_t i = 0; i < left.size(); i++) { + if (!Value::NotDistinctFrom(left[i], right[i])) { + return false; + } + } + return true; + } + void SetValues(vector values_p) { - D_ASSERT(!values || *values == values_p); + D_ASSERT(!values || PartitionValuesEqual(*values, values_p)); if (!values) { values = std::move(values_p); } diff --git a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp index c8c9c1635..c671c63db 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp @@ -117,12 +117,11 @@ unique_ptr PhysicalInsert::GetGlobalSinkState(ClientContext &co // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info) - ->Cast(); + table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info)->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table.get_mutable(); + table = insert_table; } auto result = make_uniq(context, GetTypes(), *table); return std::move(result); diff --git a/src/duckdb/src/execution/operator/projection/physical_projection.cpp b/src/duckdb/src/execution/operator/projection/physical_projection.cpp index fcc5b7ba6..5dd02daa8 100644 --- a/src/duckdb/src/execution/operator/projection/physical_projection.cpp +++ b/src/duckdb/src/execution/operator/projection/physical_projection.cpp @@ -31,6 +31,16 @@ PhysicalProjection::PhysicalProjection(PhysicalPlan &physical_plan, vectorGetExpressionClass() != ExpressionClass::BOUND_REF) { + PhysicalOperator::InitializeOutputChunk(allocator, chunk); + return; + } + } + chunk.InitializeEmpty(types); +} + OperatorResultType PhysicalProjection::Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state_p) const { auto &state = state_p.Cast(); diff --git a/src/duckdb/src/execution/operator/set/physical_union.cpp b/src/duckdb/src/execution/operator/set/physical_union.cpp index 3be025eff..78551361b 100644 --- a/src/duckdb/src/execution/operator/set/physical_union.cpp +++ b/src/duckdb/src/execution/operator/set/physical_union.cpp @@ -83,10 +83,15 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin auto dependency_type = order_matters ? PipelineDependencyType::REQUIRED : PipelineDependencyType::OPTIONAL_DEPENDENCY; dependencies = meta_pipeline.AddDependenciesFrom(union_pipeline, union_pipeline, false, dependency_type); + if (!allow_out_of_order) { + // CreateUnionPipeline already orders this pipeline after current. Include current when + // propagating that ordering to the child sink pipelines as well. + dependencies.push_back(current.shared_from_this()); + } // we also add dependencies if the LHS child can saturate all available threads // in that case, we recursively make all RHS children depend on the LHS. // This prevents breadth-first plan evaluation - if (can_saturate_threads) { + if (!allow_out_of_order || can_saturate_threads) { last_child_ptr = meta_pipeline.GetLastChild(); } } @@ -96,8 +101,10 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin children[i].get().BuildPipelines(union_pipeline, meta_pipeline); if (last_child_ptr) { - // the pointer was set, set up the dependencies - meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr); + // Explicitly ordered unions also order their children's sink pipelines. + auto dependency_mode = + !allow_out_of_order ? RecursiveDependencyMode::FORCE : RecursiveDependencyMode::RESPECT_PARALLELISM; + meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr, dependency_mode); } } } diff --git a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp index c22e2cf4f..f2b2dbbcc 100644 --- a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp @@ -144,6 +144,7 @@ optional_ptr PhysicalPlanGenerator::PlanAsOfLoopJoin(LogicalCo case ExpressionType::COMPARE_EQUAL: case ExpressionType::COMPARE_NOTEQUAL: case ExpressionType::COMPARE_DISTINCT_FROM: + case ExpressionType::COMPARE_NOT_DISTINCT_FROM: break; default: // Unsupported NLJ comparison @@ -360,6 +361,11 @@ PhysicalOperator &PhysicalPlanGenerator::PlanAsOfJoin(LogicalComparisonJoin &op) if (result) { return *result; } + if (has_predicate) { + // PhysicalAsOfJoin only evaluates the comparisons + throw NotImplementedException("Unsupported ASOF JOIN: the join predicate cannot be evaluated together " + "with these join conditions"); + } } // Check to see if we can leverage partitioning diff --git a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp index 4431083a1..3a3267568 100644 --- a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp @@ -247,7 +247,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi return join; } - D_ASSERT(op.left_projection_map.empty()); + // range joins apply both projection maps, the nested loop join only applies the left one of a MARK join idx_t nested_loop_join_threshold = Settings::Get(context); if (left.estimated_cardinality < nested_loop_join_threshold || right.estimated_cardinality < nested_loop_join_threshold) { @@ -273,6 +273,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi } if (PhysicalNestedLoopJoin::IsSupported(op.conditions, op.join_type)) { // inequality join: use nested loop + D_ASSERT(op.left_projection_map.empty() || op.join_type == JoinType::MARK); return Make(op, left, right, std::move(op.conditions), op.join_type, op.estimated_cardinality, std::move(op.filter_pushdown)); } @@ -282,6 +283,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi RewriteJoinCondition(cond.RightReference(), left.types.size()); } } + D_ASSERT(op.left_projection_map.empty()); auto condition = JoinCondition::CreateExpression(std::move(op.conditions)); return Make(op, left, right, std::move(condition), op.join_type, op.estimated_cardinality); } diff --git a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp index 6e83fd173..078f2b426 100644 --- a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp @@ -39,6 +39,24 @@ static JoinCondition CreateNotDistinctComparison(ClientContext &context, const L return cond; } +//! The types to compare the columns of EXCEPT / INTERSECT with - a collation of either side applies (as for UNION) +static vector GetComparisonTypes(ClientContext &context, const LogicalSetOperation &op, + const vector &left_types, + const vector &right_types) { + // the types only differ in their collations here, but a collation can be missing from a child (e.g. when the + // projection applying a COLLATE was removed) or from the operator (it takes the types of the left child) + vector types; + for (idx_t i = 0; i < left_types.size(); i++) { + auto type = LogicalType::ForceMaxLogicalType(context, left_types[i], right_types[i]); + // the operator types can be outdated (e.g. compressed children), only take a collation from them + if (i < op.types.size() && op.types[i].id() == type.id()) { + type = LogicalType::ForceMaxLogicalType(context, op.types[i], type); + } + types.push_back(std::move(type)); + } + return types; +} + PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { ArenaLinkedList> children(physical_plan->ArenaRef()); for (auto &child : op.children) { @@ -63,7 +81,7 @@ PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { } auto &left = children[0]; auto &right = children[1]; - auto &types = left.get().GetTypes(); + auto types = GetComparisonTypes(context, op, left.get().GetTypes(), right.get().GetTypes()); vector conditions; // create equality condition for all columns for (idx_t i = 0; i < types.size(); i++) { diff --git a/src/duckdb/src/function/aggregate/distributive/count.cpp b/src/duckdb/src/function/aggregate/distributive/count.cpp index 69360d5aa..f62027b09 100644 --- a/src/duckdb/src/function/aggregate/distributive/count.cpp +++ b/src/duckdb/src/function/aggregate/distributive/count.cpp @@ -296,6 +296,7 @@ AggregateFunction CountFunctionBase::GetFunction() { fun.GetSignature().AddParameter("arg", LogicalTypeId::ANY); fun.SetName("count"); fun.SetOrderDependent(AggregateOrderDependent::NOT_ORDER_DEPENDENT); + fun.SetCollationHandling(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS); fun.SetStructStateExport(GetCountStateType); fun.SetStatisticsCallback(CountPropagateStats); return fun; diff --git a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp index c36df8b1d..318702a0b 100644 --- a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp +++ b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp @@ -63,12 +63,22 @@ struct SortedAggregateBindData : public FunctionData { using BindInfoPtr = unique_ptr; using OrderBys = vector; - SortedAggregateBindData(ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, - BindInfoPtr &bind_info, OrderBys &order_bys) + //! fixed_layout: the buffer struct and per-key columns to use instead of mapping the keys onto the arguments + SortedAggregateBindData( + ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, BindInfoPtr &bind_info, + OrderBys &order_bys, + optional_ptr>> fixed_layout = nullptr) : context(context), function(aggregate), bind_info(std::move(bind_info)), threshold(Settings::Get(context)) { vector order_spec; - buffered_types = MapSortedColumns(children, order_bys, order_spec); + if (fixed_layout) { + for (auto &child : StructType::GetChildTypes(fixed_layout->first)) { + buffered_types.emplace_back(child.second); + } + order_spec = fixed_layout->second; + } else { + buffered_types = MapSortedColumns(children, order_bys, order_spec); + } const idx_t argument_count = children.size(); // the arguments are the leading buffered columns (referencing their input slot); appended sort keys follow buffered_cols.resize(buffered_types.size()); @@ -86,9 +96,11 @@ struct SortedAggregateBindData : public FunctionData { BuildSort(order_spec); } - SortedAggregateBindData(ClientContext &context, BoundAggregateExpression &expr) + SortedAggregateBindData( + ClientContext &context, BoundAggregateExpression &expr, + optional_ptr>> fixed_layout = nullptr) : SortedAggregateBindData(context, expr.GetChildrenMutable(), expr.FunctionMutable(), expr.BindInfoMutable(), - expr.GetOrderBysMutable()->orders) { + expr.GetOrderBysMutable()->orders, fixed_layout) { } SortedAggregateBindData(ClientContext &context, BoundWindowExpression &expr) @@ -616,29 +628,34 @@ void FunctionBinder::BindSortedAggregate(ClientContext &context, BoundAggregateE auto &children = expr.GetChildrenMutable(); auto &order_bys = *expr.GetOrderBysMutable(); + // the layout of an exported state was fixed at bind time and is part of its type - keep it, as the optimizer may + // have changed the expressions since (e.g. so that an ORDER BY key no longer matches the argument it matched) + pair> export_layout; if (state_export) { + export_layout.first = ListType::GetChildType(expr.GetReturnType()); + auto &logical_fields = StructType::GetChildTypes(export_layout.first); + ExportAggregateFunction::GetSortedStateOrders(expr.GetReturnType(), export_layout.second); + if (export_layout.second.size() != order_bys.orders.size() || children.size() > logical_fields.size()) { + throw InternalException("Exported state layout of ordered aggregate \"%s\" does not match its ORDER BY", + bound_function.GetName()); + } // the statistics optimizer may have narrowed the buffered column types since the exported type was fixed at // bind time (e.g. a small-range group column INTEGER->TINYINT) - cast them back so the buffer matches its - // declared type. The widening preserves sort order; casting reused columns to the same type keeps matching. - LogicalType plan_struct; - vector order_columns; - idx_t argument_count; - GetSortedAggregateStateLayout(expr, plan_struct, order_columns, argument_count); - auto &logical_fields = StructType::GetChildTypes(ListType::GetChildType(expr.GetReturnType())); + // declared type. The widening preserves sort order. auto cast_to = [&](unique_ptr &e, const LogicalType &type) { if (e->GetReturnType() != type) { e = BoundCastExpression::AddCastToType(context, std::move(e), type); } }; - for (idx_t i = 0; i < children.size() && i < logical_fields.size(); i++) { + for (idx_t i = 0; i < children.size(); i++) { cast_to(children[i], logical_fields[i].second); } - for (idx_t o = 0; o < order_bys.orders.size() && o < order_columns.size(); o++) { - cast_to(order_bys.orders[o].expression, logical_fields[order_columns[o].column].second); + for (idx_t o = 0; o < order_bys.orders.size(); o++) { + cast_to(order_bys.orders[o].expression, logical_fields[export_layout.second[o].column].second); } } - auto sorted_bind = make_uniq(context, expr); + auto sorted_bind = make_uniq(context, expr, state_export ? &export_layout : nullptr); if (!sorted_bind->sorted_on_args) { // The arguments are the children plus the sort columns. diff --git a/src/duckdb/src/function/cast/array_casts.cpp b/src/duckdb/src/function/cast/array_casts.cpp index 6e373fb6b..118dd5690 100644 --- a/src/duckdb/src/function/cast/array_casts.cpp +++ b/src/duckdb/src/function/cast/array_casts.cpp @@ -52,11 +52,9 @@ static bool ArrayToArrayCast(Vector &source, Vector &result, idx_t count, CastPa auto msg = StringUtil::Format("Cannot cast array of size %u to array of size %u", source_array_size, target_array_size); HandleCastError::AssignError(msg, parameters); - if (!parameters.strict) { - // if this was a TRY_CAST, we know every row will fail, so just return null - ConstantVector::SetNull(result, count_t(count)); - return false; - } + // every row fails, so just return null + ConstantVector::SetNull(result, count_t(count)); + return false; } auto &cast_data = parameters.cast_data->Cast(); diff --git a/src/duckdb/src/function/cast/bignum_casts.cpp b/src/duckdb/src/function/cast/bignum_casts.cpp index 489a94270..11b6e0ec3 100644 --- a/src/duckdb/src/function/cast/bignum_casts.cpp +++ b/src/duckdb/src/function/cast/bignum_casts.cpp @@ -276,6 +276,18 @@ BoundCastInfo Bignum::NumericToBignumCastSwitch(const LogicalType &source) { } } +struct BignumToDoubleErrorCast { + template + static inline bool Operation(SRC input, DST &result, CastParameters ¶meters) { + if (Bignum::BignumToDouble(input, result, parameters.strict)) { + return true; + } + HandleCastError::AssignError( + StringUtil::Format("Could not convert bignum '%s' to Double", Bignum::BignumToVarchar(input)), parameters); + return false; + } +}; + BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const LogicalType &source, const LogicalType &target) { D_ASSERT(source.id() == LogicalTypeId::BIGNUM); @@ -313,7 +325,7 @@ BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const Logical case LogicalTypeId::VARCHAR: return BoundCastInfo(&VectorCastHelpers::StringCast); case LogicalTypeId::DOUBLE: - return BoundCastInfo(&VectorCastHelpers::TryCastLoop); + return BoundCastInfo(&VectorCastHelpers::TryCastErrorLoop); default: return TryVectorNullCast; } diff --git a/src/duckdb/src/function/cast/cast_statistics.cpp b/src/duckdb/src/function/cast/cast_statistics.cpp index b382f1633..baebb777a 100644 --- a/src/duckdb/src/function/cast/cast_statistics.cpp +++ b/src/duckdb/src/function/cast/cast_statistics.cpp @@ -153,6 +153,10 @@ static unique_ptr StatisticsPropagateVariant(const BaseStatistic // only try this for non-nested return nullptr; } + if (input.GetStatsType() != StatisticsType::VARIANT_STATS) { + // e.g. the unknown statistics of a prepared statement parameter + return nullptr; + } if (!VariantStats::IsShredded(input)) { // not shredded return nullptr; diff --git a/src/duckdb/src/function/cast/string_cast.cpp b/src/duckdb/src/function/cast/string_cast.cpp index 1a9d2b44f..ce5893030 100644 --- a/src/duckdb/src/function/cast/string_cast.cpp +++ b/src/duckdb/src/function/cast/string_cast.cpp @@ -4,6 +4,7 @@ #include "duckdb/common/vector/list_vector.hpp" #include "duckdb/common/vector/map_vector.hpp" #include "duckdb/common/vector/struct_vector.hpp" +#include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/function/cast/default_casts.hpp" #include "duckdb/function/cast/vector_cast_helpers.hpp" #include "duckdb/common/exception/conversion_exception.hpp" @@ -323,8 +324,10 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, // set the list size after the child casts, since the casts may have replaced the child buffers ListVector::SetListSize(result, total); - if (!vector_cast_data.all_converted) { - auto &key_validity = FlatVector::ValidityMutable(result_key_child); + if (!vector_cast_data.all_converted || parameters.error_message) { + // maps with a NULL key - from a failed key cast, or keys of type NULL - fail to convert + // the key cast does not necessarily produce a flat vector (e.g. when casting to NULL keys) + auto key_validity = result_key_child.Validity(); for (idx_t row_idx = 0; row_idx < count; row_idx++) { if (!result_mask.RowIsValid(row_idx)) { continue; @@ -332,13 +335,30 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, auto list = list_data[row_idx]; for (idx_t list_idx = 0; list_idx < list.length; list_idx++) { auto idx = list.offset + list_idx; - if (!key_validity.RowIsValid(idx)) { - result_mask.SetInvalid(row_idx); + if (!key_validity.IsValid(idx)) { + HandleVectorCastError::Operation("Map keys can not be NULL.", result_mask, row_idx, + vector_cast_data); + break; } } } } - MapVector::MapConversionVerify(result, count); + if (!parameters.error_message) { + MapVector::MapConversionVerify(result, count); + return vector_cast_data.all_converted; + } + // TRY_CAST: nullify maps with duplicate keys instead of throwing + SelectionVector row_sel(1); + for (idx_t row_idx = 0; row_idx < count; row_idx++) { + if (!result_mask.RowIsValid(row_idx)) { + continue; + } + row_sel.set_index(0, row_idx); + if (MapVector::CheckMapValidity(result, 1, row_sel) != MapInvalidReason::VALID) { + HandleVectorCastError::Operation("Map keys must be unique.", result_mask, row_idx, + vector_cast_data); + } + } return vector_cast_data.all_converted; } diff --git a/src/duckdb/src/function/cast/union_casts.cpp b/src/duckdb/src/function/cast/union_casts.cpp index e4630e317..b1ad5230f 100644 --- a/src/duckdb/src/function/cast/union_casts.cpp +++ b/src/duckdb/src/function/cast/union_casts.cpp @@ -359,6 +359,11 @@ BoundCastInfo DefaultCasts::ImplicitToUnionCast(BindCastInput &input, const Logi } static bool UnionToVarcharCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + if (count == 0) { + // the intermediate union vector would not have any storage + FlatVector::SetSize(result, count_t(0)); + return true; + } // first cast all union members to varchar auto &cast_data = parameters.cast_data->Cast(); Vector varchar_union(cast_data.type, count); diff --git a/src/duckdb/src/function/cast/variant/from_variant.cpp b/src/duckdb/src/function/cast/variant/from_variant.cpp index a38820cbd..bb910a1bb 100644 --- a/src/duckdb/src/function/cast/variant/from_variant.cpp +++ b/src/duckdb/src/function/cast/variant/from_variant.cpp @@ -594,8 +594,18 @@ static optional TryCastVariantValueToMap(FromVariantConversionData &conve if (entries->IsNull()) { return Value(target_type); } + auto &children = ListValue::GetChildren(*entries); + for (auto &child : children) { + if (child.IsNull()) { + // a NULL entry has no key + if (conversion_data.error.empty()) { + conversion_data.error = "Map keys can not be NULL."; + } + return optional(); + } + } try { - return Value::MAP(entry_type, ListValue::GetChildren(*entries)); + return Value::MAP(entry_type, children); } catch (const InvalidInputException &ex) { // the entries do not form a valid map (e.g. duplicate keys) if (conversion_data.error.empty()) { @@ -680,6 +690,13 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu // Set the value to NULL and mark as failed FlatVector::SetNull(result, offset + i, true); all_valid = false; + } catch (const ConversionException &ex) { + // strict casts of the converted value (e.g. VARCHAR to an ARRAY of a different size) can throw + if (error.empty()) { + error = ErrorData(ex).RawMessage(); + } + FlatVector::SetNull(result, offset + i, true); + all_valid = false; } } if (target_type.id() == LogicalTypeId::MAP) { diff --git a/src/duckdb/src/function/function_binder.cpp b/src/duckdb/src/function/function_binder.cpp index 25f9fc9fd..2d10779e9 100644 --- a/src/duckdb/src/function/function_binder.cpp +++ b/src/duckdb/src/function/function_binder.cpp @@ -1125,6 +1125,8 @@ static string PushArgumentCollations(ClientContext &context, BoundSimpleFunction return string(); case FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS: return PushCollations(context, bound_function, children, CollationType::COMBINABLE_COLLATIONS); + case FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS: + throw InternalException("PUSH_DISTINCT_COLLATIONS is only supported for aggregate functions"); default: throw InternalException("Unrecognized collation handling"); } @@ -1622,6 +1624,10 @@ unique_ptr FunctionBinder::BindScalarFunction(shared_ptr(std::move(bound_function), std::move(children), std::move(bind_info), is_operator); + if (binder && result_func->Function().GetStability() == FunctionStability::CONSISTENT_WITHIN_QUERY) { + // the result depends on the query it is bound in, also when the bind_expression callback folds it + binder->SetAlwaysRequireRebind(); + } if (result_func->Function().HasBindExpressionCallback()) { // if a bind_expression callback is registered - call it and emit the resulting expression FunctionBindExpressionInput input(context, result_func->FunctionMutable(), result_func->BindInfoMutable().get(), @@ -1698,6 +1704,15 @@ FunctionBinder::BindAggregateFunction(shared_ptr functi vector> children, vector>> keyword_args, unique_ptr filter, AggregateType aggr_type) { + if (aggr_type == AggregateType::DISTINCT && + function->GetCollationHandling() == FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { + for (auto &child : children) { + ExpressionBinder::PushCollation(context, child, child->GetReturnType()); + } + for (auto &argument : keyword_args) { + ExpressionBinder::PushCollation(context, argument.second, argument.second->GetReturnType()); + } + } auto [bound_function, bind_info] = ResolveFunction(std::move(function), children, keyword_args); return make_uniq(std::move(bound_function), std::move(children), std::move(filter), diff --git a/src/duckdb/src/function/pragma/pragma_functions.cpp b/src/duckdb/src/function/pragma/pragma_functions.cpp index c86c04875..4ddce15a7 100644 --- a/src/duckdb/src/function/pragma/pragma_functions.cpp +++ b/src/duckdb/src/function/pragma/pragma_functions.cpp @@ -19,9 +19,9 @@ namespace duckdb { -static void PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { +static string PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { auto &config = ClientConfig::GetConfig(context); - config.enable_profiler = true; + return StringUtil::Format("SET enable_profiling=%s", SQLString(config.profiler_print_format)); } void RegisterEnableProfiling(BuiltinFunctions &set) { @@ -33,24 +33,25 @@ void RegisterEnableProfiling(BuiltinFunctions &set) { } static void PragmaDisableProfiling(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); auto &config = ClientConfig::GetConfig(context); config.enable_profiler = false; } -static void PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).enable_progress_bar = true; +static string PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar=true"; } -static void PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).enable_progress_bar = false; +static string PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar=false"; } -static void PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).print_progress_bar = true; +static string PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar_print=true"; } -static void PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).print_progress_bar = false; +static string PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar_print=false"; } //! Warn that a pragma that no longer does anything is on its way out @@ -71,14 +72,17 @@ static void PragmaDisableVerification(ClientContext &context, const FunctionPara } static void PragmaEnableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = true; } static void PragmaForceCheckpoint(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("force_checkpoint"); DBConfig::GetConfig(context).options.force_checkpoint = true; } static void PragmaDisableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = false; } @@ -91,19 +95,21 @@ static void PragmaDisableObjectCache(ClientContext &context, const FunctionParam } static void PragmaEnableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = true; } static void PragmaDisableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = false; } -static void PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(true)); +static string PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + return "SET SESSION enable_optimizer=true"; } -static void PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(false)); +static string PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + return "SET SESSION enable_optimizer=false"; } void PragmaFunctions::RegisterFunction(BuiltinFunctions &set) { diff --git a/src/duckdb/src/function/scalar/generic/getvariable.cpp b/src/duckdb/src/function/scalar/generic/getvariable.cpp index fe1fbb2a9..4eca4fa03 100644 --- a/src/duckdb/src/function/scalar/generic/getvariable.cpp +++ b/src/duckdb/src/function/scalar/generic/getvariable.cpp @@ -57,6 +57,8 @@ ScalarFunction GetVariableFun::GetFunction() { ScalarFunction getvar("getvariable", {}, LogicalType::ANY, nullptr, GetVariableBind, nullptr); getvar.GetSignature().AddParameter("variable_name", LogicalType::VARCHAR); getvar.SetBindExpressionCallback(BindGetVariableExpression); + // the value of the variable can change between queries + getvar.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return getvar; } diff --git a/src/duckdb/src/function/scalar/map/map_contains.cpp b/src/duckdb/src/function/scalar/map/map_contains.cpp index 95624c3a5..5565d0ebd 100644 --- a/src/duckdb/src/function/scalar/map/map_contains.cpp +++ b/src/duckdb/src/function/scalar/map/map_contains.cpp @@ -1,4 +1,5 @@ #include "duckdb/common/vector/map_vector.hpp" +#include "duckdb/function/function_binder.hpp" #include "duckdb/function/scalar/list/contains_or_position.hpp" #include "duckdb/planner/expression/bound_cast_expression.hpp" #include "duckdb/function/scalar/map_functions.hpp" @@ -15,12 +16,52 @@ static void MapContainsFunction(DataChunk &input, ExpressionState &state, Vector ListSearchOp(map_vec, key_vec, arg_vec, result, count); } +static bool TypeHasCollation(const LogicalType &type) { + switch (type.id()) { + case LogicalTypeId::VARCHAR: + return !type.HasAlias() && !StringType::GetCollation(type).empty(); + case LogicalTypeId::LIST: + return TypeHasCollation(ListType::GetChildType(type)); + case LogicalTypeId::ARRAY: + return TypeHasCollation(ArrayType::GetChildType(type)); + default: + return false; + } +} + +static unique_ptr BindMapContainsExpression(FunctionBindExpressionInput &input) { + const auto &key_type = input.children[1]->GetReturnType(); + const auto &map_key_type = MapType::KeyType(input.children[0]->GetReturnType()); + if (!TypeHasCollation(key_type) && !TypeHasCollation(map_key_type)) { + return nullptr; + } + + FunctionBinder function_binder(input.context); + ErrorData error; + vector> children; + children.push_back(std::move(input.children[0])); + auto keys = function_binder.BindScalarFunction(Identifier::DefaultSchema(), "map_keys", std::move(children), error); + if (!keys) { + error.Throw(); + } + children.clear(); + children.push_back(std::move(keys)); + children.push_back(std::move(input.children[1])); + auto result = + function_binder.BindScalarFunction(Identifier::DefaultSchema(), "list_contains", std::move(children), error); + if (!result) { + error.Throw(); + } + return result; +} + ScalarFunction MapContainsFun::GetFunction() { auto key_type = LogicalType::TEMPLATE("K"); auto val_type = LogicalType::TEMPLATE("V"); ScalarFunction fun("map_contains", {}, LogicalType::BOOLEAN, MapContainsFunction); fun.GetSignature().AddParameter("map", LogicalType::MAP(key_type, val_type)).AddParameter("key", key_type); + fun.SetBindExpressionCallback(BindMapContainsExpression); return fun; } diff --git a/src/duckdb/src/function/scalar/operator/arithmetic.cpp b/src/duckdb/src/function/scalar/operator/arithmetic.cpp index ca3983d28..bdc3e8e01 100644 --- a/src/duckdb/src/function/scalar/operator/arithmetic.cpp +++ b/src/duckdb/src/function/scalar/operator/arithmetic.cpp @@ -472,14 +472,16 @@ static void BignumAdd(DataChunk &args, ExpressionState &state, Vector &result) { static void BignumSubtract(DataChunk &args, ExpressionState &state, Vector &result) { auto &heap = StringVector::GetStringHeap(result); + // negate a copy of the right operand - the input must not be modified, it can e.g. also be the left operand + vector negated_rhs; BinaryExecutor::Execute( args.data[0], args.data[1], result, [&](bignum_t a, bignum_t b) { const BignumIntermediate lhs(a); - BignumIntermediate rhs(b); + negated_rhs.resize(b.data.GetSize()); + memcpy(negated_rhs.data(), b.data.GetData(), b.data.GetSize()); + BignumIntermediate rhs(negated_rhs.data(), negated_rhs.size()); rhs.NegateInPlace(); - auto result_value = BignumIntermediate::Add(heap, lhs, rhs); - rhs.NegateInPlace(); - return result_value; + return BignumIntermediate::Add(heap, lhs, rhs); }); } @@ -550,7 +552,9 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi function.SetFallible(); // INTERVAL ordering uses 30-day months but DATE arithmetic uses calendar months, // e.g. '2025-02-01' + INTERVAL '29 days' = '2025-03-02' > + INTERVAL '1 month' = '2025-03-01'. - function.SetArgProperties({inc, unset}); + // Adding months clamps the day, e.g. both '2000-01-30' and '2000-01-31' + INTERVAL '1 month' are + // '2000-02-29', so this is not strictly increasing in the DATE. + function.SetArgProperties({ArgProperties().NonDecreasing(), unset}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, @@ -586,7 +590,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({unset, inc}); + function.SetArgProperties({unset, ArgProperties().NonDecreasing()}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { // TIME +/- INTERVAL wraps modulo 24h. @@ -604,7 +608,8 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({unset, inc}); + // not monotonic: adding months clamps the day but keeps the time, e.g. + // '2000-01-30 23:00' + INTERVAL '1 month' > '2000-01-31 01:00' + INTERVAL '1 month' return function; } break; @@ -640,7 +645,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({inc, unset}); + // not monotonic: see INTERVAL + TIMESTAMP return function; } break; @@ -874,7 +879,8 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const function.SetFallible(); // Monotonic in DATE only; INTERVAL ordering uses 30-day months but DATE arithmetic uses // calendar months so monotonicity in the INTERVAL arg is broken (see OperatorAddFun). - function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); + // Subtracting months clamps the day, so this is not strictly increasing in the DATE. + function.SetArgProperties({ArgProperties().NonDecreasing(), ArgProperties()}); return function; } break; @@ -891,8 +897,9 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const "-", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - // Monotonic in TIMESTAMP only; see DATE - INTERVAL above. - function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); + // not monotonic: subtracting months clamps the day but keeps the time, e.g. + // '2000-03-30 23:00' - INTERVAL '1 month' > '2000-03-31 01:00' - INTERVAL '1 month' + return function; } break; diff --git a/src/duckdb/src/function/scalar/sequence/nextval.cpp b/src/duckdb/src/function/scalar/sequence/nextval.cpp index c8bd13b70..b8257b420 100644 --- a/src/duckdb/src/function/scalar/sequence/nextval.cpp +++ b/src/duckdb/src/function/scalar/sequence/nextval.cpp @@ -157,6 +157,16 @@ void NextValModifiedDatabases(ClientContext &context, FunctionModifiedDatabasesI input.properties.RegisterDBModify(seq.sequence.ParentCatalog(), context, DatabaseModificationType::SEQUENCE); } +//! currval reads the sequence but holds a reference to its catalog entry, so a prepared statement must be rebound +//! when the catalog changes (e.g. the sequence is dropped) without marking the database as modified +void CurrValReadDatabases(ClientContext &context, FunctionModifiedDatabasesInput &input) { + if (!input.bind_data) { + return; + } + auto &seq = input.bind_data->Cast(); + input.properties.RegisterDBRead(seq.sequence.ParentCatalog(), context); +} + } // namespace ScalarFunction NextvalFun::GetFunction() { @@ -181,6 +191,7 @@ ScalarFunction CurrvalFun::GetFunction() { curr_val.SetBindCallback(NextValBind); curr_val.SetSerializeCallback(Serialize); curr_val.SetDeserializeCallback(Deserialize); + curr_val.SetModifiedDatabasesCallback(CurrValReadDatabases); curr_val.SetInitStateCallback(NextValLocalFunction); curr_val.SetVolatile(); curr_val.SetFallible(); diff --git a/src/duckdb/src/function/scalar/string/contains.cpp b/src/duckdb/src/function/scalar/string/contains.cpp index a980ce1fc..dece4681e 100644 --- a/src/duckdb/src/function/scalar/string/contains.cpp +++ b/src/duckdb/src/function/scalar/string/contains.cpp @@ -32,13 +32,6 @@ struct ContainsUnaligned { } }; -struct ContainsGeneric { - template - static bool MatchRemainder(const unsigned char *haystack, const unsigned char *needle, idx_t needle_size) { - return memcmp(haystack + sizeof(UNSIGNED), needle + sizeof(UNSIGNED), needle_size - sizeof(UNSIGNED)) == 0; - } -}; - template static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, idx_t needle_size, idx_t base_offset) { @@ -67,6 +60,66 @@ static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const return DConstants::INVALID_INDEX; } +//! Linear-time (Knuth-Morris-Pratt) search, used when the needle keeps almost-matching the haystack +static idx_t ContainsLinear(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, + idx_t needle_size, idx_t base_offset) { + // prefix[i] is the length of the longest proper prefix of needle[0..i] that is also a suffix of it + vector prefix(needle_size, 0); + for (idx_t i = 1, k = 0; i < needle_size; i++) { + while (k > 0 && needle[i] != needle[k]) { + k = prefix[k - 1]; + } + if (needle[i] == needle[k]) { + k++; + } + prefix[i] = k; + } + for (idx_t i = 0, k = 0; i < haystack_size; i++) { + while (k > 0 && haystack[i] != needle[k]) { + k = prefix[k - 1]; + } + if (haystack[i] == needle[k]) { + k++; + } + if (k == needle_size) { + return base_offset + i + 1 - needle_size; + } + } + return DConstants::INVALID_INDEX; +} + +//! Like Contains, but every candidate position compares up to needle_size bytes, which is +//! quadratic when the needle keeps almost-matching - switch to a linear-time search once that happens +static idx_t ContainsLong(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, + idx_t needle_size, idx_t base_offset) { + if (needle_size > haystack_size) { + return DConstants::INVALID_INDEX; + } + const idx_t candidate_end = haystack_size - (needle_size - 1); + auto needle_entry = Load(needle); + // the number of candidates whose remainder we compare before switching, roughly linear in the haystack + const idx_t max_full_compares = 16 + (haystack_size * 8) / needle_size; + idx_t full_compares = 0; + for (idx_t offset = 0; offset < candidate_end; offset++) { + auto location = + static_cast(memchr(haystack + offset, needle[0], candidate_end - offset)); + if (!location) { + return DConstants::INVALID_INDEX; + } + offset = UnsafeNumericCast(location - haystack); + if (Load(location) != needle_entry) { + continue; + } + if (memcmp(location + sizeof(uint64_t), needle + sizeof(uint64_t), needle_size - sizeof(uint64_t)) == 0) { + return base_offset + offset; + } + if (++full_compares > max_full_compares) { + return ContainsLinear(location, haystack_size - offset, needle, needle_size, base_offset + offset); + } + } + return DConstants::INVALID_INDEX; +} + struct ContainsOperator { template static inline TR Operation(TA left, TB right) { @@ -105,7 +158,7 @@ idx_t FindStrInStr(const unsigned char *haystack, idx_t haystack_size, const uns case 8: return Contains(haystack, haystack_size, needle, 8, base_offset); default: - return Contains(haystack, haystack_size, needle, needle_size, base_offset); + return ContainsLong(haystack, haystack_size, needle, needle_size, base_offset); } } diff --git a/src/duckdb/src/function/scalar/string/length.cpp b/src/duckdb/src/function/scalar/string/length.cpp index 8fddb4375..e1e3e440e 100644 --- a/src/duckdb/src/function/scalar/string/length.cpp +++ b/src/duckdb/src/function/scalar/string/length.cpp @@ -1,6 +1,8 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/numeric_utils.hpp" #include "duckdb/common/types/bit.hpp" +#include "duckdb/common/vector/vector_iterator.hpp" +#include "duckdb/common/vector/vector_writer.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/storage/statistics/base_statistics.hpp" #include "duckdb/storage/statistics/numeric_stats.hpp" @@ -220,13 +222,24 @@ void ArrayLengthBinaryFunction(DataChunk &args, ExpressionState &state, Vector & auto &dimensions = data.dimensions; auto max_dimension = static_cast(dimensions.size()); - UnaryExecutor::Execute(dimension, result, [&](int64_t dimension) { - if (dimension < 1 || dimension > max_dimension) { + // the result is NULL for NULL arrays + const auto count = args.size(); + auto array_validity = args.data[0].Validity(); + auto dimension_values = dimension.Values(); + auto writer = FlatVector::Writer(result, count); + for (idx_t i = 0; i < count; i++) { + auto entry = dimension_values[i]; + if (!array_validity.IsValid(i) || !entry.IsValid()) { + writer.WriteNull(); + continue; + } + auto dim = entry.GetValue(); + if (dim < 1 || dim > max_dimension) { throw OutOfRangeException(StringUtil::Format( - "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dimension, max_dimension)); + "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dim, max_dimension)); } - return dimensions[UnsafeNumericCast(dimension - 1)]; - }); + writer.WriteValue(dimensions[UnsafeNumericCast(dim - 1)]); + } } void ArrayOrListLengthBinaryResolveTypes(ResolveScalarFunctionTypesInput &input) { diff --git a/src/duckdb/src/function/scalar/string/regexp.cpp b/src/duckdb/src/function/scalar/string/regexp.cpp index cdc2b5edb..8143e451b 100644 --- a/src/duckdb/src/function/scalar/string/regexp.cpp +++ b/src/duckdb/src/function/scalar/string/regexp.cpp @@ -415,6 +415,15 @@ static unique_ptr RegexExtractBind(BindScalarFunctionInput &input) } } + if (constant_pattern && constant_string.find("\\C") == string::npos) { + auto validation_options = options; + validation_options.set_log_errors(false); + if (RE2(constant_string, validation_options).ok()) { + // A valid UTF-8 pattern without byte escapes cannot return a partial character. + bound_function.SetErrorMode(FunctionErrors::CANNOT_ERROR); + } + } + return make_uniq(options, std::move(constant_string), constant_pattern, group_index, no_match_returns_input); } diff --git a/src/duckdb/src/function/scalar/system/aggregate_export.cpp b/src/duckdb/src/function/scalar/system/aggregate_export.cpp index 7a598344f..d43e1de71 100644 --- a/src/duckdb/src/function/scalar/system/aggregate_export.cpp +++ b/src/duckdb/src/function/scalar/system/aggregate_export.cpp @@ -754,10 +754,14 @@ unique_ptr BindToAggregateState(INPUT &input) { "constant"); } } - auto function_name_val = input.GetNonNullConstant(1); + // the arguments are not yet cast to the parameter types when binding + auto function_name_val = input.GetNonNullConstant(1).DefaultCastAs(LogicalType::VARCHAR); auto function_name = StringValue::Get(function_name_val); auto signature_val = input.GetNonNullConstant(2); + if (signature_val.type().id() != LogicalTypeId::LIST) { + throw BinderException("to_aggregate_state: the signature must be a list of types"); + } // the signature lists all of the argument types in order vector argument_types; for (auto &arg : ListValue::GetChildren(signature_val)) { @@ -905,6 +909,20 @@ unique_ptr ExportAggregateFunction::StateToSQL(const LogicalTy return make_uniq(QualifiedName("system", "main", "to_aggregate_state"), std::move(arguments)); } +void ExportAggregateFunction::GetSortedStateOrders(const LogicalType &state_type, + vector &orders) { + auto ext_info = state_type.GetExtensionInfo(); + if (!ext_info || state_type.id() != LogicalTypeId::LIST) { + throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); + } + auto order_entry = ext_info->properties.find("order_bys"); + if (order_entry == ext_info->properties.end()) { + throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); + } + auto &buffer_struct = ListType::GetChildType(state_type); + ParseOrderBys(order_entry->second, StructType::GetChildTypes(buffer_struct).size(), orders); +} + void ExportAggregateFunction::SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout) { auto &bound_function = aggregate.FunctionMutable(); // functions with an explicit export callback use it as the finalize; others use the field-based serialization diff --git a/src/duckdb/src/function/table/arrow_conversion.cpp b/src/duckdb/src/function/table/arrow_conversion.cpp index 89a30178f..b2bbae167 100644 --- a/src/duckdb/src/function/table/arrow_conversion.cpp +++ b/src/duckdb/src/function/table/arrow_conversion.cpp @@ -48,6 +48,50 @@ T *ArrowBufferData(ArrowArray &array, idx_t buffer_idx) { return (T *)array.buffers[buffer_idx]; // NOLINT } +static bool IsAligned(const_data_ptr_t ptr, idx_t alignment) { + return reinterpret_cast(ptr) % alignment == 0; +} + +//! Arrow buffers are not required to be aligned to their value type, and reading an unaligned buffer through a +//! typed pointer is undefined behavior. This reads the values in place when they are aligned, and from an aligned +//! copy of the [offset, offset + count) range when they are not. +template +class ArrowAlignedBufferData { +public: + ArrowAlignedBufferData(const ArrowArray &array, idx_t buffer_idx, idx_t offset, idx_t count) { + auto source = static_cast(array.buffers[buffer_idx]) + offset * sizeof(T); + if (IsAligned(source, alignof(T))) { + data = reinterpret_cast(source); + return; + } + copy = make_unsafe_uniq_array_uninitialized(count); + memcpy(static_cast(copy.get()), source, count * sizeof(T)); + data = copy.get(); + } + + const T &operator[](idx_t idx) const { + return data[idx]; + } + const T *get() const { + return data; + } + +private: + const T *data; + unsafe_unique_array copy; +}; + +//! Points the vector to the values when they are aligned to their type, and copies them into it when they are not +static void SetVectorData(Vector &vector, data_ptr_t data, idx_t size) { + auto type_size = GetTypeIdSize(vector.GetType().InternalType()); + if (IsAligned(data, MinValue(type_size, sizeof(uint64_t)))) { + FlatVector::SetData(vector, data, count_t(size)); + return; + } + memcpy(FlatVector::GetDataMutable(vector), data, type_size * size); + FlatVector::SetSize(vector, size); +} + static void GetValidityMask(ValidityMask &mask, ArrowArray &array, idx_t chunk_offset, idx_t size, int64_t parent_offset, int64_t nested_offset = -1, bool add_null = false) { // In certain we don't need to or cannot copy arrow's validity mask to duckdb. @@ -126,7 +170,7 @@ static ArrowListOffsetData ConvertArrowListOffsetsTemplated(Vector &vector, Arro } idx_t cur_offset = 0; - auto offsets = ArrowBufferData(array, 1) + effective_offset; + ArrowAlignedBufferData offsets(array, 1, effective_offset, size + 1); start_offset = offsets[0]; auto list_data = FlatVector::Writer(vector, size); for (idx_t i = 0; i < size; i++) { @@ -149,8 +193,8 @@ static ArrowListOffsetData ConvertArrowListViewOffsetsTemplated(Vector &vector, auto &list_size = result.list_size; list_size = 0; - auto offsets = ArrowBufferData(array, 1) + effective_offset; - auto sizes = ArrowBufferData(array, 2) + effective_offset; + ArrowAlignedBufferData offsets(array, 1, effective_offset, size); + ArrowAlignedBufferData sizes(array, 2, effective_offset, size); // In ListArrays the offsets have to be sequential // ListViewArrays do not have this same constraint @@ -342,7 +386,7 @@ static void ArrowToDuckDBMapVerify(const Vector &vector, idx_t count) { } template -static void SetVectorString(Vector &vector, idx_t size, char *cdata, T *offsets) { +static void SetVectorString(Vector &vector, idx_t size, char *cdata, const T *offsets) { auto strings = FlatVector::GetDataMutable(vector); for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -359,7 +403,7 @@ static void SetVectorString(Vector &vector, idx_t size, char *cdata, T *offsets) static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, idx_t current_pos) { auto strings = FlatVector::Writer(vector, size); - auto arrow_string = ArrowBufferData(array, 1) + current_pos; + ArrowAlignedBufferData arrow_string(array, 1, current_pos, size); for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -372,7 +416,7 @@ static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, i // | Bytes 0-3 | Bytes 4-15 | // |------------|---------------------------------------| // | length | data (padded with 0) | - strings.WriteStringRef(string_t(arrow_string[row_idx].GetInlineData(), length)); + strings.WriteValue(string_t(arrow_string[row_idx].GetInlineData(), length)); } else { // This string is not inlined, we have to check a different buffer and offsets // | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | @@ -393,7 +437,7 @@ static void DirectConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs auto data_ptr = ArrowBufferData(array, 1) + internal_type * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data_ptr, count_t(size)); + SetVectorData(vector, data_ptr, size); } template @@ -401,8 +445,8 @@ static void TimeConversion(Vector &vector, ArrowArray &array, idx_t chunk_offset int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_writer = FlatVector::Writer(vector, size); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), + size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { int64_t result; @@ -431,8 +475,8 @@ static void TimeNSConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), + size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { // dtime_ns_t.micros actually holds nanos (!) @@ -457,8 +501,8 @@ static void UUIDConversion(Vector &vector, const ArrowArray &array, idx_t chunk_ int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { tgt_ptr[row].lower = static_cast(BSwapIfLE(src_ptr[row].upper)); @@ -483,8 +527,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { if (!TryMultiplyOperator::Operation(src_ptr[row], conversion, tgt_ptr[row].value)) { @@ -506,8 +550,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].months = 0; @@ -520,8 +564,8 @@ static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].micros = 0; @@ -532,8 +576,8 @@ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t ch static void IntervalConversionMonthDayNanos(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = src_ptr[row].days; tgt_ptr[row].micros = src_ptr[row].nanoseconds / Interval::NANOS_PER_MICRO; @@ -758,9 +802,15 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBRunEndEncoded(Vector &vector, c } } template -void ConvertDecimal(SRC src_ptr, Vector &vector, ArrowArray &array, idx_t size, int64_t nested_offset, - uint64_t parent_offset, idx_t chunk_offset, ValidityMask &val_mask, - DecimalBitWidth arrow_bit_width) { +static void ConvertDecimal(Vector &vector, ArrowArray &array, idx_t size, idx_t offset) { + if (sizeof(SRC) == GetTypeIdSize(vector.GetType().InternalType())) { + auto data = ArrowBufferData(array, 1) + offset * sizeof(SRC); + SetVectorData(vector, data, size); + return; + } + + ArrowAlignedBufferData src_ptr(array, 1, offset, size); + auto &val_mask = FlatVector::Validity(vector); switch (vector.GetType().InternalType()) { case PhysicalType::INT16: { auto tgt_ptr = FlatVector::GetDataMutable(vector); @@ -774,58 +824,36 @@ void ConvertDecimal(SRC src_ptr, Vector &vector, ArrowArray &array, idx_t size, break; } case PhysicalType::INT32: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_32) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } break; } case PhysicalType::INT64: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_64) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } break; } case PhysicalType::INT128: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_128) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } - break; } default: @@ -918,16 +946,18 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar switch (size_type) { case ArrowVariableSizeType::SUPER_SIZE: { auto cdata = ArrowBufferData(array, 2); - auto offsets = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - SetVectorString(vector, size, cdata, offsets); + ArrowAlignedBufferData offsets( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size + 1); + SetVectorString(vector, size, cdata, offsets.get()); break; } case ArrowVariableSizeType::NORMAL: { auto cdata = ArrowBufferData(array, 2); - auto offsets = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - SetVectorString(vector, size, cdata, offsets); + ArrowAlignedBufferData offsets( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size + 1); + SetVectorString(vector, size, cdata, offsets.get()); break; } case ArrowVariableSizeType::VIEW: { @@ -968,8 +998,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::MILLISECONDS: { //! convert date from nanoseconds to days - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); auto tgt_ptr = FlatVector::GetDataMutable(vector); for (idx_t row = 0; row < size; row++) { tgt_ptr[row] = date_t(UnsafeNumericCast(static_cast(src_ptr[row]) / @@ -1003,8 +1034,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1064,8 +1096,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1122,8 +1155,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].micros = src_ptr[row] / 1000; tgt_ptr[row].days = 0; @@ -1147,34 +1181,18 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar break; } case LogicalTypeId::DECIMAL: { - auto &val_mask = FlatVector::ValidityMutable(vector); auto &datetime_info = arrow_type.GetTypeInfo(); - auto bit_width = datetime_info.GetBitWidth(); - - switch (bit_width) { - case DecimalBitWidth::DECIMAL_32: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + auto offset = GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + switch (datetime_info.GetBitWidth()) { + case DecimalBitWidth::DECIMAL_32: + ConvertDecimal(vector, array, size, offset); break; - } - - case DecimalBitWidth::DECIMAL_64: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + case DecimalBitWidth::DECIMAL_64: + ConvertDecimal(vector, array, size, offset); break; - } - - case DecimalBitWidth::DECIMAL_128: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + case DecimalBitWidth::DECIMAL_128: + ConvertDecimal(vector, array, size, offset); break; - } default: throw NotImplementedException("Unsupported precision for Arrow Decimal Type."); } @@ -1488,9 +1506,16 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBDictionary(Vector &vector, Arro } auto offset_type = arrow_type.GetDuckType(); //! Get Pointer to Indices of Dictionary - auto indices = ArrowBufferData(array, 1) + - GetTypeIdSize(offset_type.InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + auto index_size = GetTypeIdSize(offset_type.InternalType()); + auto indices = + ArrowBufferData(array, 1) + + index_size * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + unsafe_unique_array aligned_indices; + if (!IsAligned(indices, index_size)) { + aligned_indices = make_unsafe_uniq_array_uninitialized((index_size * size + 7) / 8); + memcpy(aligned_indices.get(), indices, index_size * size); + indices = reinterpret_cast(aligned_indices.get()); + } SelectionVector sel; if (has_nulls) { diff --git a/src/duckdb/src/function/table/checkpoint.cpp b/src/duckdb/src/function/table/checkpoint.cpp index a59e3dd9d..c696a4d4d 100644 --- a/src/duckdb/src/function/table/checkpoint.cpp +++ b/src/duckdb/src/function/table/checkpoint.cpp @@ -11,7 +11,7 @@ struct CheckpointBindData : public FunctionData { explicit CheckpointBindData(optional_ptr db) : db(db) { } - optional_ptr db; + mutable optional_ptr db; public: unique_ptr Copy() const override { @@ -49,7 +49,7 @@ static unique_ptr CheckpointBind(ClientContext &context, TableFunc template static void TemplatedCheckpointFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { auto &bind_data = data_p.bind_data->Cast(); - auto &transaction_manager = TransactionManager::Get(*bind_data.db.get_mutable()); + auto &transaction_manager = TransactionManager::Get(*bind_data.db); transaction_manager.Checkpoint(context, FORCE); } diff --git a/src/duckdb/src/function/table/range.cpp b/src/duckdb/src/function/table/range.cpp index 8af91754c..038189e31 100644 --- a/src/duckdb/src/function/table/range.cpp +++ b/src/duckdb/src/function/table/range.cpp @@ -262,7 +262,8 @@ struct RangeDateTimeBindData : public TableFunctionData { return; } - cardinality = idx_t(delta / increment); + // a step pointing away from the end produces an empty range + cardinality = delta / increment < 0 ? 0 : idx_t(delta / increment); } idx_t cardinality; diff --git a/src/duckdb/src/function/table/system/create_external_resource.cpp b/src/duckdb/src/function/table/system/create_external_resource.cpp index 046a3678c..65a585e5f 100644 --- a/src/duckdb/src/function/table/system/create_external_resource.cpp +++ b/src/duckdb/src/function/table/system/create_external_resource.cpp @@ -212,6 +212,11 @@ static void CreateExternalResourceFunction(ClientContext &context, TableFunction type->create_function); } handle = RequireResourceMap(result->Collection().GetRows().GetValue(0, 0), type->create_function, "handle"); + if (handle.IsNull()) { + // the handle identifies the resource for status and teardown - without one it can never be destroyed + throw InvalidInputException("create_external_resource: create function \"%s\" returned a NULL handle", + type->create_function); + } LogExternalResourceOperation(context, bind_data.type_name, bind_data.resource_name, "create", string(), NoExtraInfo()); } catch (std::exception &ex) { diff --git a/src/duckdb/src/function/table/system/enable_profiling.cpp b/src/duckdb/src/function/table/system/enable_profiling.cpp index 0b830beb6..f5979b71e 100644 --- a/src/duckdb/src/function/table/system/enable_profiling.cpp +++ b/src/duckdb/src/function/table/system/enable_profiling.cpp @@ -1,5 +1,6 @@ #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/client_context.hpp" +#include "duckdb/main/config.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" @@ -89,6 +90,21 @@ static unique_ptr BindEnableProfiling(ClientContext &context, Tabl // The metrics are placed by position however they were passed; the overload settled pattern or list of names bind_data->metrics = input.inputs[0]; + auto &config = DBConfig::GetConfig(context); + config.CheckLock(EnableProfilingSetting::Name); + if (!bind_data->coverage.IsNull()) { + config.CheckLock(ProfilingCoverageSetting::Name); + } + if (!bind_data->save_location.IsNull()) { + config.CheckLock(ProfilingOutputSetting::Name); + } + if (!bind_data->mode.IsNull()) { + config.CheckLock(ProfilingModeSetting::Name); + } + if (!bind_data->metrics.IsNull()) { + config.CheckLock(TrackedMetricsSetting::Name); + } + return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); @@ -102,6 +118,7 @@ static void DisableProfiling(ClientContext &context, TableFunctionInput &data, D static unique_ptr BindDisableProfiling(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { + DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/system/external_resources.cpp b/src/duckdb/src/function/table/system/external_resources.cpp index faef29aa9..c612011ed 100644 --- a/src/duckdb/src/function/table/system/external_resources.cpp +++ b/src/duckdb/src/function/table/system/external_resources.cpp @@ -241,6 +241,9 @@ static unique_ptr RegisterExternalResourceBind(ClientContext &cont if (input.inputs[0].IsNull() || input.inputs[1].IsNull()) { throw InvalidInputException("register_external_resource: the type and name must not be NULL"); } + if (input.inputs[2].IsNull()) { + throw InvalidInputException("register_external_resource: the handle must not be NULL"); + } resource.type = StringValue::Get(input.inputs[0]); resource.name = StringValue::Get(input.inputs[1]); // The handle is the durable identity and doubles as the deleter payload. diff --git a/src/duckdb/src/function/table/system/logging_utils.cpp b/src/duckdb/src/function/table/system/logging_utils.cpp index 198e83b5d..1b1a0004d 100644 --- a/src/duckdb/src/function/table/system/logging_utils.cpp +++ b/src/duckdb/src/function/table/system/logging_utils.cpp @@ -6,6 +6,8 @@ #include "duckdb/logging/log_manager.hpp" #include "duckdb/logging/logging.hpp" #include "duckdb/main/client_context.hpp" +#include "duckdb/main/config.hpp" +#include "duckdb/main/settings.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_entries.hpp" #include "duckdb/main/extension_helper.hpp" @@ -68,6 +70,13 @@ static unique_ptr BindEnableLogging(ClientContext &context, TableF throw InvalidInputException("EnableLogging: expected 0 or 1 parameter"); } + auto &config = DBConfig::GetConfig(context); + config.CheckLock(EnableLogging::Name); + config.CheckLock(EnabledLogTypes::Name); + config.CheckLock(LoggingLevel::Name); + config.CheckLock(LoggingMode::Name); + config.CheckLock(LoggingStorage::Name); + auto result = make_uniq(); bool storage_isset = false; @@ -170,6 +179,7 @@ static void TruncateLogs(ClientContext &context, TableFunctionInput &data, DataC static unique_ptr BindDisableLogging(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { + DBConfig::GetConfig(context).CheckLock(EnableLogging::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index c0db64a9e..cde76bec0 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha47600" +#define DUCKDB_PATCH_VERSION "0-alpha48256" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha47600" +#define DUCKDB_VERSION "v2.0.0-alpha48256" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "d4355d01c5" +#define DUCKDB_SOURCE_ID "8588363bf1" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/variant/variant_shredding.cpp b/src/duckdb/src/function/variant/variant_shredding.cpp index ea748f388..f3f511ff7 100644 --- a/src/duckdb/src/function/variant/variant_shredding.cpp +++ b/src/duckdb/src/function/variant/variant_shredding.cpp @@ -343,10 +343,10 @@ bool VariantShreddingState::ValueIsShredded(UnifiedVariantVectorData &variant, i return false; } if (type_id == VariantLogicalType::DECIMAL) { - auto physical_type = type.InternalType(); + // the shredded column stores the unscaled value, so it only holds decimals of its scale that fit its width auto decimal_data = VariantUtils::DecodeDecimalData(variant, row, values_index); - auto decimal_physical_type = decimal_data.GetPhysicalType(); - return physical_type == decimal_physical_type; + return type.InternalType() == decimal_data.GetPhysicalType() && + decimal_data.scale == DecimalType::GetScale(type) && decimal_data.width <= DecimalType::GetWidth(type); } return true; } diff --git a/src/duckdb/src/function/window/window_aggregator.cpp b/src/duckdb/src/function/window/window_aggregator.cpp index 3f2846917..d034c2619 100644 --- a/src/duckdb/src/function/window/window_aggregator.cpp +++ b/src/duckdb/src/function/window/window_aggregator.cpp @@ -33,7 +33,9 @@ WindowAggregatorGlobalState::WindowAggregatorGlobalState(ClientContext &client, locals(0), finalized(0) { if (aggr.filter) { // Start with all invalid and set the ones that pass - filter_mask.Initialize(group_count, false); + filter_mask_data = BufferAllocator::Get(client).Allocate(group_count * sizeof(bool)); + memset(filter_mask_data.get(), 0, filter_mask_data.GetSize()); + filter_mask.Initialize(reinterpret_cast(filter_mask_data.get()), group_count); } else { filter_mask.InitializeEmpty(group_count); } diff --git a/src/duckdb/src/function/window/window_value_function.cpp b/src/duckdb/src/function/window/window_value_function.cpp index a9735aa08..6d9e66ecc 100644 --- a/src/duckdb/src/function/window/window_value_function.cpp +++ b/src/duckdb/src/function/window/window_value_function.cpp @@ -628,6 +628,19 @@ unique_ptr WindowLeadLagExecutor::GetLocal(ExecutionContext &con return make_uniq(context, glstate); } +//! The index of the row at the offset - saturated if it does not fit, as it is outside the partition anyway +static int64_t LeadLagIndex(ExpressionType type, int64_t row_idx, int64_t offset) { + int64_t result; + if (type == ExpressionType::WINDOW_LEAD) { + if (!TryAddOperator::Operation(row_idx, offset, result)) { + result = offset < 0 ? NumericLimits::Minimum() : NumericLimits::Maximum(); + } + } else if (!TrySubtractOperator::Operation(row_idx, offset, result)) { + result = offset < 0 ? NumericLimits::Maximum() : NumericLimits::Minimum(); + } + return result; +} + void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_chunk, DataChunk &bounds, Vector &result, idx_t row_idx, OperatorSinkInput &sink) { auto &glstate = sink.global_state.Cast(); @@ -668,12 +681,7 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c frame = FrameBounds(frame_begin[i], MaxValue(frame_end[i], frame_begin[i])); const auto own_row = glstate.row_tree->Rank(frame.start, frame.end, row_idx) - 1; // (2) adjust the row number by adding or subtracting an offset - auto val_idx = NumericCast(own_row); - if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { - val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); - } else { - val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); - } + const auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(own_row), offset); const auto frame_width = NumericCast(frame.end - frame.start); if (val_idx >= 0 && val_idx < frame_width) { // (3) find the row at that offset @@ -727,12 +735,7 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c } offset = leadlag_offset.GetCell(i); } - int64_t val_idx = (int64_t)row_idx; - if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { - val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); - } else { - val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); - } + auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(row_idx), offset); idx_t delta = 0; if (val_idx < (int64_t)row_idx) { @@ -1546,7 +1549,8 @@ void WindowFillExecutor::GetSharing(WindowExecutor &executor, WindowSharedExpres if (arg_order_idx.empty()) { // We use the range ordering, even if it has not been defined if (executor.range_idx == DConstants::INVALID_INDEX) { - D_ASSERT(wexpr.OrderBy().size() == 1); + // the first ordering - also when it is shared with an argument ordering that is a prefix of the orderings + D_ASSERT(!wexpr.OrderBy().empty()); // We don't need the validity mask because we have also requested the valid range for the ordering. executor.range_idx = shared.RegisterCollection(wexpr.OrderBy()[0].expression, false); } diff --git a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp index cc7ef078c..b859af388 100644 --- a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +++ b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp @@ -64,6 +64,8 @@ class SequenceCatalogEntry : public StandardEntry { int64_t NextValue(DuckTransaction &transaction); int64_t SetValue(DuckTransaction &transaction, int64_t value, bool is_called); void ReplayValue(uint64_t usage_count, int64_t counter, optional last_value); + //! Derive the last value from the counter - storage versions prior to v2.0.0 do not store it + void DeriveLastValue(); string ToSQL() const override; diff --git a/src/duckdb/src/include/duckdb/common/allocator.hpp b/src/duckdb/src/include/duckdb/common/allocator.hpp index 12f9eb5fb..c69fbe2cf 100644 --- a/src/duckdb/src/include/duckdb/common/allocator.hpp +++ b/src/duckdb/src/include/duckdb/common/allocator.hpp @@ -125,6 +125,8 @@ class Allocator { DUCKDB_API static Allocator &DefaultAllocator(); DUCKDB_API static shared_ptr &DefaultAllocatorReference(); + //! Total mapped allocator memory, if supported by this allocator. + optional_idx GetMappedMemory() const; static bool SupportsFlush(); static optional_idx DecayDelay(); static void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count); diff --git a/src/duckdb/src/include/duckdb/common/enum_util.hpp b/src/duckdb/src/include/duckdb/common/enum_util.hpp index 6a2297a91..5159f95e2 100644 --- a/src/duckdb/src/include/duckdb/common/enum_util.hpp +++ b/src/duckdb/src/include/duckdb/common/enum_util.hpp @@ -292,6 +292,8 @@ enum class HLLStorageType : uint8_t; enum class HTTPClientCachePolicy : uint8_t; +enum class HTTPRetryOutcomeOwner : uint8_t; + enum class HTTPStatusCode : uint16_t; enum class HTTPTransportReusePolicy : uint8_t; @@ -1059,6 +1061,9 @@ const char* EnumUtil::ToChars(HLLStorageType value); template<> const char* EnumUtil::ToChars(HTTPClientCachePolicy value); +template<> +const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value); + template<> const char* EnumUtil::ToChars(HTTPStatusCode value); @@ -2014,6 +2019,9 @@ HLLStorageType EnumUtil::FromString(const char *value); template<> HTTPClientCachePolicy EnumUtil::FromString(const char *value); +template<> +HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value); + template<> HTTPStatusCode EnumUtil::FromString(const char *value); diff --git a/src/duckdb/src/include/duckdb/common/enums/current_transaction_state.hpp b/src/duckdb/src/include/duckdb/common/enums/current_transaction_state.hpp deleted file mode 100644 index aff42da2a..000000000 --- a/src/duckdb/src/include/duckdb/common/enums/current_transaction_state.hpp +++ /dev/null @@ -1,15 +0,0 @@ -//===----------------------------------------------------------------------===// -// DuckDB -// -// duckdb/common/enums/current_transaction_state.hpp -// -// -//===----------------------------------------------------------------------===// - -#pragma once - -namespace duckdb { - -enum CurrentTransactionState { IN_ACTIVE_TRANSACTION, NOT_IN_ACTIVE_TRANSACTION }; - -} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp index b0d0e9c2d..10617fde2 100644 --- a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp +++ b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp @@ -23,6 +23,11 @@ class HTTPException : public Exception { explicit HTTPException(const string &message) : Exception(ExceptionType::HTTP, message) { } + //! Rebuild an HTTP exception from extra info captured earlier (e.g. to extend its message). + explicit HTTPException(const unordered_map &extra_info, const string &message) + : Exception(extra_info, ExceptionType::HTTP, message) { + } + template ::status = 0, typename... ARGS> explicit HTTPException(RESPONSE &response, const string &msg, ARGS &&...params) : HTTPException(static_cast(response.status), response.body, response.headers, response.reason, msg, diff --git a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp index 534020b92..a5b0d97fc 100644 --- a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp +++ b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/identifier.hpp" #include "duckdb/common/types/column/partitioned_column_data.hpp" #include "duckdb/common/open_file_info.hpp" #include "duckdb/original/std/sstream.hpp" @@ -18,7 +19,8 @@ namespace duckdb { struct MultiFilePushdownInfo; struct HivePartitioningFilterInfo { - unordered_map column_map; + identifier_map_t column_map; + Identifier filename_column; bool hive_enabled; bool filename_enabled; }; diff --git a/src/duckdb/src/include/duckdb/common/hugeint.hpp b/src/duckdb/src/include/duckdb/common/hugeint.hpp index acdc4fb4b..844106506 100644 --- a/src/duckdb/src/include/duckdb/common/hugeint.hpp +++ b/src/duckdb/src/include/duckdb/common/hugeint.hpp @@ -17,7 +17,9 @@ struct hugeint_t { // NOLINT: use numeric casing public: hugeint_t() = default; - DUCKDB_API hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` + //! constexpr so that arithmetic against a constant operand can be folded at compile + //! time; the body is at the bottom of this header + constexpr hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` constexpr hugeint_t(int64_t upper, uint64_t lower) : lower(lower), upper(upper) { } constexpr hugeint_t(const hugeint_t &rhs) = default; @@ -28,20 +30,22 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API string ToString() const; // comparison operators - DUCKDB_API bool operator==(const hugeint_t &rhs) const; - DUCKDB_API bool operator!=(const hugeint_t &rhs) const; - DUCKDB_API bool operator<=(const hugeint_t &rhs) const; - DUCKDB_API bool operator<(const hugeint_t &rhs) const; - DUCKDB_API bool operator>(const hugeint_t &rhs) const; - DUCKDB_API bool operator>=(const hugeint_t &rhs) const; + //! constexpr so that comparisons fold at compile time + constexpr bool operator==(const hugeint_t &rhs) const; + constexpr bool operator!=(const hugeint_t &rhs) const; + constexpr bool operator<=(const hugeint_t &rhs) const; + constexpr bool operator<(const hugeint_t &rhs) const; + constexpr bool operator>(const hugeint_t &rhs) const; + constexpr bool operator>=(const hugeint_t &rhs) const; // arithmetic operators - DUCKDB_API hugeint_t operator+(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator-(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator*(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator/(const hugeint_t &rhs) const; + //! constexpr so the compiler can fold these when an operand is a compile-time constant + constexpr hugeint_t operator+(const hugeint_t &rhs) const; + constexpr hugeint_t operator-(const hugeint_t &rhs) const; + constexpr hugeint_t operator*(const hugeint_t &rhs) const; + constexpr hugeint_t operator/(const hugeint_t &rhs) const; DUCKDB_API hugeint_t operator%(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator-() const; + constexpr hugeint_t operator-() const; // bitwise operators DUCKDB_API hugeint_t operator>>(const hugeint_t &rhs) const; @@ -79,6 +83,321 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API explicit operator uhugeint_t() const; }; +//===--------------------------------------------------------------------===// +// constexpr 128-bit arithmetic +// +// These live here rather than next to the rest of Hugeint because +// duckdb/common/types/hugeint.hpp includes duckdb/common/limits.hpp, which +// includes this file back. Everything the digit-parsing loops need has to be +// reachable from here, and the bodies are at the bottom of the file because a +// member function body defined inside the class is looked up at the class's +// closing brace -- above the helpers below. +//===--------------------------------------------------------------------===// + +//! NumericLimits::Minimum()/Maximum(), spelled out because limits.hpp +//! includes this file back so the traits cannot be used here +constexpr hugeint_t HugeintMinimum() { + return hugeint_t(INT64_MIN, 0); +} + +constexpr hugeint_t HugeintMaximum() { + return hugeint_t(INT64_MAX, UINT64_MAX); +} + +constexpr bool HugeintEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper == rhs.upper && lhs.lower == rhs.lower; +} + +constexpr bool HugeintNotEquals(hugeint_t lhs, hugeint_t rhs) { + return !HugeintEquals(lhs, rhs); +} + +constexpr bool HugeintGreaterThan(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower > rhs.lower); +} + +constexpr bool HugeintGreaterThanEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower >= rhs.lower); +} + +constexpr bool HugeintLessThan(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower < rhs.lower); +} + +constexpr bool HugeintLessThanEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower <= rhs.lower); +} + +//! Two's-complement negation, written out because the operators below cannot call +//! operator-() on themselves without the fold failing +constexpr hugeint_t HugeintNegate(hugeint_t n) { + return hugeint_t(static_cast(~static_cast(n.upper) + (n.lower == 0)), 0 - n.lower); +} + +constexpr int HugeintSign(hugeint_t n) { + // the sign is not decided by upper alone: upper == 0 with a non-zero lower is positive + return int(HugeintGreaterThan(n, hugeint_t(0))) - int(HugeintLessThan(n, hugeint_t(0))); +} + +constexpr hugeint_t HugeintAbs(hugeint_t n) { + return n.upper < 0 ? HugeintNegate(n) : n; +} + +constexpr hugeint_t HugeintAddOne(hugeint_t n) { + const int carry = (n.lower == UINT64_MAX) ? 1 : 0; + return hugeint_t(static_cast(static_cast(n.upper) + carry), n.lower + 1); +} + +constexpr hugeint_t HugeintSubtractOne(hugeint_t n) { + // lower wraps to UINT64_MAX, so a borrow is taken out of upper + const int borrow = (n.lower == 0) ? 1 : 0; + return hugeint_t(static_cast(static_cast(n.upper) - borrow), n.lower - 1); +} + +//! Helpers for the bit-at-a-time division, from the same uint128_t reference the +//! runtime implementation uses +constexpr uint8_t HugeintHighestBit(hugeint_t bits) { + uint8_t out = 0; + if (bits.upper) { + // bit index 64 is the lowest bit of upper + out = 64; + uint64_t up = static_cast(bits.upper); + while (up) { + up >>= 1; + out++; + } + } else { + uint64_t low = bits.lower; + while (low) { + low >>= 1; + out++; + } + } + return out; +} + +constexpr bool HugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { + if (bit_position < 64) { + return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); + } + return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); +} + +//! Shift left by 1..63 bits. Called by the division loop below with amount == 1. +//! Takes no D_ASSERT: in assertion builds that macro is an unconditional function call, which +//! would make this function unusable in a constant expression. +constexpr hugeint_t HugeintLeftShift(hugeint_t lhs, uint32_t amount) { + hugeint_t result {0, 0}; + result.lower = lhs.lower << amount; + result.upper = static_cast((static_cast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); + return result; +} + +constexpr hugeint_t HugeintMultiplyUnchecked(hugeint_t lhs, hugeint_t rhs) { + // Unchecked multiplication uses the low 128 bits, including for negative operands. +#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) + const auto left = (static_cast<__uint128_t>(static_cast(lhs.upper)) << 64) | lhs.lower; + const auto right = (static_cast<__uint128_t>(static_cast(rhs.upper)) << 64) | rhs.lower; + const auto result = left * right; + return hugeint_t(static_cast(result >> 64), static_cast(result)); +#else + // Multiply code adapted from: + // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp + + // split values into 4 32-bit parts + uint64_t top[4] {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, + lhs.lower & 0xffffffff}; + uint64_t bottom[4] {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, + rhs.lower & 0xffffffff}; + uint64_t products[4][4] {}; + + // multiply each component of the values + for (idx_t x = 0; x < 4; x++) { + for (idx_t y = 0; y < 4; y++) { + products[x][y] = top[x] * bottom[y]; + } + } + + // first row + uint64_t fourth32 = (products[3][3] & 0xffffffff); + uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); + uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); + uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); + + // second row + third32 += (products[2][3] & 0xffffffff); + second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); + first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); + + // third row + second32 += (products[1][3] & 0xffffffff); + first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); + + // fourth row + first32 += (products[0][3] & 0xffffffff); + + // move carry to next digit + third32 += fourth32 >> 32; + second32 += third32 >> 32; + first32 += second32 >> 32; + + // remove carry from current digit + fourth32 &= 0xffffffff; + third32 &= 0xffffffff; + second32 &= 0xffffffff; + first32 &= 0xffffffff; + + return hugeint_t(static_cast((first32 << 32) + second32), (third32 << 32) + fourth32); +#endif +} + +//! Result of a 128-bit division. Hugeint::DivMod's out-parameter form is declared in +//! types/hugeint.hpp, which is not visible from this header +struct HugeintDivModResult { + hugeint_t quotient; + hugeint_t remainder; +}; + +constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs); + +//! Precondition: lhs or rhs equals HugeintMinimum(). Only reached through HugeintDivMod. +//! Takes no D_ASSERT for the same reason as HugeintLeftShift. +constexpr HugeintDivModResult HugeintDivModMinimum(hugeint_t lhs, hugeint_t rhs) { + // rhs == MINIMUM: the quotient is 1 with remainder 0 only when lhs == MINIMUM; + // every other lhs has |lhs| < |MINIMUM|, so the quotient is 0 and the remainder is lhs + if (rhs.upper == INT64_MIN && rhs.lower == 0) { + if (lhs.upper == INT64_MIN && lhs.lower == 0) { + return HugeintDivModResult {hugeint_t(1), hugeint_t(0)}; + } + return HugeintDivModResult {hugeint_t(0), lhs}; + } + + auto shifted = HugeintDivMod(HugeintAddOne(lhs), rhs); + auto quotient = shifted.quotient; + auto remainder = shifted.remainder; + + // If the 1 mattered we need to adjust the result, otherwise the remainder + if (HugeintEquals(HugeintAddOne(HugeintAbs(remainder)), HugeintAbs(rhs))) { + quotient = HugeintSign(rhs) > 0 ? HugeintSubtractOne(quotient) : HugeintAddOne(quotient); + remainder = hugeint_t(0); + } else { + remainder = HugeintSubtractOne(remainder); + } + return HugeintDivModResult {quotient, remainder}; +} + +//! Bit-at-a-time 128-bit division, adapted from +//! https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp +constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs) { + if (HugeintEquals(rhs, hugeint_t(0))) { + return HugeintDivModResult {hugeint_t(0), lhs}; + } + + // MINIMUM cannot be negated, so it has to be handled before any sign is stripped + if (HugeintEquals(lhs, HugeintMinimum()) || HugeintEquals(rhs, HugeintMinimum())) { + return HugeintDivModMinimum(lhs, rhs); + } + + bool lhs_negative = lhs.upper < 0; + bool rhs_negative = rhs.upper < 0; + if (lhs_negative) { + lhs = HugeintNegate(lhs); + } + if (rhs_negative) { + rhs = HugeintNegate(rhs); + } + + hugeint_t quotient {0, 0}; + hugeint_t remainder {0, 0}; + + const uint8_t highest_bit_set = HugeintHighestBit(lhs); + for (uint8_t x = highest_bit_set; x > 0; x--) { + // left-shift the current quotient and remainder by 1 + quotient = HugeintLeftShift(quotient, 1); + remainder = HugeintLeftShift(remainder, 1); + + // we get the value of the bit at position X, where position 0 is the least-significant bit + if (HugeintIsBitSet(lhs, x - 1)) { + remainder.lower += 1; + if (remainder.lower == 0) { + remainder.upper += 1; + } + } + if (HugeintGreaterThanEquals(remainder, rhs)) { + // the remainder has passed the division multiplier: subtract the divisor, + // carrying a borrow out of lower, and add one to the quotient + const int64_t borrow = remainder.lower < rhs.lower ? 1 : 0; + remainder.lower -= rhs.lower; + remainder.upper -= rhs.upper + borrow; + quotient.lower += 1; + if (quotient.lower == 0) { + quotient.upper += 1; + } + } + } + if (lhs_negative ^ rhs_negative) { + quotient = HugeintNegate(quotient); + } + if (lhs_negative) { + remainder = HugeintNegate(remainder); + } + return HugeintDivModResult {quotient, remainder}; +} + +constexpr hugeint_t::hugeint_t(int64_t value) // NOLINT: Allow implicit conversion from `int64_t` + : lower(static_cast(value)), upper(value < 0 ? -1 : 0) { +} + +constexpr bool hugeint_t::operator==(const hugeint_t &rhs) const { + return HugeintEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator!=(const hugeint_t &rhs) const { + return HugeintNotEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator<(const hugeint_t &rhs) const { + return HugeintLessThan(*this, rhs); +} + +constexpr bool hugeint_t::operator<=(const hugeint_t &rhs) const { + return HugeintLessThanEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator>(const hugeint_t &rhs) const { + return HugeintGreaterThan(*this, rhs); +} + +constexpr bool hugeint_t::operator>=(const hugeint_t &rhs) const { + return HugeintGreaterThanEquals(*this, rhs); +} + +constexpr hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { + return hugeint_t(static_cast(static_cast(upper) + static_cast(rhs.upper) + + ((lower + rhs.lower) < lower)), + lower + rhs.lower); +} + +constexpr hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { + return hugeint_t(static_cast(static_cast(upper) - static_cast(rhs.upper) - + ((lower - rhs.lower) > lower)), + lower - rhs.lower); +} + +constexpr hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { + return HugeintMultiplyUnchecked(*this, rhs); +} + +constexpr hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { + // this is the unchecked division: dividing by zero yields 0 and MINIMUM / -1 wraps, + // matching Hugeint::Divide. Callers that need overflow checks use TryDivMod. + return HugeintDivMod(*this, rhs).quotient; +} + +constexpr hugeint_t hugeint_t::operator-() const { + return HugeintNegate(*this); +} + } // namespace duckdb namespace std { diff --git a/src/duckdb/src/include/duckdb/common/mutex.hpp b/src/duckdb/src/include/duckdb/common/mutex.hpp index 11e3b9bd9..9d55a8141 100644 --- a/src/duckdb/src/include/duckdb/common/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/mutex.hpp @@ -34,6 +34,32 @@ class DUCKDB_CAPABILITY("mutex") annotated_mutex : public internal::mutex_impl_t } }; +// Annotated shared_mutex implementation. +class DUCKDB_CAPABILITY("mutex") annotated_shared_mutex : public internal::mutex_impl_t { +private: + using Impl = internal::mutex_impl_t; + +public: + void lock() DUCKDB_ACQUIRE() { + Impl::lock(); + } + void unlock() DUCKDB_RELEASE() { + Impl::unlock(); + } + bool try_lock() DUCKDB_TRY_ACQUIRE(true) { + return Impl::try_lock(); + } + void lock_shared() DUCKDB_ACQUIRE_SHARED() { + Impl::lock_shared(); + } + void unlock_shared() DUCKDB_RELEASE_SHARED() { + Impl::unlock_shared(); + } + bool try_lock_shared() DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_shared(); + } +}; + // Annotated lock_guard implementation. template class DUCKDB_SCOPED_CAPABILITY annotated_lock_guard : public internal::lock_impl_t> { @@ -99,6 +125,50 @@ class DUCKDB_SCOPED_CAPABILITY annotated_unique_lock : public internal::lock_imp } }; +// Annotated shared_lock implementation. +template +class DUCKDB_SCOPED_CAPABILITY annotated_shared_lock : public internal::lock_impl_t> { +private: + using Impl = internal::lock_impl_t>; + +public: + annotated_shared_lock() = default; + explicit annotated_shared_lock(M &m) DUCKDB_ACQUIRE_SHARED(m) : Impl(m) { + } + annotated_shared_lock(M &m, std::defer_lock_t t) noexcept DUCKDB_EXCLUDES(m) : Impl(m, t) { + } + annotated_shared_lock(M &m, std::try_to_lock_t t) DUCKDB_TRY_ACQUIRE_SHARED(true, m) : Impl(m, t) { + } + annotated_shared_lock(M &m, std::adopt_lock_t t) DUCKDB_REQUIRES_SHARED(m) : Impl(m, t) { + } + + // Disable copy and enable move. + annotated_shared_lock(const annotated_shared_lock &) = delete; + annotated_shared_lock &operator=(const annotated_shared_lock &) = delete; + annotated_shared_lock(annotated_shared_lock &&) = default; + annotated_shared_lock &operator=(annotated_shared_lock &&) = default; + + ~annotated_shared_lock() DUCKDB_RELEASE() = default; + + void lock() DUCKDB_ACQUIRE_SHARED() { + Impl::lock(); + } + bool try_lock() DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock(); + } + template + bool try_lock_for(const std::chrono::duration &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_for(timeout); + } + template + bool try_lock_until(const std::chrono::time_point &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_until(timeout); + } + void unlock() DUCKDB_RELEASE() { + Impl::unlock(); + } +}; + // Unannotated mutex type, which is alias for STL ones. using mutex = std::mutex; template diff --git a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp index 0f9b6a338..755515ff9 100644 --- a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp @@ -497,6 +497,17 @@ struct DecimalCastData { StoreType limit; }; +//! StoreType max/min divided by 10, for the overflow check in DecimalCastOperation::HandleDigit. +//! These are forced to be compile-time constants so the division is folded away: with a hugeint_t +//! store type it is a 128-bit division that would otherwise run for every digit. hugeint_t::operator/ +//! is constexpr, but it is too large to be inlined at the call site, so the compiler does not fold it +//! unless constant evaluation is actually required. +template +struct DecimalCastLimits { + static constexpr T MaximumDiv10 = NumericLimits::Maximum() / 10; + static constexpr T MinimumDiv10 = NumericLimits::Minimum() / 10; +}; + struct DecimalCastOperation { template static bool HandleDigit(T &state, uint8_t digit) { @@ -510,12 +521,12 @@ struct DecimalCastOperation { } state.digit_count++; if (NEGATIVE) { - if (state.result < (NumericLimits::Minimum() / 10)) { + if (state.result < DecimalCastLimits::MinimumDiv10) { return false; } state.result = state.result * 10 - digit; } else { - if (state.result > (NumericLimits::Maximum() / 10)) { + if (state.result > DecimalCastLimits::MaximumDiv10) { return false; } state.result = state.result * 10 + digit; diff --git a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp index 6e1fb2db5..4debd3d80 100644 --- a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp @@ -83,7 +83,7 @@ struct IntegerDecimalCastData { using StoreType = int64_t; StoreType result; StoreType decimal; - uint16_t decimal_digits; + idx_t decimal_digits; }; template <> @@ -92,7 +92,7 @@ struct IntegerDecimalCastData { using StoreType = uint64_t; StoreType result; StoreType decimal; - uint16_t decimal_digits; + idx_t decimal_digits; }; struct IntegerDecimalCastOperation : IntegerCastOperation { @@ -129,12 +129,12 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { } // Handle decimals - e = UnsafeNumericCast(exponent - state.decimal_digits); + auto shift = static_cast(exponent) - static_cast(state.decimal_digits); store_t remainder = 0; - if (e < 0) { - if (static_cast(-e) < NumericLimits::Digits()) { + if (shift < 0) { + if (static_cast(-shift) < NumericLimits::Digits()) { store_t power = 1; - while (e++ < 0) { + while (shift++ < 0) { power *= 10; } remainder = state.decimal % power; @@ -145,7 +145,7 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { state.decimal = 0; } } else { - while (e-- > 0) { + while (shift-- > 0) { if (!TryMultiplyOperator::Operation(state.decimal, (store_t)10, state.decimal)) { return false; } @@ -168,7 +168,10 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { template static bool HandleDecimal(T &state, uint8_t digit) { using store_t = typename T::StoreType; - if (DUCKDB_UNLIKELY(state.decimal > (NumericLimits::Maximum() - digit) / 10)) { + // digits past this position cannot affect the result for any int16_t exponent + static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); + if (DUCKDB_UNLIKELY(state.decimal_digits == MAX_DECIMAL_DIGITS || + state.decimal > (NumericLimits::Maximum() - digit) / 10)) { // Simply ignore any more decimals return true; } @@ -263,6 +266,10 @@ static bool IntegerCastLoop(const char *buf, idx_t len, T &result, bool strict) } } if (StringUtil::CharacterIsSpace(buf[pos])) { + if (pos == start_pos) { + // a sign followed only by whitespace is not a number + return false; + } // skip any trailing spaces while (++pos < len) { if (!StringUtil::CharacterIsSpace(buf[pos])) { diff --git a/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp b/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp new file mode 100644 index 000000000..773357fdc --- /dev/null +++ b/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp @@ -0,0 +1,192 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// duckdb/common/sorting/buffered_merge.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/common/algorithm.hpp" +#include "duckdb/common/helper.hpp" +#include "duckdb/common/typedefs.hpp" +#include "duckdb/common/allocator.hpp" +#include "duckdb/common/exception.hpp" + +namespace duckdb { + +//! Scratch buffer for merging sorted runs: the merge is correct without it, so the allocation must never fail a sort. +//! Starting at half the keys, the size is halved until the buffer manager can provide it (or it becomes too small to +//! be worth having). +class SortMergeBuffer { +public: + //! Number of keys the buffer holds at most: the smaller side of a merge is at most half the keys + static idx_t MaximumKeyCount(idx_t key_count) { + return key_count / 2 + 1; + } + //! Memory the buffer takes at most, so that it can be budgeted along with the keys it merges + static idx_t MaximumSize(idx_t key_count, idx_t key_size) { + return MaximumKeyCount(key_count) * key_size; + } + + SortMergeBuffer(Allocator &allocator, idx_t key_count, idx_t key_size) : count(0) { + static constexpr idx_t MINIMUM_KEYS = 1024; + for (idx_t keys = MaximumKeyCount(key_count); keys >= MINIMUM_KEYS; keys /= 2) { + try { + data = allocator.Allocate(keys * key_size); + count = keys; + return; + } catch (OutOfMemoryException &) { + // try a smaller buffer + } + } + } + + template + SORT_KEY *Keys() { + return reinterpret_cast(data.get()); + } + idx_t Count() const { + return count; + } + +private: + AllocatedData data; + idx_t count; +}; + +//! Rotates [first, last) so that [middle, last) comes before [first, middle) and returns the new middle. The smaller +//! of the two pieces goes through the scratch buffer when it fits, otherwise this is a plain std::rotate; neither +//! allocates. +template +ITERATOR BufferedRotate(ITERATOR first, ITERATOR middle, ITERATOR last, SORT_KEY *buffer, const idx_t buffer_count) { + const auto left_count = static_cast(middle - first); + const auto right_count = static_cast(last - middle); + if (left_count > right_count && right_count <= buffer_count) { + std::move(middle, last, buffer); + std::move_backward(first, middle, last); + std::move(buffer, buffer + right_count, first); + return first + right_count; + } + if (left_count <= buffer_count) { + std::move(first, middle, buffer); + std::move(middle, last, first); + std::move_backward(buffer, buffer + left_count, last); + return last - left_count; + } + return std::rotate(first, middle, last); +} + +//! Merges the sorted ranges [first, middle) and [middle, last) in place using the given scratch buffer, which may be +//! any size (including empty): nothing is allocated. When the smaller side fits in the buffer it is moved out and +//! merged back, forward when it is the left side and backward when it is the right side, which keeps equal keys in +//! order. Otherwise the larger side is split in half, the other side is split at the matching key by binary search, +//! the two middle pieces are rotated and both halves are merged recursively, until the pieces fit the buffer. Without a +//! buffer this is the O(n log n) in-place merge that std::inplace_merge uses when it cannot get memory, with a buffer +//! of half the keys it is a single linear pass, and anything in between makes progressively fewer passes. +template +void BufferedMerge(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare, SORT_KEY *buffer, + const idx_t buffer_count) { + while (true) { + const auto left_count = static_cast(middle - first); + const auto right_count = static_cast(last - middle); + if (left_count == 0 || right_count == 0) { + return; + } + // the ranges are already in order, nothing to merge + if (!compare(*middle, *(middle - 1))) { + return; + } + // one key on each side: swap them (the split below needs at least two keys on the larger side to progress) + if (left_count + right_count == 2) { + std::iter_swap(first, middle); + return; + } + if (left_count <= right_count && left_count <= buffer_count) { + // move the left side out and merge forward into [first, last) + std::move(first, middle, buffer); + auto buffer_end = buffer + left_count; + auto buffer_it = buffer; + auto right_it = middle; + auto out = first; + while (buffer_it != buffer_end && right_it != last) { + if (compare(*right_it, *buffer_it)) { + *out = std::move(*right_it); + ++right_it; + } else { + *out = std::move(*buffer_it); + ++buffer_it; + } + ++out; + } + std::move(buffer_it, buffer_end, out); + return; + } + if (right_count <= buffer_count) { + // move the right side out and merge backward into [first, last) + std::move(middle, last, buffer); + auto buffer_begin = buffer; + auto buffer_it = buffer + right_count; + auto left_it = middle; + auto out = last; + while (buffer_it != buffer_begin && left_it != first) { + --out; + if (compare(*(buffer_it - 1), *(left_it - 1))) { + --left_it; + *out = std::move(*left_it); + } else { + --buffer_it; + *out = std::move(*buffer_it); + } + } + // anything left in the buffer goes to the front of the remaining gap + std::move_backward(buffer_begin, buffer_it, out); + return; + } + // neither side fits: split the larger side in half and the other side at the matching key, so that + // everything in [first_cut, middle) is greater than or equal to everything in [middle, second_cut) ... + // (copies of existing iterators: a default-constructed block iterator has no state to iterate over) + ITERATOR first_cut = first; + ITERATOR second_cut = middle; + if (left_count > right_count) { + first_cut = first + left_count / 2; + second_cut = std::lower_bound(middle, last, *first_cut, compare); + } else { + second_cut = middle + right_count / 2; + first_cut = std::upper_bound(first, middle, *second_cut, compare); + } + // ... swap those two pieces, which leaves two independent merges on either side of the new middle + auto new_middle = BufferedRotate(first_cut, middle, second_cut, buffer, buffer_count); + // recurse into the smaller one and loop on the larger one, which bounds the recursion depth by log2(n) + if (new_middle - first < last - new_middle) { + BufferedMerge(first, first_cut, new_middle, compare, buffer, buffer_count); + first = new_middle; + middle = second_cut; + } else { + BufferedMerge(new_middle, second_cut, last, compare, buffer, buffer_count); + last = new_middle; + middle = first_cut; + } + } +} + +//! Merge policy for vergesort (and the sorts it calls into) that merges through a SortMergeBuffer, so that every run +//! merge of a sort, including the nested ones, uses the one buffer that was budgeted for it and nothing else +template +class BufferedMergePolicy { +public: + explicit BufferedMergePolicy(SortMergeBuffer &buffer) : keys(buffer.Keys()), count(buffer.Count()) { + } + + template + void operator()(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare) const { + BufferedMerge(first, middle, last, compare, keys, count); + } + +private: + SORT_KEY *keys; + idx_t count; +}; + +} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp index fedb2dba4..560c58078 100644 --- a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp +++ b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp @@ -58,6 +58,8 @@ class SortedRun { idx_t Count() const; //! Size of this sorted run idx_t SizeInBytes() const; + //! Size of this sorted run plus the merge buffer that Finalize allocates to sort it + idx_t SizeInBytesForSorting() const; private: mutex merger_global_state_lock; diff --git a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp index 5bc8261cc..61355a8ac 100644 --- a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp @@ -12,17 +12,21 @@ #pragma once #include +#include namespace duckdb { // Forward declaration for annotated mutex types. class annotated_mutex; +class annotated_shared_mutex; // Forward declaration for annotated lock types. template class annotated_unique_lock; template class annotated_lock_guard; +template +class annotated_shared_lock; namespace internal { @@ -40,6 +44,12 @@ struct standard_impl<::duckdb::annotated_mutex> { using type = std::mutex; }; +// Specialization for `std::shared_mutex`. +template <> +struct standard_impl<::duckdb::annotated_shared_mutex> { + using type = std::shared_mutex; +}; + // Type alias for lock types. template using mutex_impl_t = standard_impl_t; @@ -56,6 +66,12 @@ struct standard_impl<::duckdb::annotated_lock_guard> { using type = std::lock_guard>; }; +// Specialization for `std::shared_lock`. +template +struct standard_impl<::duckdb::annotated_shared_lock> { + using type = std::shared_lock>; +}; + template using lock_impl_t = standard_impl_t; diff --git a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp index cf943612d..b1346c193 100644 --- a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp +++ b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp @@ -71,11 +71,11 @@ union arrow_string_view_t { const char *GetInlineData() const { return IsInline() ? inlined.data : ref.prefix; } - int32_t GetBufferIndex() { + int32_t GetBufferIndex() const { D_ASSERT(!IsInline()); return ref.buffer_index; } - int32_t GetOffset() { + int32_t GetOffset() const { D_ASSERT(!IsInline()); return ref.offset; } diff --git a/src/duckdb/src/include/duckdb/common/types/time.hpp b/src/duckdb/src/include/duckdb/common/types/time.hpp index 764ed6857..d15c2752d 100644 --- a/src/duckdb/src/include/duckdb/common/types/time.hpp +++ b/src/duckdb/src/include/duckdb/common/types/time.hpp @@ -29,7 +29,8 @@ class Time { optional_ptr nanos = nullptr); // No hour limit DUCKDB_API static bool TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, - bool strict = false, optional_ptr nanos = nullptr); + bool strict = false, optional_ptr nanos = nullptr, + bool negative = false); //! Convert a time object to a string in the format "hh:mm:ss" DUCKDB_API static string ToString(dtime_t time); @@ -56,7 +57,7 @@ class Time { private: static bool TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos = nullptr); + optional_ptr nanos = nullptr, bool negative = false); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/types/validity_mask.hpp b/src/duckdb/src/include/duckdb/common/types/validity_mask.hpp index 5ad74dbfc..f3c351bf8 100644 --- a/src/duckdb/src/include/duckdb/common/types/validity_mask.hpp +++ b/src/duckdb/src/include/duckdb/common/types/validity_mask.hpp @@ -419,6 +419,13 @@ struct ValidityArray { validity_mask = validity_data.get(); memset(validity_mask, initial, sizeof(bool) * count); } + //! Points the array at storage of `count` entries owned by the caller (e.g. a buffer-allocated block, so that an + //! O(rows) array counts against the memory limit); the storage must outlive the array + inline void Initialize(bool *validity, idx_t count) { + validity_data.reset(); + validity_mask = validity; + capacity = count; + } inline void InitializeEmpty(idx_t count) { capacity = count; } diff --git a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp index 96c1612de..a2b86440c 100644 --- a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp +++ b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp @@ -74,6 +74,8 @@ struct ExecuteFunctionState : public ExpressionState { public: unique_ptr local_state; + //! The buffers of all-constant arguments, which can be shared with the input and have to get their size back + vector> constant_argument_buffers; private: //! The column index of the "unary" input column that may be a dictionary vector diff --git a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp index 1fe4827a9..15860231e 100644 --- a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp +++ b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp @@ -554,10 +554,12 @@ class JoinHashTable { //! An empty tuple that's a "dead end", can be used to stop chains early unsafe_unique_array dead_end; - //! Whether or not to use a bloom filter will be determined by the operator - BloomFilter bloom_filter; + //! Bloom filter over the join keys. An external join fills it during Sink, an in-memory join during + //! finalize. Shared by every thread-local hash table of this operator, and shared again with any scan it + //! gets published to, so it outlives a reset of this table + shared_ptr bloom_filter; + //! Whether finalize should build the bloom filter from the materialized build side bool should_build_bloom_filter = false; - idx_t bloom_filter_init_count = 0; unique_ptr prefix_range_filter; bool should_build_prefix_range_filter = false; @@ -639,14 +641,27 @@ class JoinHashTable { return PointerTableCapacity(count) * sizeof(data_ptr_t); } + void SetBloomFilter(shared_ptr filter) { + bloom_filter = std::move(filter); + } + //! Requests a filter built during finalize, from the materialized build side (in-memory joins only) void SetBuildBloomFilter(const bool should_build) { this->should_build_bloom_filter = should_build; } - void PrepareBloomFilterForFinalize(); - BloomFilter &GetBloomFilter() { + shared_ptr GetBloomFilter() { return bloom_filter; } + //! Marks the bloom filter complete. The reference stays, because the thread-local tables hold it too + void SealBloomFilter() { + if (bloom_filter) { + bloom_filter->Seal(context); + } + } + //! Whether the filter is complete and rejects enough for pushing it into a scan to pay off + bool HasPublishableBloomFilter() const { + return bloom_filter && bloom_filter->IsSealed() && bloom_filter->IsUseful(); + } void SetPrefixRangeFilter(unique_ptr filter) { prefix_range_filter = std::move(filter); diff --git a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp index 4bb415cb7..9ed0dc06b 100644 --- a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp +++ b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp @@ -506,6 +506,7 @@ pair MergeSortTree::SelectNth(const SubFrames &fr // Go over all children until we found enough in range const auto *level_data = tree[level_no].first.data(); + const auto level_size = tree[level_no].first.size(); while (true) { idx_t matched = 0; std::array matches; @@ -526,7 +527,8 @@ pair MergeSortTree::SelectNth(const SubFrames &fr matched += idx_t(match.second - match.first); } - if (matched > n) { + // Move down if there are enough in this child, or if this is the last child of the level + if (matched > n || (result + 1) * level_width >= level_size) { // Too much in this level, so move down to leftmost child candidate within the cascade range for (idx_t f = 0; f < frames.size(); ++f) { auto &cascade_idx = cascades[f]; diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp index d2eab3a84..1671b85fc 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp @@ -27,13 +27,13 @@ class PhysicalBatchInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality); //! The table to insert into - optional_ptr insert_table; + mutable optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table vector> bound_constraints; //! Table schema, in case of CREATE TABLE AS - optional_ptr schema; + mutable optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Preferred input batch size diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp index b67be1ab7..965f4ff11 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp @@ -87,7 +87,7 @@ class PhysicalInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality, bool parallel); //! The table to insert into - optional_ptr insert_table; + mutable optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table @@ -95,7 +95,7 @@ class PhysicalInsert : public PhysicalOperator { //! If the returning statement is present, return the whole chunk bool return_chunk; //! Table schema, in case of CREATE TABLE AS - optional_ptr schema; + mutable optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Whether or not the INSERT can be executed in parallel diff --git a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp index b12125a65..417427b69 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp @@ -24,6 +24,7 @@ class PhysicalProjection : public PhysicalOperator { vector> select_list; public: + void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const override; unique_ptr GetOperatorState(ExecutionContext &context) const override; OperatorResultType Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state) const override; diff --git a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp index ddb6e10b1..df0a6d347 100644 --- a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp +++ b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp @@ -91,6 +91,11 @@ class PhysicalOperator { return false; } + //! Initialize the vectors this operator uses for its output + virtual void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const { + chunk.Initialize(allocator, types); + } + //! Functions to help decide how to set up pipeline dependencies idx_t EstimatedThreadCount() const; bool CanSaturateThreads(ClientContext &context) const; diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp index 104c71ae4..25faa7444 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp @@ -162,7 +162,8 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res VarintEncode(length, str_blob_data); memcpy(str_blob_data + length_varint_size, const_data_ptr_cast(str), length); } - blob_offset_data[result_index] += length_varint_size + length; + OffsetData::AddBlobOffset(blob_offset_data[result_index], length_varint_size); + OffsetData::AddBlobOffset(blob_offset_data[result_index], length); break; } case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: { @@ -182,7 +183,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_uint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(uint64_t)); } - blob_offset_data[result_index] += sizeof(uint64_t); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(uint64_t)); break; } case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: { @@ -192,7 +193,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_sint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(int64_t)); } - blob_offset_data[result_index] += sizeof(int64_t); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(int64_t)); break; } case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: @@ -211,7 +212,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res } memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(double)); } - blob_offset_data[result_index] += sizeof(double); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(double)); break; } default: diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp index 0c972435e..86abb7d4b 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp @@ -16,6 +16,14 @@ struct EnumConversionPayload { EnumConversionPayload(const string_t *values, idx_t size) : values(values), size(size) { } +public: + const string_t &GetString(idx_t index) const { + if (index >= size) { + throw InternalException("ENUM index %llu is out of range for an ENUM with %llu values", index, size); + } + return values[index]; + } + public: const string_t *values; idx_t size; @@ -101,19 +109,19 @@ static void WriteData(data_ptr_t ptr, const T &val, uint32_t lengths[3], EnumCon template <> void WriteData(data_ptr_t ptr, const uint8_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint16_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint32_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } @@ -184,19 +192,19 @@ static void GetValueSize(const T &val, uint32_t lengths[3], idx_t &lengths_size, template <> void GetValueSize(const uint8_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint16_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint32_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } @@ -215,9 +223,10 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob auto &variant = result.variant; uint32_t lengths[3]; - idx_t lengths_size = 0; for (idx_t i = 0; i < count; i++) { auto index = source[i]; + // the blob space to reserve for this row - nothing for NULL rows + idx_t lengths_size = 0; auto result_index = selvec ? selvec->get_index(i) : i; auto &blob_offset = blob_offset_data[result_index]; @@ -225,7 +234,6 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob if (TYPE_ID != VariantLogicalType::VARIANT_NULL && source_validity.RowIsValid(index)) { //! Write the value auto &val = source_data[index]; - lengths_size = 0; GetValueSize(val, lengths, lengths_size, payload); WriteVariantMetadata(result, result_index, values_offset_data, blob_offset, values_index_selvec, i, GetTypeId(val)); diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp index 83c37856a..65ead7f16 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp @@ -74,8 +74,7 @@ template void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t &blob_offset, idx_t length, idx_t children_offset) { const auto length_varint_size = GetVarintSize(length); - const auto child_offset_varint_size = - NumericCast(length_varint_size ? GetVarintSize(children_offset) : 0); + const auto child_offset_varint_size = NumericCast(length ? GetVarintSize(children_offset) : 0); if (WRITE_DATA) { auto &blob_value = result.blob_data[result_index]; @@ -86,7 +85,7 @@ void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t } } - blob_offset += length_varint_size + child_offset_varint_size; + OffsetData::AddBlobOffset(blob_offset, length_varint_size + child_offset_varint_size); } struct ContainerSelectionVectors { diff --git a/src/duckdb/src/include/duckdb/function/compression_function.hpp b/src/duckdb/src/include/duckdb/function/compression_function.hpp index 54cebc0c5..0068ee52b 100644 --- a/src/duckdb/src/include/duckdb/function/compression_function.hpp +++ b/src/duckdb/src/include/duckdb/function/compression_function.hpp @@ -10,6 +10,7 @@ #include "duckdb/common/common.hpp" #include "duckdb/common/enums/compression_type.hpp" +#include "duckdb/common/enums/scan_vector_type.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/insertion_order_preserving_map.hpp" #include "duckdb/common/mutex.hpp" @@ -162,8 +163,11 @@ typedef void (*compression_scan_vector_t)(ColumnSegment &segment, ColumnScanStat typedef void (*compression_scan_partial_t)(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset); //! Function prototype used for reading a subset of the values of a vector indicated by a selection vector +//! The selected values are written to 'result' starting at 'result_offset'. Emitting a compressed vector (e.g. a +//! constant vector) is only allowed if 'scan_type' is SCAN_ENTIRE_VECTOR typedef void (*compression_select_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType scan_type); //! Function prototype used for applying a filter to a vector while scanning that vector typedef void (*compression_filter_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, diff --git a/src/duckdb/src/include/duckdb/function/function.hpp b/src/duckdb/src/include/duckdb/function/function.hpp index aab9fa6b5..affaae7c9 100644 --- a/src/duckdb/src/include/duckdb/function/function.hpp +++ b/src/duckdb/src/include/duckdb/function/function.hpp @@ -56,10 +56,14 @@ enum class FunctionStability : uint8_t { CONSISTENT = 0, VOLATILE = 1, CONSISTEN //! PROPAGATE_COLLATIONS -> this function combines collation from its inputs and emits them again (default) //! PUSH_COMBINABLE_COLLATIONS -> combinable collations are executed for the input arguments //! IGNORE_COLLATIONS -> collations are completely ignored by the function +//! PUSH_DISTINCT_COLLATIONS -> aggregate DISTINCT arguments are replaced by their comparison keys +//! Only valid for aggregates that can consume keys instead of original values. +//! Ordinary (non-DISTINCT) calls keep their original arguments. enum class FunctionCollationHandling : uint8_t { PROPAGATE_COLLATIONS = 0, PUSH_COMBINABLE_COLLATIONS = 1, - IGNORE_COLLATIONS = 2 + IGNORE_COLLATIONS = 2, + PUSH_DISTINCT_COLLATIONS = 3 }; struct FunctionData { diff --git a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp index 4a4361232..6e77374f9 100644 --- a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp +++ b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp @@ -16,6 +16,7 @@ namespace duckdb { class BoundFunctionExpression; +struct SortedAggregateStateOrder; struct ConstantOrNull { static bool IsConstantOrNull(BoundFunctionExpression &expr, const Value &val); @@ -32,6 +33,8 @@ struct ExportAggregateFunction { static unique_ptr Bind(unique_ptr child_aggregate); static void SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout); static unique_ptr StateToSQL(const LogicalType &type, unique_ptr value); + //! The per-key (column, modifiers) of the AGGREGATE_STATE type of an ordered aggregate + static void GetSortedStateOrders(const LogicalType &state_type, vector &orders); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/function/window/window_aggregator.hpp b/src/duckdb/src/include/duckdb/function/window/window_aggregator.hpp index ae015928c..e5fe8cd3e 100644 --- a/src/duckdb/src/include/duckdb/function/window/window_aggregator.hpp +++ b/src/duckdb/src/include/duckdb/function/window/window_aggregator.hpp @@ -141,6 +141,8 @@ class WindowAggregatorGlobalState : public GlobalSinkState { //! The filtered rows in inputs. ValidityArray filter_mask; + //! Storage of the filter mask (one byte per row), from the buffer allocator so that it counts against the limit + AllocatedData filter_mask_data; //! Lock for single threading mutable mutex lock; diff --git a/src/duckdb/src/include/duckdb/logging/log_type.hpp b/src/duckdb/src/include/duckdb/logging/log_type.hpp index 94bfbef13..bf8758d60 100644 --- a/src/duckdb/src/include/duckdb/logging/log_type.hpp +++ b/src/duckdb/src/include/duckdb/logging/log_type.hpp @@ -17,6 +17,7 @@ namespace duckdb { struct FileHandle; struct BaseRequest; struct HTTPResponse; +struct MetricTiming; class PhysicalOperator; enum class PhysicalOperatorType : uint8_t; class AttachedDatabase; @@ -125,7 +126,8 @@ class MetricsLogType : public LogType { static LogicalType GetLogType(); - static string ConstructLogMessage(const string &metric, const Value &value); + static string ConstructLogMessage(const string &metric, const Value &value, + optional_ptr timing = nullptr); }; class CheckpointLogType : public LogType { diff --git a/src/duckdb/src/include/duckdb/main/active_query_context.hpp b/src/duckdb/src/include/duckdb/main/active_query_context.hpp index 4aad2a1c9..f7c87d98b 100644 --- a/src/duckdb/src/include/duckdb/main/active_query_context.hpp +++ b/src/duckdb/src/include/duckdb/main/active_query_context.hpp @@ -9,25 +9,44 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/enums/result_lifetime.hpp" #include "duckdb/common/progress_bar/progress_bar.hpp" #include "duckdb/execution/executor.hpp" #include "duckdb/main/prepared_statement_data.hpp" +#include "duckdb/main/query_parameters.hpp" +#include "duckdb/planner/statement_preprocessor.hpp" namespace duckdb { class BaseQueryResult; +class QueryResult; +//! The user statement a connection runs, from its submission until a call ends it. A statement that expands into +//! several runs them one after another, each with its own executor, as one query struct ActiveQueryContext { public: - //! The query that is currently being executed + explicit ActiveQueryContext(PreprocessedStatement statements_p) : statements(std::move(statements_p.statements)) { + } + + //! The text of the statement that is running string query; - //! Prepared statement data + //! Prepared statement data of the statement that is running shared_ptr prepared; - //! The query executor + //! The executor of the statement that is running unique_ptr executor; - //! The progress bar + //! The progress bar of the statement that is running unique_ptr progress_bar; //! Whether a collector built the open result, which has no way to end the query bool collector_built_result = false; + //! The format every statement of the query is submitted in; null is the chunk format + shared_ptr format; + //! The consumer's retention decision, applied to the result statement's buffer when it is created + ResultLifetime retention = ResultLifetime::UNDECIDED; + //! Whether the statement whose rows the result carries has started + bool result_statement_started = false; + //! The handle of a statement that expands into several, which the statement producing the result fills in + optional_ptr handle; + //! Whether the statements run inside the submission, where a result collector may build its own result object + bool inside_submission = false; public: void SetOpenResult(BaseQueryResult &result) { @@ -39,10 +58,54 @@ struct ActiveQueryContext { bool HasOpenResult() const { return open_result != nullptr; } + //! Whether the user's statement expanded into several + bool ExpandsIntoSeveral() const { + // Started statements stay in the list, moved out + return statements.size() > 1; + } + bool HasNextStatement() const { + return next_statement < statements.size(); + } + unique_ptr TakeNextStatement() { + D_ASSERT(HasNextStatement()); + return std::move(statements[next_statement++]); + } + //! Records the databases a statement of the query read and modified + void RecordDatabases(const StatementProperties &properties) { + MergeDatabases(properties, databases); + } + //! Whether a statement of the query that started modifies a database + bool Wrote() const { + return !databases.IsReadOnly(); + } + //! The properties of the statement that produces the result, describing the databases of the whole query + StatementProperties QueryProperties(StatementProperties properties) const { + MergeDatabases(databases, properties); + return properties; + } private: //! The currently open result BaseQueryResult *open_result = nullptr; + //! The statements the user's statement preprocessed into + vector> statements; + //! The index of the statement that starts next + idx_t next_statement = 0; + //! The databases the statements that already ran read and modified + StatementProperties databases; + +private: + static void MergeDatabases(const StatementProperties &source, StatementProperties &target) { + for (auto &entry : source.read_databases) { + target.read_databases.insert(entry); + } + for (auto &entry : source.modified_databases) { + auto inserted = target.modified_databases.insert(entry); + if (!inserted.second) { + inserted.first->second.modifications |= entry.second.modifications; + } + } + } }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp index b90f20fce..eedc6ce06 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_internal.hpp @@ -714,6 +714,14 @@ inline auto Convert(CV2Schema *schema) -> duckdb_v2_schema_handle { auto NullArgumentError(duckdb_v2_error_info_handle *err, const char *function, const char *argument) noexcept -> DUCKDB_V2_ERROR; +//! Raised for metadata asked for before execution bound the statement that produces it. Reported as +//! ERROR_INPUT_METADATA_DEFERRED +class MetadataDeferredException : public InvalidInputException { +public: + explicit MetadataDeferredException(const string &msg) : InvalidInputException(msg) { + } +}; + // Classify the exception currently being handled into a V2 error code and detail strings. Must be called from inside // a catch block. Never throws: if rendering the detail itself fails, it degrades to a bare code with empty detail // (RESOURCE_OUT_OF_MEMORY on allocation failure). Defined in capi_v2.cpp. @@ -834,9 +842,6 @@ DUCKDB_V2_ERROR WithErrorHandler(duckdb_v2_error_info_handle *err, T callback) n //---------------------------------------------------------------------------------------------------------------------- struct ConnectionBusySlotV2 : public ClientContextState { - //! The result of a statement that expands into a group, until it finishes. Between two statements of a group - //! the engine has no statement open, but the group's transaction is still running - std::atomic owner {nullptr}; // The number of `connection_interrupt` calls. A result records it when its statement starts, and an INTERRUPT // error is a consumer cancellation (-> CANCELLED status) only if it moved since: a DuckDB-initiated interrupt, // e.g. a `max_execution_time` timeout, shares the INTERRUPT exception type (-> error). The DuckDB's internal @@ -849,17 +854,6 @@ inline shared_ptr GetBusySlot(ClientContext &context) { return context.registered_state->GetOrCreate(BUSY_SLOT_STATE_KEY); } -//! The engine's refusal, raised for a group that holds the connection between two of its statements -inline void ThrowConnectionInUse() { - throw ResourceInUseException(ClientContext::OPEN_RESULT_ERROR); -} - -inline void ThrowIfGroupRunning(ConnectionBusySlotV2 &slot) { - if (slot.owner.load() != nullptr) { - ThrowConnectionInUse(); - } -} - //---------------------------------------------------------------------------------------------------------------------- // Text sinks //---------------------------------------------------------------------------------------------------------------------- diff --git a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp index 7dc14c68b..031190fa6 100644 --- a/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp +++ b/src/duckdb/src/include/duckdb/main/capi_v2/capi_v2_result_internal.hpp @@ -10,7 +10,6 @@ #include "duckdb/common/arrow/arrow_wrapper.hpp" #include "duckdb/main/capi_v2/capi_v2_internal.hpp" -#include "duckdb/main/valid_checker.hpp" #include "duckdb/main/query_result_stream.hpp" #include "duckdb/main/result_format.hpp" @@ -29,7 +28,6 @@ struct ResultWrapperV2 { // a destructor must not drive locked engine state behind a catch-all. stream.reset(); handle.reset(); - ReleaseBusySlot(); } State state = State::PENDING; @@ -41,48 +39,23 @@ struct ResultWrapperV2 { //! Decided when the handle is adopted, since a prepared execution's handle reports the EXECUTE that runs it //! rather than the statement as written bool stream_when_ready = false; - //! Every fragment is submitted in this format; null is the chunk format. + //! The statement is submitted in this format; null is the chunk format. shared_ptr format; - //! Keeps the ClientContext alive for starting subsequent fragments and - //! preserves the guarantee that an undrained result survives disconnect: + //! Preserves the guarantee that an undrained result survives disconnect: //! the connection handle (a bare Connection *) may be destroyed while a //! result is live, but the context it shared with us stays alive here. shared_ptr context; - //! The preprocessed statement group; fragment_index points at the next - //! fragment to start, fragment_count remembers the group size after - //! fragments is cleared on terminal transitions. - vector> fragments; - idx_t fragment_index = 0; - idx_t fragment_count = 0; - //! Positional parameter values for this execution, keyed by binding identifier - //! ("1".."N"). Empty for an unparameterized statement (StartNextFragment takes - //! the no-values path). Applied only to the first fragment (a parameterized - //! statement does not expand into a group). - identifier_map_t param_values; - //! True while the currently executing fragment is the principal one - //! (its chunks are surfaced; other fragments' output is discarded). - bool principal_active = false; - //! True once a row-producing fragment has been selected as principal. - bool principal_seen = false; - //! True once the principal fragment's metadata has been captured. + //! True once the metadata has been captured. For a statement that expands into several, that is once execution + //! bound the statement that produces the result, or the group ended without one. bool metadata_available = false; - //! True when statement_execute injected its own wrapping transaction for - //! this group (autocommit input that preprocessing expanded and wrapped - //! in BEGIN ... COMMIT). Distinguishes a bridge-owned transaction, which - //! must be rolled back on incomplete destroy, from a user-managed one, - //! which must not be touched. Captured at query time because the engine's - //! auto_rollback flag is not reliably observable from the bridge's - //! fragment execution path. - bool owns_wrapping_transaction = false; - - //! The connection's busy slot, kept for the result's lifetime for its cancel count. A group claims the slot - //! until a terminal transition or destroy, whichever comes first. + + //! The connection's busy slot, kept for the result's lifetime for its cancel count. shared_ptr busy_slot; //! The slot's cancel count when this result's statement started idx_t cancel_requests_at_start = 0; - //! Principal fragment's metadata, valid once metadata_available. + //! The result's metadata, valid once metadata_available. The statement type is known at submission. vector types; vector names; StatementType statement_type = StatementType::INVALID_STATEMENT; @@ -93,67 +66,25 @@ struct ResultWrapperV2 { //! Sticky error, recorded when state == ERRORED. ErrorData error; - //! Mirrors ClientContext::Query's error handling for expanded groups - //! (client_context.cpp, chain-append loop): a group that cannot complete - //! is undone as a whole. The transaction statement_execute injected to wrap - //! it is rolled back; a user-managed transaction it ran inside (which the - //! bridge never wraps) is invalidated, since part of the group may already - //! have applied. Only a group still in progress is undone: once its result - //! has ended, the active transaction may be a later one. - void AbandonIncompleteGroup() { - if (fragment_count < 2 || !context || (state != State::PENDING && state != State::STREAMING)) { - return; - } - if (!context->transaction.HasActiveTransaction()) { - return; - } - if (!owns_wrapping_transaction) { - ValidChecker::Invalidate(context->ActiveTransaction(), - "a statement that expands into several was abandoned before it completed"); - return; - } - // Mirrors Connection::Rollback (Query("ROLLBACK") + throw on error), - // driven through the retained context so it works after disconnect. - auto result = context->Query("ROLLBACK", QueryParameters()); - result->ThrowIfError(); - } - //! Close() the live engine result so an abandoned active query is cleaned - //! up (freeing the executor, which breaks the ClientContext ref cycle), - //! then undo a group that is still in progress. May throw; the terminal - //! states leave pending/result null and the group settled, so this is then a no-op. + //! up (freeing the executor, which breaks the ClientContext ref cycle). The + //! engine undoes a statement that expands into several as a whole. May throw. void Finalize() { if (stream) { stream->Close(); } else if (handle) { handle->Close(); } - AbandonIncompleteGroup(); - } - - //! Releases a group's claim on the connection. Only the current owner can - //! release the slot, so a release after the connection moved on is a - //! no-op. - void ReleaseBusySlot() { - if (busy_slot) { - void *expected = this; - busy_slot->owner.compare_exchange_strong(expected, nullptr); - } } - // State-machine entry points; defined in query_result-v2.cpp. All of + // State-machine entry points; defined in capi_v2_result.cpp. All of // them throw DuckDB exceptions on failure (callers wrap in // WithErrorHandler) and record sticky errors before throwing. //! Adopts an already-submitted query into the state machine: the single seam both the - //! stateless (fragment) and prepared paths reach. When is_principal, captures its metadata and - //! surfaces its chunks. written_type is the type of the statement as written, since a prepared execution's - //! handle reports the EXECUTE that runs it. Throws on a submission error. - void BeginPending(unique_ptr handle, bool is_principal, StatementType written_type); - //! Starts the pending query for the next fragment, selecting it as - //! principal per the engine-mirrored rule and adopting it via BeginPending. - //! Throws on prepare errors. - void StartNextFragment(); + //! stateless and prepared paths reach. written_type is the type of the statement as written, since a prepared + //! execution's handle reports the EXECUTE that runs it. Throws on a submission error. + void BeginPending(unique_ptr handle, StatementType written_type); //! Drives one unit of work; never blocks. On CHUNK, out_unit holds the //! produced unit; on every other status it is reset. FORMAT must be `format`. template @@ -164,17 +95,19 @@ struct ResultWrapperV2 { //! or the stream ends (nullptr). Cancellation throws InterruptException. template unique_ptr FetchBlocking(); - //! Steps an expanding statement until its principal fragment's metadata is available. Stops early when the - //! result ends without one. + //! Steps a statement that expands into several until its metadata is available. Stops early when the result + //! ends without it. template void AdvanceToMetadata(); //! Runs to the end. Returns the changed-row count of a CHANGED_ROWS result, 0 otherwise. template idx_t Drain(); - //! Throws unless the principal fragment's metadata is available. - void RequireMetadata() const; + //! Throws MetadataDeferredException unless the metadata is available. + void RequireMetadata(); private: + //! Copies the metadata off the live handle or stream once the engine knows it, since both go when the result ends + void CaptureMetadata(); //! Shared error sink: interrupts become the sticky CANCELLED state //! (returned as a status), everything else becomes the sticky ERRORED //! state and throws. @@ -186,13 +119,13 @@ auto Convert(duckdb_v2_result_handle handle) -> ResultWrapperV2 *; auto ConvertArrowResult(ResultWrapperV2 *wrapper) -> duckdb_v2_arrow_result_handle; auto Convert(duckdb_v2_arrow_result_handle handle) -> ResultWrapperV2 *; -//! Preprocesses and submits a borrowed statement in `format`; a statement that expands into a group claims the -//! connection's busy slot. Throws ResourceInUseException while a statement on the connection is still running. +//! Submits a borrowed statement in `format`. Throws ResourceInUseException while a statement on the connection is +//! still running. auto ExecuteStatementV2(const shared_ptr &context, const SQLStatement &statement, const duckdb_v2_identifier_t *parameter_names, const duckdb_v2_value_handle *parameter_values, idx_t parameter_count, const char *function_name, shared_ptr format) -> unique_ptr; -//! Runs a prepared statement as a single-statement result. `context` is the session the result holds on to, so it +//! Runs a prepared statement. `context` is the session the result holds on to, so it //! survives disconnect. Throws ResourceInUseException while a statement on the connection is still running. auto ExecutePreparedStatementV2(const shared_ptr &context, PreparedStatement &prepared, identifier_map_t &values, shared_ptr format) diff --git a/src/duckdb/src/include/duckdb/main/client_context.hpp b/src/duckdb/src/include/duckdb/main/client_context.hpp index 6d82b01e5..62ef1312e 100644 --- a/src/duckdb/src/include/duckdb/main/client_context.hpp +++ b/src/duckdb/src/include/duckdb/main/client_context.hpp @@ -16,6 +16,7 @@ #include "duckdb/common/enums/query_result_state.hpp" #include "duckdb/common/enums/prepared_statement_mode.hpp" #include "duckdb/common/enums/connection_type.hpp" +#include "duckdb/common/enums/result_lifetime.hpp" #include "duckdb/common/error_data.hpp" #include "duckdb/common/pair.hpp" #include "duckdb/common/unordered_set.hpp" @@ -45,7 +46,7 @@ class LogicalOperator; class PreparedStatement; class PreparedStatementData; class StatementIterator; -class StatementPreprocessor; +struct PreprocessedStatement; class Relation; class BufferedFileWriter; class QueryProfiler; @@ -77,6 +78,9 @@ struct StatementSignature { vector types; vector parameters; StatementProperties properties; + //! The statement expands into several; its result is known only once execution binds the statement that + //! produces it + bool metadata_deferred = false; }; //! Interrupt state for the client context @@ -89,6 +93,7 @@ class ClientContext : public enable_shared_from_this { friend class BufferedData; // ExecuteTaskInternal, PollInternal friend class SimpleBufferedData; // ExecuteTaskInternal, PollInternal friend class BatchedBufferedData; // ExecuteTaskInternal, PollInternal + friend class ResultStreamBase; // drives a statement that expands before its result buffer exists friend class ConnectionManager; friend class Connection; friend class PhysicalTransaction; @@ -97,6 +102,11 @@ class ClientContext : public enable_shared_from_this { //! The error of a statement submitted while a result that no call ended holds the connection static constexpr const char *OPEN_RESULT_ERROR = "connection has an open result; drain or destroy it before starting a new query"; + //! The error of a query that ran past max_execution_time + static constexpr const char *QUERY_DEADLINE_ERROR = "Query exceeded maximum execution time"; + //! The error of a statement that expands into several, given parameters + static constexpr const char *EXPANDING_PARAMETERS_ERROR = + "parameters are not supported for a statement that expands into several"; public: DUCKDB_API explicit ClientContext(shared_ptr db); @@ -151,6 +161,8 @@ class ClientContext : public enable_shared_from_this { //! Check for interrupt or timeout, throws InterruptException if triggered DUCKDB_API void InterruptCheck() const; + //! InterruptCheck that reads the clock on every call, for waits that block between checks + DUCKDB_API void BlockingInterruptCheck() const; //! Enable query profiling DUCKDB_API void EnableProfiling(); @@ -218,14 +230,16 @@ class ClientContext : public enable_shared_from_this { //! Blocking. Runs a relation to completion and returns its handle DUCKDB_API unique_ptr Execute(const shared_ptr &relation); - //! Prepare a query + //! Prepare a query. A statement that expands into several is kept as written and expanded at each execution, so + //! its metadata is known only from the result DUCKDB_API unique_ptr Prepare(const string &query); - //! Directly prepare a SQL statement + //! Directly prepare a SQL statement, as Prepare(query) does DUCKDB_API unique_ptr Prepare(unique_ptr statement); //! Deallocate the prepared statement with the given name - does nothing if it does not exist DUCKDB_API void RemovePreparedStatement(const string &name); //! Bind a statement and return its signature, without building a PreparedStatement, optimizing, or //! executing. Read-only: binding touches no in-flight query state, so a live result survives. Throws on error. + //! A statement that expands into several reports metadata_deferred with a placeholder column DUCKDB_API StatementSignature BindStatement(unique_ptr statement); //! Throws the error a new statement gets while a result that no call ended holds the connection DUCKDB_API void VerifyNoOpenResult(); @@ -236,19 +250,10 @@ class ClientContext : public enable_shared_from_this { //! Register function in the temporary schema DUCKDB_API void RegisterFunction(CreateFunctionInfo &info); - //! Iterate a query's statements as a StatementIterator (iterator-style API). The caller drives - //! Peek() + GetStatement() to walk through ready-to-execute statements one by one + //! Iterate the user statements of a script as parsed. The submission of each one expands it, so a statement that + //! the engine rewrites into several is still one statement here DUCKDB_API StatementIterator IterateStatements(const string &query); - //! Preprocess a peel of parse-facing statements into engine-facing ones (PRAGMA reparse, - //! MULTI_STATEMENT unpack, transaction wrapping), replacing `buffer` in place. Acquires the - //! context lock internally when `lock` is null (callers that do not already hold it, e.g. the - //! shell). Drives StatementIterator's preprocessing. Pass a `preprocessor` to carry state, such as - //! an explicit BEGIN, across multiple calls for the same query. - DUCKDB_API void PreprocessStatements(vector> &buffer, - optional_ptr lock = nullptr, - optional_ptr preprocessor = nullptr); - //! Extract the logical plan of a query DUCKDB_API unique_ptr ExtractPlan(const string &query); @@ -332,10 +337,16 @@ class ClientContext : public enable_shared_from_this { //! when the query failed: the error is on the handle unique_ptr CompleteDelegatedInternal(ClientContextLock &lock, QueryResult &result); - //! Parse statements from a query + //! The user statements of a query, as parsed: a submission expands each one vector> ParseStatementsInternal(ClientContextLock &lock, const string &query); + //! Expands a user statement for a caller that binds it without running it; a statement that expands into + //! several is refused with the given message + unique_ptr ExpandSingleStatementInternal(ClientContextLock &lock, unique_ptr statement, + const char *refusal); + //! A verification that runs a statement of its own, like a prepared or an explained copy, is skipped when + //! allow_nested_statements is false void StatementVerification(ClientContextLock &lock, unique_ptr &statement, - QueryParameters query_parameters); + QueryParameters query_parameters, bool allow_nested_statements = true); //! Whether a result that no call ended holds the connection, so that a new statement is refused bool OpenResultHoldsConnection(ClientContextLock &lock); @@ -351,10 +362,12 @@ class ClientContext : public enable_shared_from_this { //! Ends the active query as abandoned: nothing it did is committed void AbortInternal(ClientContextLock &lock); unique_ptr SubmitStatement(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, Retention retention); + const QueryParameters ¶meters, bool verify, Retention retention); + //! Starts a bound statement of the active query. Returns its handle; for a statement of a query that expands + //! into several, null, unless a result collector built its own result object unique_ptr SubmitPreparedStatementInternal(ClientContextLock &lock, shared_ptr statement_data_p, - const QueryParameters ¶meters, Retention retention); + const QueryParameters ¶meters); void CheckIfPreparedStatementIsExecutable(PreparedStatementData &statement); //! Internally prepare a SQL statement. Caller must hold the context_lock. @@ -362,7 +375,12 @@ class ClientContext : public enable_shared_from_this { unique_ptr statement, const QueryParameters ¶meters); unique_ptr SubmitStatementInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, Retention retention); + const QueryParameters ¶meters); + //! Expands a user statement into the statements it runs as, without the statements of its transaction handling + PreprocessedStatement PreprocessStatementInternal(ClientContextLock &lock, unique_ptr statement); + //! Runs the statements of a query that expands up to the one producing its result, inside the submission, so + //! that a result collector building its own result object serves that statement. Returns that object, if any + unique_ptr RunToResultStatementInternal(ClientContextLock &lock, QueryResult &handle); unique_ptr RunStatementInternal(ClientContextLock &lock, unique_ptr statement, const QueryParameters ¶meters, bool verify = true); unique_ptr PrepareInternal(ClientContextLock &lock, unique_ptr statement); @@ -370,9 +388,33 @@ class ClientContext : public enable_shared_from_this { unique_ptr LockContext(); - void BeginQueryInternal(ClientContextLock &lock, const SQLStatement &statement); + //! Begins the query of a user statement, whose first statement runs next + void BeginQueryInternal(ClientContextLock &lock, PreprocessedStatement statements, + const QueryParameters ¶meters, Retention retention); + //! Begins one statement of the active query: its query number, its text and its query log line + void BeginStatementInternal(ClientContextLock &lock, const string &query); + //! Refreshes the logger for the statement that begins + void RefreshLoggerInternal(); + //! Ends the statement of a query that expands into several once it finished, and starts the next one + QueryResultState StartNextStatementInternal(ClientContextLock &lock); + //! Ends the statement that finished and starts the next one. Returns the result object a collector built for it, + //! which only a statement started inside the submission may have + unique_ptr AdvanceStatementInternal(ClientContextLock &lock); + bool QueryDeadlinePassed() const; + //! Ends the statement of the active query that finished, when another one follows + void EndStatementInternal(ClientContextLock &lock); ErrorData EndQueryInternal(ClientContextLock &lock, bool success, bool invalidate_transaction, optional_ptr previous_error); + //! The result of the finished query. For a statement that expands into several and returns no rows, that is an + //! empty result in the query's format, the way a statement that returns nothing reports + unique_ptr TakeFinishedResultInternal(ClientContextLock &lock); + //! A finished result without rows in `format`, the chunk format when null + unique_ptr EmptyResultInternal(StatementType statement_type, StatementProperties properties, + vector types, vector names, + shared_ptr format); + //! Records the consumer's retention decision for a result statement that has not started. The first decision + //! stands; returns it + ResultLifetime DecideResultLifetimeInternal(ClientContextLock &lock, ResultLifetime decision); //! Wait until a task is available to execute void WaitForTask(ClientContextLock &lock, BaseQueryResult &result); @@ -395,6 +437,8 @@ class ClientContext : public enable_shared_from_this { const QueryParameters ¶meters); bool ErrorInvalidatesTransaction(ExceptionType type); + //! Whether an error ends the active query by invalidating the transaction it runs in + bool ErrorInvalidatesQueryTransaction(ExceptionType type); private: //! Lock on using the ClientContext in parallel diff --git a/src/duckdb/src/include/duckdb/main/config.hpp b/src/duckdb/src/include/duckdb/main/config.hpp index ff6e23f80..29c921aca 100644 --- a/src/duckdb/src/include/duckdb/main/config.hpp +++ b/src/duckdb/src/include/duckdb/main/config.hpp @@ -172,6 +172,45 @@ struct DBConfigOptions { bool operator==(const DBConfigOptions &other) const; }; +//! The replacement scans of a database. Scans can be registered while queries are being bound, so the list is +//! guarded by a lock and the binder iterates over a snapshot. +class ReplacementScanList { +public: + ReplacementScanList() = default; + ReplacementScanList(ReplacementScanList &&other) noexcept { + lock_guard guard(other.lock); + scans = std::move(other.scans); + } + ReplacementScanList &operator=(ReplacementScanList &&other) noexcept { + if (this != &other) { + lock_guard other_guard(other.lock); + lock_guard guard(lock); + scans = std::move(other.scans); + } + return *this; + } + + template + void emplace_back(ARGS &&...args) { // NOLINT + auto scan = make_shared_ptr(std::forward(args)...); + lock_guard guard(lock); + scans.push_back(std::move(scan)); + } + void push_back(ReplacementScan scan) { // NOLINT + auto shared_scan = make_shared_ptr(std::move(scan)); + lock_guard guard(lock); + scans.push_back(std::move(shared_scan)); + } + vector> Snapshot() const { + lock_guard guard(lock); + return scans; + } + +private: + mutable mutex lock; + vector> scans; +}; + struct DBConfig { friend class DatabaseInstance; friend class StorageManager; @@ -183,7 +222,7 @@ struct DBConfig { DUCKDB_API ~DBConfig(); //! Replacement table scans are automatically attempted when a table name cannot be found in the schema - vector replacement_scans; + ReplacementScanList replacement_scans; //! The FileSystem to use, can be overwritten to allow for injecting custom file systems for testing purposes (e.g. //! RamFS or something similar) @@ -320,16 +359,21 @@ struct DBConfig { //! Returns the default value of an option static SettingLookupResult TryGetDefaultValue(optional_ptr option, Value &result); - bool CanAccessFile(const string &path, FileType type); + //! The opener (if any) is used to expand "~" using the connection-specific home_directory + bool CanAccessFile(const string &path, FileType type, optional_ptr opener = nullptr); void AddAllowedConfig(const Identifier &config_name); void AddAllowedDirectory(const string &path); void AddAllowedPath(const string &path); + //! Whether the given optimizer is disabled (disabled_optimizers) + DUCKDB_API bool IsOptimizerDisabled(OptimizerType type) const; + DUCKDB_API set GetDisabledOptimizers() const; + DUCKDB_API void SetDisabledOptimizers(set disabled_optimizers); //! Allows a database file and its WAL files, so a database can be opened while external access is disabled. //! Only possible through API calls, not SQL calls. void AddAllowedDatabasePath(const string &database_path); vector GetAllowedDirectories() const; vector GetAllowedPaths() const; - string SanitizeAllowedPath(const string &path) const; + string SanitizeAllowedPath(const string &path, optional_ptr opener = nullptr) const; ExtensionCallbackManager &GetCallbackManager(); const ExtensionCallbackManager &GetCallbackManager() const; @@ -345,6 +389,8 @@ struct DBConfig { mutable mutex config_lock; //! Guards allowed_paths and allowed_directories, which a running instance can extend while files are being opened mutable mutex allowed_paths_lock; + //! Guards disabled_optimizers, which can be changed while other connections plan queries + mutable mutex disabled_optimizers_lock; unique_ptr compression_functions; unique_ptr encoding_functions; unique_ptr arrow_extensions; diff --git a/src/duckdb/src/include/duckdb/main/connection.hpp b/src/duckdb/src/include/duckdb/main/connection.hpp index 53ce22789..bd1d62923 100644 --- a/src/duckdb/src/include/duckdb/main/connection.hpp +++ b/src/duckdb/src/include/duckdb/main/connection.hpp @@ -133,7 +133,7 @@ class Connection { //! DEFAULT_SCHEMA. DUCKDB_API unique_ptr TableInfo(const Identifier &table_name); - //! Extract a set of SQL statements from a specific query + //! Extract the user statements of a script as parsed, without expanding any of them DUCKDB_API vector> ExtractStatements(const string &query); //! Extract the logical plan that corresponds to a query DUCKDB_API unique_ptr ExtractPlan(const string &query); diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index ceb405b13..0671844d9 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -717,10 +717,12 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"pi", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"position", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_clear_cache", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, + {"postgres_close_pinned_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_configure_pool", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_execute", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_hstore_get", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_hstore_to_json", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"postgres_pin_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_query", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"pow", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"power", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -1357,7 +1359,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"mysql_bit1_as_boolean", "mysql_scanner"}, {"mysql_debug_show_queries", "mysql_scanner"}, {"mysql_enable_filter_pushdown", "mysql_scanner"}, - {"mysql_enable_remote_pushdown_ddl", "mysql_scanner"}, + {"mysql_enable_remote_pushdown", "mysql_scanner"}, {"mysql_enable_transactions", "mysql_scanner"}, {"mysql_incomplete_dates_as_nulls", "mysql_scanner"}, {"mysql_pool_acquire_mode", "mysql_scanner"}, @@ -1377,13 +1379,12 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_connection_cache", "postgres_scanner"}, {"pg_connection_limit", "postgres_scanner"}, {"pg_debug_show_queries", "postgres_scanner"}, - {"pg_experimental_filter_pushdown", "postgres_scanner"}, + {"pg_enable_filter_pushdown", "postgres_scanner"}, {"pg_idle_in_transaction_timeout_millis", "postgres_scanner"}, {"pg_null_byte_replacement", "postgres_scanner"}, {"pg_numeric_as_varchar", "postgres_scanner"}, {"pg_numeric_nan_as_null", "postgres_scanner"}, {"pg_oauth_token", "postgres_scanner"}, - {"pg_order_pushdown", "postgres_scanner"}, {"pg_pages_per_task", "postgres_scanner"}, {"pg_pool_acquire_mode", "postgres_scanner"}, {"pg_pool_enable_reaper_thread", "postgres_scanner"}, @@ -1393,6 +1394,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_pool_max_connections", "postgres_scanner"}, {"pg_pool_max_lifetime_millis", "postgres_scanner"}, {"pg_pool_wait_timeout_millis", "postgres_scanner"}, + {"pg_query_mode", "postgres_scanner"}, {"pg_staleness_query", "postgres_scanner"}, {"pg_staleness_query_enabled", "postgres_scanner"}, {"pg_statement_timeout_millis", "postgres_scanner"}, diff --git a/src/duckdb/src/include/duckdb/main/extension_manager.hpp b/src/duckdb/src/include/duckdb/main/extension_manager.hpp index b7c0f731b..d45cc4e4b 100644 --- a/src/duckdb/src/include/duckdb/main/extension_manager.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_manager.hpp @@ -32,10 +32,16 @@ class ExtensionActiveLoad { ExtensionActiveLoad(DatabaseInstance &db, ExtensionInfo &info, Identifier extension_name_p, Identifier alias_p) : db(db), load_lock(info.lock), info(info), extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; + ExtensionActiveLoad(DatabaseInstance &db, shared_ptr info_p, Identifier extension_name_p, + Identifier alias_p) + : db(db), info_owner(std::move(info_p)), load_lock(info_owner->lock), info(*info_owner), + extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; ~ExtensionActiveLoad() = default; DatabaseInstance &db; + //! Keeps the info alive when the extension is removed from the manager during the load + shared_ptr info_owner; unique_lock load_lock; ExtensionInfo &info; Identifier extension_name; @@ -52,7 +58,7 @@ class ExtensionManager { DUCKDB_API bool ExtensionIsLoaded(const string &name); DUCKDB_API vector GetExtensions(); - DUCKDB_API optional_ptr GetExtensionInfo(const string &name); + DUCKDB_API shared_ptr GetExtensionInfo(const string &name); DUCKDB_API unique_ptr BeginLoad(const ExtensionLoadOptions &options); DUCKDB_API static ExtensionManager &Get(DatabaseInstance &db); @@ -63,7 +69,7 @@ class ExtensionManager { private: DatabaseInstance &db; mutex lock; - identifier_map_t> loaded_extensions_info; + identifier_map_t> loaded_extensions_info; string extension_load_prefix; }; diff --git a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp index 42305444e..7ea6228e3 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp @@ -9,6 +9,9 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/mutex.hpp" +#include "duckdb/common/shared_ptr.hpp" +#include "duckdb/common/time_point.hpp" #include namespace duckdb { @@ -16,32 +19,116 @@ namespace duckdb { struct HTTPParams; //! An attempt's decision; constructing one does not consume retry budget. +//! +//! The loop that runs an operation is its coordinator: after inspecting a response (status, headers and body) it +//! decides explicitly whether the operation succeeded, failed for good, is retried, or is re-sent corrected. The budget +//! only admits retries, waits between them and keeps the host quota's books, refunding once after a reported success. +//! Finish() is the decision of a loop that does not judge the operation itself (core's request loop, or a provider +//! that has not adopted the explicit protocol): with a status-owned budget the quota is then settled from the HTTP +//! status core recorded; a coordinator-owned budget waits for the coordinator's report. class HTTPRetryDecision { friend class HTTPRetryBudget; friend class HTTPUtil; public: + //! Stop without judging the operation: a nested loop leaves the decision to the coordinator. The outermost loop of + //! a status-owned budget settles the quota from the HTTP status core recorded (the pre-protocol behaviour); a + //! coordinator-owned budget stays unsettled until ReportSuccess() or ReportFailure(). DUCKDB_API static HTTPRetryDecision Finish(); + //! The operation succeeded: the coordinator validated the response, the quota is refunded once. Only the owner + //! of a coordinator-owned budget decides this. + DUCKDB_API static HTTPRetryDecision Success(); + //! The operation failed for good (e.g. an error document behind HTTP 200): nothing is refunded. Only the owner of + //! a coordinator-owned budget decides this. + DUCKDB_API static HTTPRetryDecision Failure(); + //! Re-send after correcting the request (credentials, region, endpoint): no retry is consumed and no back-off is + //! waited, bounded by HTTPRetryBudget::MAX_CORRECTIONS per operation. + DUCKDB_API static HTTPRetryDecision Correction(); + //! Retry after a received server error (e.g. HTTP 500). DUCKDB_API static HTTPRetryDecision Retry(); + //! Retry after a transport failure (connection error, timeout, truncated body): no response was received. + DUCKDB_API static HTTPRetryDecision Transient(); + //! Retry after a throttling response (HTTP 429/503), honoring an optional Retry-After header value. + DUCKDB_API static HTTPRetryDecision Throttled(const string &retry_after = string()); private: - enum class Type : uint8_t { FINISH, RETRY, THROTTLED }; + enum class Type : uint8_t { FINISH, SUCCESS, FAILURE, CORRECTION, RETRY, TRANSIENT, THROTTLED }; explicit HTTPRetryDecision(Type type_p, string retry_after_p = {}) : type(type_p), retry_after(std::move(retry_after_p)) { } - static HTTPRetryDecision Throttled(const string &retry_after); private: Type type; string retry_after; }; -//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. +//! Retry tokens shared by every operation against one host, after the AWS SDK retry quota. +//! Retries spend tokens and successes refund them, so a host that keeps failing stops being retried. +//! A host nobody has talked to for a while starts over with a full quota, like the CRT drops an idle host's bucket. +class HTTPRetryQuota { +public: + static constexpr int64_t INITIAL_CAPACITY = 500; + //! Cost of retrying a received server error or throttle: the server answered, it is just unhappy. + static constexpr int64_t RETRY_COST = 5; + //! Cost of retrying a transport failure: nothing is known about the server, so it costs more. + static constexpr int64_t TRANSIENT_RETRY_COST = 10; + //! Refund for a request that succeeded without retrying. + static constexpr int64_t NO_RETRY_REFUND = 1; + //! A quota untouched for this long is full again when it is next used. + static constexpr int64_t IDLE_RESET_MS = 60 * 1000; + +public: + //! Take `cost` tokens; false (and nothing taken) when fewer are available. + DUCKDB_API bool TryAcquire(int64_t cost, int64_t now_ms = TimePoint::GetTickMs()); + //! Return tokens, never above the initial capacity. + DUCKDB_API void Release(int64_t amount, int64_t now_ms = TimePoint::GetTickMs()); + DUCKDB_API int64_t Available() const; + //! Milliseconds since the quota was last charged or refunded. + DUCKDB_API int64_t IdleMs(int64_t now_ms = TimePoint::GetTickMs()) const; + +private: + //! Refill an idle quota and record the use; the lock must be held. + void Touch(int64_t now_ms); + +private: + mutable mutex lock; + int64_t capacity = INITIAL_CAPACITY; + int64_t last_used_ms = TimePoint::GetTickMs(); +}; + +//! Who settles an operation's outcome with the host quota, fixed when the operation starts. +enum class HTTPRetryOutcomeOwner : uint8_t { + //! Nobody validates beyond the HTTP status: the status core recorded settles the quota once the outermost loop + //! stops. Core's own request loop and loops that have not adopted the explicit protocol. + STATUS, + //! The coordinator that runs the operation validates the response and settles the quota itself, with a + //! Success/Failure decision or ReportSuccess/ReportFailure after its loop; core's status is never final for it. + //! An operation whose coordinator never reports stays charged, like one whose outcome is unknown. + COORDINATOR +}; + +//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. One budget is one +//! logical operation (one `amz-sdk-invocation-id`): every send it makes, including corrected re-sends, is one of +//! its attempts, as in the AWS SDK's attempt loop. The host quota is settled once per operation, when its outcome is +//! known, by the owner fixed at construction: a coordinator reports it (Success/Failure decisions or +//! ReportSuccess/ReportFailure, whenever it has validated the response, inside or after its loop); for a status-owned +//! budget it is taken from the HTTP status core recorded (RecordStatusOutcome). A status recorded inside an outer Run +//! waits for that loop to stop, since an outer loop may still retry. class HTTPRetryBudget { friend class HTTPUtil; public: - DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms); + //! Longest single back-off sleep, as in the AWS SDKs. + static constexpr uint64_t MAX_BACKOFF_MS = 20000; + //! Retries granted beyond `http_retries` for throttled responses: the server asked to slow down, not to stop. + static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; + //! Corrected re-sends admitted per operation: a credential refresh and a region redirect, with room for a + //! provider that needs both again after the other; past this the operation ends with the last response. + static constexpr uint64_t MAX_CORRECTIONS = 4; + +public: + DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms, + HTTPRetryOutcomeOwner owner = HTTPRetryOutcomeOwner::STATUS); HTTPRetryBudget(const HTTPRetryBudget &) = delete; HTTPRetryBudget &operator=(const HTTPRetryBudget &) = delete; @@ -51,21 +138,88 @@ class HTTPRetryBudget { DUCKDB_API void Run(const std::function &attempt); //! Retries admitted for HTTP 429 or 503 responses. DUCKDB_API uint64_t ThrottledRetries() const; + //! Retries admitted across all participating requests. + DUCKDB_API uint64_t RetriesUsed() const; + //! Number the next send of the operation: the first call returns 1. + DUCKDB_API uint64_t BeginAttempt(); + //! Sends so far that were neither the first nor an admitted retry: re-sends after the coordinator corrected the + //! request (credentials, region), which do not consume retries. + DUCKDB_API uint64_t Corrections() const; + //! Most attempts the operation can make: the first one, `http_retries`, the throttle extras and the corrected + //! re-sends made so far, so that every numbered attempt stays within the bound advertised with it. + DUCKDB_API uint64_t MaxAttempts() const; + //! Whether the current Run refused a retry because the host's quota ran out. + DUCKDB_API bool QuotaExhausted() const; + //! Whether the current Run refused a corrected re-send because MAX_CORRECTIONS were made already. + DUCKDB_API bool CorrectionsExhausted() const; + //! Charge the next admitted retries to this host's quota; a request to another host rebinds. + DUCKDB_API void BindQuota(shared_ptr quota); + //! Who settles this operation's outcome. + DUCKDB_API HTTPRetryOutcomeOwner Owner() const; + //! The coordinator's final word, for a coordinator that validates the response after its Run returned: the + //! operation succeeded, refund the last admitted retry to the quota it was charged to, or NO_RETRY_REFUND to the + //! bound quota. Settles once; a later report of the same operation changes nothing. Coordinator-owned budgets only. + DUCKDB_API void ReportSuccess(); + //! The coordinator's final word: the operation failed, the last admitted retry is not refunded. Coordinator-owned + //! budgets only. + DUCKDB_API void ReportFailure(); + //! What the HTTP status of the last response says, recorded by core's request loop. For a status-owned budget it + //! settles the quota, at once outside a Run or when the outermost Run stops; a coordinator-owned budget ignores + //! it, its coordinator judges the response. + DUCKDB_API void RecordStatusOutcome(bool success); + //! Whether a Run of this budget is in progress, i.e. a nested loop is running inside an outer one. + DUCKDB_API bool InRun() const; + + //! Back-off window for `retry` is [0, min(wait * backoff^(retry - 1), MAX_BACKOFF_MS)]: full jitter, or its upper + //! half for throttled retries so they always keep some distance. The result is raised to `floor_ms`. + DUCKDB_API static uint64_t BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, + uint64_t random_value, bool throttled = false); + //! Parse a Retry-After header (delay in seconds or an HTTP-date) into a delay in milliseconds. + DUCKDB_API static bool TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms); private: //! Core's hook runs after admission/backoff, outside the attempt's transport-error handling. void Run(const std::function &attempt, const std::function &before_retry); bool ConsumeAndWait(const HTTPRetryDecision &decision); + //! Admit a corrected re-send within MAX_CORRECTIONS. + bool AdmitCorrection(); + uint64_t ThrottleExtraRetries() const; + //! Settle the operation's outcome with the quota once: refund on success, then forget the last admitted retry. + void Settle(bool success); + //! Explicit decisions and reports belong to a coordinator-owned budget; anything else is a programming error. + void RequireCoordinator(const char *what) const; + //! Forget the last admitted retry, so that nothing is refunded for it. + void ForgetLastRetry(); private: //! Retry settings captured for the operation. const uint64_t retries; const uint64_t retry_wait_ms; const float retry_backoff; + //! Who settles the outcome with the quota. + const HTTPRetryOutcomeOwner owner; //! Retries admitted across all participating requests. uint64_t retries_used = 0; uint64_t throttled_retries = 0; + //! Sends numbered so far. + uint64_t attempts = 0; + //! Corrected re-sends admitted by Correction() decisions. + uint64_t corrections_admitted = 0; + //! Nesting depth of Run; status outcomes recorded while this is positive wait for the outermost loop's decision. + uint64_t run_depth = 0; + //! Outcome core recorded from the HTTP status, pending the outermost loop's stop (status-owned budgets only). + enum class RecordedOutcome : uint8_t { NONE, SUCCESS, FAILURE }; + RecordedOutcome status_outcome = RecordedOutcome::NONE; + //! Whether the operation's outcome has been settled with the quota; a new admitted retry starts a new settlement. + bool outcome_settled = false; + //! Quota of the host the current request goes to, if any. + shared_ptr quota; + //! Quota and tokens of the most recent admitted retry, refunded on success. + shared_ptr last_retry_quota; + int64_t last_retry_cost = 0; + bool quota_exhausted = false; + bool corrections_exhausted = false; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/http/http_util.hpp b/src/duckdb/src/include/duckdb/main/http/http_util.hpp index 309dcfe77..40385438e 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_util.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_util.hpp @@ -14,6 +14,9 @@ #include "duckdb/common/enums/http_status_code.hpp" #include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/time_point.hpp" +#include "duckdb/common/mutex.hpp" +#include "duckdb/common/unordered_map.hpp" +#include "duckdb/main/http/http_retry_budget.hpp" #include namespace duckdb { @@ -399,8 +402,18 @@ class HTTPUtil { virtual unique_ptr SendRequest(BaseRequest &request, unique_ptr &client); virtual void LogRequest(BaseRequest &request, optional_ptr response); - //! Whether a failed request should be retried, possibly using HTTPResponse information, and allowing overrides + //! Whether a failed request should be retried. Judges received responses and statuses a handler turned into an + //! exception alike; core knows only the generic statuses (IsRetryableStatus), a provider adds what its service + //! says in the body (e.g. an S3 error document whose code is retryable behind a final-looking status). DUCKDB_API virtual bool ShouldRetry(const BaseRequest &request, const HTTPResponse &response); + //! Runs before every attempt with the 1-based attempt number and the configured maximum, e.g. to tag headers. + DUCKDB_API virtual void PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts); + //! Retry quota shared by all requests of this provider against one host. + DUCKDB_API shared_ptr GetRetryQuota(const string &proto_host_port); + //! Whether a received status is worth retrying: 408, 418, 429 and the server errors that can clear up. + DUCKDB_API static bool IsRetryableStatus(HTTPStatusCode status); + //! Hosts remembered for retry quotas; past this the host unused for longest makes room. + static constexpr idx_t MAX_RETRY_QUOTAS = 1024; //! Whether replaying this request is safe. POST is the only method we cannot assume is idempotent. DUCKDB_API static bool IsIdempotent(RequestType type); @@ -419,9 +432,15 @@ class HTTPUtil { HTTPClientCachePolicy initial_cache_policy, HTTPClientCachePolicy &retry_cache_policy); +private: + //! Protects retry_quotas. + mutex retry_quota_lock; + //! Per-host retry quotas, created on first use; budgets keep theirs alive across an eviction. + unordered_map> retry_quotas; + public: static duckdb::unique_ptr - RunRequestWithRetry(const std::function(void)> &on_request, const BaseRequest &request, + RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, const std::function &retry_cb); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/prepared_statement.hpp b/src/duckdb/src/include/duckdb/main/prepared_statement.hpp index f4e199827..b99700d0a 100644 --- a/src/duckdb/src/include/duckdb/main/prepared_statement.hpp +++ b/src/duckdb/src/include/duckdb/main/prepared_statement.hpp @@ -43,11 +43,14 @@ struct PreparedStatementInfo { //! A handle to a prepared statement that lives in the client context. The statement itself is prepared through //! `PREPARE AS ...` and executed through `EXECUTE (...)` - this class only holds the name. +//! A statement that expands into several is not prepared in the client context: each execution submits it anew. class PreparedStatement { public: //! Create a handle to the prepared statement with the given name in the client context DUCKDB_API PreparedStatement(const shared_ptr &context, string name, string query, PreparedStatementInfo info); + //! Create a handle to a statement that does not expand into exactly one, whose result is known only at execution + DUCKDB_API PreparedStatement(const shared_ptr &context, unique_ptr statement); //! Create a prepared statement that was not successfully prepared DUCKDB_API explicit PreparedStatement(ErrorData error); @@ -88,6 +91,8 @@ class PreparedStatement { DUCKDB_API bool TryGetParameterType(const Identifier &identifier, LogicalType &result) const; //! Returns the map of parameter index to the expected type of parameter DUCKDB_API case_insensitive_map_t GetExpectedParameterTypes() const; + //! Whether the result names, types and properties are known before execution; runs nothing + DUCKDB_API bool MetadataAvailable() const; //! Non-blocking. Submits the prepared statement with the given arguments and returns its handle template @@ -206,10 +211,14 @@ class PreparedStatement { ErrorData error; //! The metadata of the statement, as it was prepared PreparedStatementInfo info; + //! The statement as the user wrote it, when it does not expand into exactly one + unique_ptr expanding_statement; private: - //! Create the `EXECUTE (...)` statement that runs this prepared statement with the given values - unique_ptr CreateExecuteStatement(const identifier_map_t &named_values) const; + //! Create the statement that runs this prepared statement with the given values: `EXECUTE (...)`, or a + //! copy of the statement that expands, whose values are handed to the submission in the parameters + unique_ptr CreateStatement(identifier_map_t &named_values, + QueryParameters ¶meters) const; unique_ptr SubmitRecursive(vector &values) { return Submit(values); diff --git a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp index d9319c0db..9e0261a74 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp @@ -12,6 +12,7 @@ #include "duckdb/common/enums/output_type.hpp" #include "duckdb/common/progress_bar/progress_bar.hpp" #include "duckdb/common/types/value.hpp" +#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unordered_set.hpp" #include "duckdb/common/constants.hpp" #include "duckdb/common/enums/metric_type.hpp" @@ -28,6 +29,41 @@ namespace duckdb { struct QueryProfileResult; enum class ProfilingParameterNames : uint8_t { FORMAT, COVERAGE, SAVE_LOCATION, MODE, METRICS }; +struct MetricTiming { + idx_t duration = 0; + timestamp_t start_time = timestamp_t::epoch(); + timestamp_t end_time = timestamp_t::epoch(); + bool has_bounds = false; + + void Add(idx_t addition) { + duration += addition; + } + + void Add(idx_t addition, timestamp_t start, timestamp_t end) { + Add(addition); + UpdateBounds(start, end); + } + + void Merge(const MetricTiming &other) { + Add(other.duration); + if (other.has_bounds) { + UpdateBounds(other.start_time, other.end_time); + } + } + +private: + void UpdateBounds(timestamp_t start, timestamp_t end) { + if (!has_bounds) { + start_time = start; + end_time = end; + has_bounds = true; + return; + } + start_time = MinValue(start_time, start); + end_time = MaxValue(end_time, end); + } +}; + class GatheredMetrics { public: GatheredMetrics() = default; @@ -72,7 +108,7 @@ class GatheredMetrics { } public: - void WriteMetricsToLog(ClientContext &context) const; + void WriteMetricsToLog(ClientContext &context, const unordered_map &timings) const; //! Copy all enabled metrics into a QueryProfileResult node using lowercase string keys void MetricsToProfileResult(QueryProfileResult &result) const; diff --git a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp index 383266135..fa5fdcc00 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp @@ -13,6 +13,7 @@ #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/profiler/profiling_node.hpp" #include "duckdb/common/profiler.hpp" +#include "duckdb/common/types/timestamp.hpp" namespace duckdb_yyjson { struct yyjson_mut_doc; @@ -44,7 +45,11 @@ struct QueryMetrics { public: void UpdateMetric(const string &key, idx_t addition) { - string_timings[key] += addition; + string_timings[key].Add(addition); + } + + void UpdateMetric(const string &key, idx_t addition, timestamp_t start_time, timestamp_t end_time) { + string_timings[key].Add(addition, start_time, end_time); } void UpdateMetricCounter(const string &key, idx_t addition) { @@ -68,7 +73,7 @@ struct QueryMetrics { if (it == string_timings.end()) { return 0.0; } - return static_cast(it->second) / 1e9; + return static_cast(it->second.duration) / 1e9; } idx_t GetStringCounter(const string &key) const { @@ -91,7 +96,7 @@ struct QueryMetrics { return total_memory_allocated.load(); } - const unordered_map &GetMetricTimings() const { + const unordered_map &GetMetricTimings() const { return string_timings; } @@ -119,7 +124,7 @@ struct QueryMetrics { void Merge(const QueryMetrics &other) { for (const auto &entry : other.string_timings) { - string_timings[entry.first] += entry.second; + string_timings[entry.first].Merge(entry.second); } for (const auto &entry : other.string_counters) { string_counters[entry.first] += entry.second; @@ -131,7 +136,7 @@ struct QueryMetrics { private: // String-keyed timings for optimizer, storage and phase timing metrics - unordered_map string_timings; + unordered_map string_timings; // String-keyed counters for storage counter metrics (e.g. "storage.wal_replay_entry_count") unordered_map string_counters; @@ -149,6 +154,7 @@ struct MetricsTimer { if (!is_active) { return; } + start_time = Timestamp::GetCurrentTimestamp(); profiler.Start(); } ~MetricsTimer() { @@ -164,12 +170,14 @@ struct MetricsTimer { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); + std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); } MetricsTimer &operator=(MetricsTimer &&other) noexcept { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); + std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); return *this; } @@ -181,7 +189,7 @@ struct MetricsTimer { } is_active = false; profiler.End(); - query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos()); + query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos(), start_time, Timestamp::GetCurrentTimestamp()); } void Reset() { @@ -196,6 +204,7 @@ struct MetricsTimer { optional_ptr query_metrics; string metric_name; Profiler profiler; + timestamp_t start_time = timestamp_t::epoch(); bool is_active; }; diff --git a/src/duckdb/src/include/duckdb/main/query_profiler.hpp b/src/duckdb/src/include/duckdb/main/query_profiler.hpp index 1ed2c856e..9446666a1 100644 --- a/src/duckdb/src/include/duckdb/main/query_profiler.hpp +++ b/src/duckdb/src/include/duckdb/main/query_profiler.hpp @@ -113,7 +113,7 @@ class QueryProfiler { //! Add to a metric counter (profiling-only). DUCKDB_API void AddToMetricCounter(const string &key, idx_t amount); //! Add parser time measured before the per-statement profiler was started. - void AddParserTime(const Profiler &parser_timer); + void AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time); //! Set an arbitrary metric value (profiling-only; no-op when profiling is disabled). DUCKDB_API void SetMetric(const string &key, Value new_value); diff --git a/src/duckdb/src/include/duckdb/main/query_result.hpp b/src/duckdb/src/include/duckdb/main/query_result.hpp index 2ec03f0fd..0ec695e99 100644 --- a/src/duckdb/src/include/duckdb/main/query_result.hpp +++ b/src/duckdb/src/include/duckdb/main/query_result.hpp @@ -41,6 +41,9 @@ class BaseQueryResult { DUCKDB_API virtual ~BaseQueryResult(); public: + //! Whether the result's schema and properties are known. False for a statement that expands into several until + //! the one producing the result is bound, unless it failed. Runs nothing + DUCKDB_API bool MetadataAvailable() const; //! Returns the type of the statement that created this result DUCKDB_API StatementType GetStatementType() const; //! Returns the properties of the statement that created this result @@ -61,6 +64,12 @@ class BaseQueryResult { DUCKDB_API ErrorData &GetErrorObject(); DUCKDB_API const ErrorData &GetErrorObject() const; +protected: + //! Creates a successful result whose metadata the statement producing it will provide + explicit BaseQueryResult(StatementType statement_type); + void SetMetadata(StatementProperties properties, vector types, vector names); + void RequireMetadata() const; + private: //! The type of the statement that created this result StatementType statement_type; @@ -70,6 +79,7 @@ class BaseQueryResult { vector types; //! The names of the result vector names; + bool metadata_available = true; //! Whether or not execution was successful bool success; //! The error (in case execution was not successful) @@ -90,6 +100,10 @@ class QueryResult : public BaseQueryResult { DUCKDB_API QueryResult(shared_ptr context, PreparedStatementData &statement, vector types, ClientProperties client_properties, shared_ptr buffer); + //! Creates the handle of a freshly submitted statement that expands into several: its metadata and buffer come + //! from the statement that produces the result, once that is bound + QueryResult(shared_ptr context, StatementType statement_type, shared_ptr format, + ClientProperties client_properties); //! Creates a detached result over an existing, finalized collection DUCKDB_API QueryResult(StatementType statement_type, StatementProperties properties, vector names, unique_ptr collection, ClientProperties client_properties); @@ -113,7 +127,8 @@ class QueryResult : public BaseQueryResult { static void DeduplicateColumns(vector &names); public: - //! Returns the query's current state. Does not participate in execution. + //! Returns the query's current state. Runs no task; for a statement that expands into several, it starts the + //! next statement once the running one finished. DUCKDB_API QueryResultState Poll(); //! Executes a single task of the query on the calling thread. Decides nothing: READY means the engine is //! waiting for the retention decision, and every further call returns READY, running nothing, until a @@ -215,6 +230,10 @@ class QueryResult : public BaseQueryResult { //! recorded, and reports it as an error state QueryResultState Cancelled(); void CompleteInternal(ClientContextLock &lock); + //! Ends the query as failed after an exception escaped a call that holds the context lock, and releases the + //! context + void AbandonAfterThrowInternal(ClientContextLock &lock, ErrorData error); + void SettleRetainedInternal(ClientContextLock &lock); //! Ends a query whose retained execution finished and collects its rows QueryResultState EndFinishedInternal(ClientContextLock &lock); void HandleFetchFailure(ClientContextLock &lock, ErrorData error); @@ -228,6 +247,12 @@ class QueryResult : public BaseQueryResult { void InitializeChunkFormatState(); //! Takes the rows only: the handle already holds the format and state its buffer settled at submission void AdoptCollected(QueryResult &produced); + //! Takes the metadata and buffer of the statement that produces the result of a statement that expands + void AdoptResultStatement(StatementProperties properties, PreparedStatementData &statement, + shared_ptr buffer); + //! Takes the metadata, format state and rows of a finished statement that expands into several and returns no + //! rows + void AdoptRowlessResult(QueryResult &produced); DUCKDB_API void PrepareCollected(const char *expected); DUCKDB_API const ResultFormatGlobalState &CheckedFormatState(const char *expected) const; [[noreturn]] void ThrowFormatMismatch(const char *expected) const; @@ -236,11 +261,12 @@ class QueryResult : public BaseQueryResult { //! The client context this result belongs to. Null once the query has ended shared_ptr context; //! The buffer created for this query at submission. It carries the retention decision and, for - //! a stream, the units (null for a detached or an error result) + //! a stream, the units (null for a detached or an error result, and for a statement that expands until the one + //! producing the result has started) shared_ptr buffer; //! Set at construction; never null for a non-error result shared_ptr format; - //! Set at construction; never null for a non-error result + //! Never null for a non-error result whose metadata is available shared_ptr format_state; //! In the format's own storage, which also holds the fetch cursor unique_ptr collection; diff --git a/src/duckdb/src/include/duckdb/main/query_result_stream.hpp b/src/duckdb/src/include/duckdb/main/query_result_stream.hpp index 95953d773..20570e70a 100644 --- a/src/duckdb/src/include/duckdb/main/query_result_stream.hpp +++ b/src/duckdb/src/include/duckdb/main/query_result_stream.hpp @@ -19,11 +19,10 @@ namespace duckdb { //! Whether a reader that picks the consumption mode on its consumer's behalf streams a statement: only a SELECT that //! returns rows does. written_type is the statement as written, since a prepared execution reports the EXECUTE that -//! runs it, and the properties are the running statement's, since an unresolved parameter type leaves the prepared -//! statement's own copy incomplete. A reader that may stop reading also needs a statement that writes nothing, since -//! a statement abandoned before its end is rolled back -DUCKDB_API bool StreamsByDefault(StatementType written_type, const StatementProperties &properties, - bool reads_to_the_end = true); +//! runs it; the result's properties are read only for a SELECT, since a statement that expands into several has none +//! before it runs. A reader that may stop reading also needs a statement that writes nothing, since a statement +//! abandoned before its end is rolled back +DUCKDB_API bool StreamsByDefault(StatementType written_type, const QueryResult &result, bool reads_to_the_end = true); //! Opening a stream settles retention on draining and consumes the handle: no random access class ResultStreamBase { @@ -38,7 +37,8 @@ class ResultStreamBase { public: //! Reports READY while a unit is poppable, else where execution stands. Runs no task. An interrupt - //! or an execution error is recorded on the stream and reported as EXECUTION_ERROR + //! or an execution error is recorded on the stream and reported as EXECUTION_ERROR. Like ExecuteTask, it does + //! not end the query at FINISHED; reading the end of the stream does DUCKDB_API QueryResultState Poll(); //! Executes a single task of the query on the calling thread. An interrupt or an execution error //! is recorded on the stream and reported as EXECUTION_ERROR @@ -49,6 +49,8 @@ class ResultStreamBase { DUCKDB_API void Close(); //! Whether this stream is still the connection's open result DUCKDB_API bool IsOpen(); + //! Whether the result's schema and properties are known. Runs nothing + DUCKDB_API bool MetadataAvailable() const; DUCKDB_API void SetError(ErrorData error); DUCKDB_API bool HasError() const; @@ -77,6 +79,9 @@ class ResultStreamBase { private: unique_ptr FetchUnitInternal(ClientContextLock &lock); + //! Before the statement producing the result of a statement that expands into several started, the stream + //! drives the statements before it through the query + QueryResultState DriveWithoutBufferInternal(ClientContextLock &lock, bool execute_task); //! Runs a buffer call under the context lock and maps any failure onto the stream as //! EXECUTION_ERROR. Once the stream has ended it keeps reporting the terminal state QueryResultState GuardedInternal(const char *name, diff --git a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp index 99a81cbb6..010b1afe9 100644 --- a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp @@ -9,6 +9,7 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/identifier.hpp" namespace duckdb { class DatabaseInstance; @@ -25,6 +26,8 @@ struct CreateHTTPSecretFunctions { static vector GetDefaultSecretTypes(); //! Get the default secret functions static vector GetDefaultSecretFunctions(); + //! Current sensitive keys for both new and persisted HTTP secrets. + static identifier_set_t GetRedactedKeys(); protected: //! HTTP secret CONFIG provider diff --git a/src/duckdb/src/include/duckdb/main/secret/secret.hpp b/src/duckdb/src/include/duckdb/main/secret/secret.hpp index 6183a6e5c..7299301da 100644 --- a/src/duckdb/src/include/duckdb/main/secret/secret.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/secret.hpp @@ -217,13 +217,7 @@ class KeyValueSecret : public BaseSecret { Deserialize(Deserializer &deserializer, BaseSecret base_secret) { // NOLINT(performance-unnecessary-value-param): callback signature. auto result = make_uniq(base_secret); - Value secret_map_value; - deserializer.ReadProperty(201, "secret_map", secret_map_value); - - for (const auto &entry : ListValue::GetChildren(secret_map_value)) { - auto kv_struct = StructValue::GetChildren(entry); - result->secret_map[Identifier(kv_struct[0].ToString())] = kv_struct[1]; - } + ReadSecretMap(deserializer, result->secret_map); Value redact_set_value; deserializer.ReadProperty(202, "redact_keys", redact_set_value); @@ -261,6 +255,9 @@ class KeyValueSecret : public BaseSecret { identifier_tree_t secret_map; //! keys that are sensitive and should be redacted identifier_set_t redact_keys; + +private: + static void ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result); }; // Helper class to fetch secret parameters in a cascading way. The idea being that in many cases there is a direct diff --git a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp index 7111b76ce..a7a8bc886 100644 --- a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp +++ b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp @@ -8,28 +8,21 @@ #pragma once -#include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/profiler.hpp" +#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unique_ptr.hpp" -#include "duckdb/common/vector.hpp" #include "duckdb/main/parse_iterator.hpp" namespace duckdb { class ClientContext; -class ClientContextLock; class SQLStatement; -class StatementPreprocessor; -//! Iterator over the engine-facing statements of a query. +//! Iterator over the user statements of a script, as parsed. The submission of each statement expands it. //! //! Usage: //! StatementIterator it(ParseIterator(context, sql)); //! while (it.Peek()) { //! auto stmt = it.GetStatement(); -//! if (!stmt) { -//! continue; // a peel that preprocessing swallowed (empty expansion) -//! } -//! // ready-to-execute statement //! } //! class StatementIterator { @@ -45,48 +38,33 @@ class StatementIterator { // Not move-assignable: holds a ClientContext reference, which cannot be rebound. StatementIterator &operator=(StatementIterator &&) = delete; - //! Returns true while more input remains (a buffered engine statement, or another parse-facing - //! statement to pull). Parses ahead as needed but does NOT preprocess — safe as a lookahead. + //! Returns true while another statement remains. Parses ahead, so it throws a parser error. DUCKDB_API bool Peek(); //! Grammar-free predicate: is there another statement after the current one? Never parses and //! never throws (see ParseIterator::HasMore), so it is safe to consult before the current - //! statement has executed — e.g. to decide whether the current statement is the last. + //! statement has executed, for example to decide whether the current statement is the last. DUCKDB_API bool HasMore(); - //! Pull + preprocess the next engine-facing statement. Returns nullptr when a peel preprocesses - //! to nothing (skip with `continue`) or when the input is exhausted (Peek would return false). - //! Self-locking variant for callers that do not hold the context lock. + //! Pull the next statement. Returns nullptr once the input is exhausted (Peek would return false). DUCKDB_API unique_ptr GetStatement(); - //! Same, for callers that already hold the context lock. - DUCKDB_API unique_ptr GetStatementWithLock(ClientContextLock &lock); //! Pull the next statement and attach its parser timing to the per-statement query profiler. DUCKDB_API unique_ptr GetStatementForExecution(); - //! Same, for callers that already hold the context lock. - DUCKDB_API unique_ptr GetStatementForExecutionWithLock(ClientContextLock &lock); private: - //! Shared body for both Get variants. `lock` is null for the self-locking path (preprocessing - //! then acquires the lock itself) or the held lock for callers that already have it. - unique_ptr GetStatementInternal(optional_ptr lock, bool profile_statement); + unique_ptr GetStatementInternal(bool profile_statement); private: - //! The single parse-facing source this iterator preprocesses. Always constructed — the - //! single-statement ctor forwards to ParseIterator's single-statement ctor. + //! The single parse-facing source this iterator pulls from. ParseIterator source; - //! The bound context, inherited from `source`. Used for preprocessing / transaction state / locking. + //! The bound context, inherited from `source`, whose profiler receives the parser timing. ClientContext &context; - //! The parse-facing statement buffered by Peek, waiting to be preprocessed. + //! The statement parsed ahead by Peek. unique_ptr pending_statement; - //! Parser timing for the currently buffered parse-facing statement. + //! Parser timing for the statement parsed ahead. Profiler parser_timer; - //! Engine-facing statements produced by preprocessing one parse-facing peel. Drained - //! one-at-a-time across GetStatement calls before pulling + preprocessing the next peel. - vector> buffer; - idx_t buffer_cursor = 0; - //! Shared across all peels, so a peel is preprocessed knowing about an explicit BEGIN in an earlier - //! peel, even when the caller hasn't executed that BEGIN yet. - unique_ptr preprocessor; + timestamp_t parser_start_time = timestamp_t::epoch(); + timestamp_t parser_end_time = timestamp_t::epoch(); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp index 0647b333d..25407233e 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp @@ -21,6 +21,12 @@ class JoinOrderOptimizer { explicit JoinOrderOptimizer(ClientContext &context); public: + //! Deepest nesting of child optimizers (one per non-reorderable operator, projection, aggregate, ...) that is still + //! join-order optimized. Every level recurses through Optimize/Build/ExtractJoinRelations, so a very deep plan + //! (e.g. a long chain of EXCEPT/INTERSECT) would otherwise overflow the stack long before the plan depth limit is + //! hit; subplans below this depth keep their original join order, which is always a valid plan. + static constexpr idx_t MAX_RECURSION_DEPTH = 128; + JoinOrderOptimizer CreateChildOptimizer(); //! Perform join reordering inside a plan unique_ptr Optimize(unique_ptr plan, optional_ptr stats = nullptr); diff --git a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp index 334f3cde2..c7171f095 100644 --- a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp @@ -12,6 +12,7 @@ #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/types.hpp" #include "duckdb/planner/column_binding.hpp" +#include "duckdb/planner/column_binding_map.hpp" namespace duckdb { @@ -54,4 +55,18 @@ struct RelationStats { Identifier table_name; }; +//! Looks up column statistics by binding, the first relation and column with a binding wins +class RelationColumnStatsLookup { +public: + RelationColumnStatsLookup() = default; + explicit RelationColumnStatsLookup(const RelationStats &stats); + +public: + void AddRelation(const RelationStats &stats); + optional_ptr Find(ColumnBinding binding) const; + +private: + column_binding_map_t> columns; +}; + } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp index 716dc2f72..82a6d54d4 100644 --- a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp @@ -129,6 +129,8 @@ class BaseColumnPruner : public LogicalOperatorVisitor { private: //! The current mode of the pruner, enables/disables certain behaviors BaseColumnPrunerMode mode = BaseColumnPrunerMode::DEFAULT; + //! True while visiting the child of OPERATOR_TRY. Do not push throwing CASTs (#25236). + bool inside_try = false; }; //! The RemoveUnusedColumns optimizer traverses the logical operator tree and removes any columns that are not required diff --git a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp index afc6450a5..b40a20121 100644 --- a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp +++ b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp @@ -14,6 +14,8 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/mutex.hpp" +#include "duckdb/common/condition_variable.hpp" +#include "duckdb/common/optional.hpp" #include "duckdb/common/deque.hpp" #include "duckdb/common/vector.hpp" #include "duckdb/common/serializer/async_memory_governor.hpp" @@ -177,6 +179,10 @@ class ScanReadAhead { read_ahead.pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; } --read_ahead.active_producers; + // a pushed job was signalled already, a released slot frees one thread and a done scan all of them + if (!committed || read_ahead.IsDone()) { + read_ahead.SignalProgress(read_ahead.IsDone()); + } } ScanReadAhead &read_ahead; @@ -198,16 +204,29 @@ class ScanReadAhead { lock_guard guard(lock); return !pending_jobs.empty(); } + //! The number of jobs that may be in flight under the current budget + idx_t ReservationDepth() const; //! Reserve an in-flight job slot for producing a job bool TryReserveSlot(); //! Schedule the job's I/O and admit the job to the queue void PushJob(unique_ptr job, vector> io_tasks); //! Release a read-ahead slot void ReleaseSlot(); + //! Wake a thread waiting for another thread to push a job, release a slot or finish producing, or all of them + void SignalProgress(bool wake_all); + //! SignalProgress for a caller that holds the lock + void NotifyProgress(bool wake_all); + //! Wake one waiting thread, or all of them + void WakeWaiters(bool wake_all); + //! Wait until the read-ahead made progress since the given epoch, or the timeout passed + void WaitForProgress(ClientContext &context, idx_t epoch); + idx_t ProgressEpoch() const; private: //! Minimum budget charge per job, beyond its scheduled I/O a job carries scan-state overhead (decode buffers) static constexpr idx_t MINIMUM_JOB_IO_CHARGE = 16ULL * 1024 * 1024; + //! How long a thread yields for another thread to push a job before it waits for it + static constexpr idx_t PRODUCER_YIELD_MICROS = 1000; private: ClientContext &context; @@ -233,6 +252,11 @@ class ScanReadAhead { atomic done {false}; //! Threads that reserved a slot but have not pushed their job yet atomic active_producers {0}; + //! Incremented whenever a waiting thread could make progress + atomic progress_epoch {0}; + //! Threads waiting for progress, only these need a notification + atomic waiting_threads {0}; + condition_variable progress; //! File-opens scheduled on the async pool that have not completed yet, shared with the open tasks shared_ptr> pending_opens; //! Async I/O executor (async pool) diff --git a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp index d1d76b01e..a2054a041 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp @@ -11,13 +11,13 @@ struct IsDistinctFromTail { struct ComparisonExpressionTail { ExpressionType comparison_type; - vector not_keywords; unique_ptr expression; }; struct BetweenInLikeOperator { bool has_not = false; unique_ptr expression; + vector> casts; }; struct ParsedOperator { @@ -31,7 +31,7 @@ struct OtherOperatorTail { unique_ptr expression; }; -enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR }; +enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR, CAST }; struct IsExpressionTail { ExpressionTailType type = ExpressionTailType::IS_TEST; @@ -39,6 +39,7 @@ struct IsExpressionTail { IsDistinctFromTail distinct; ComparisonExpressionTail comparison; OtherOperatorTail other; + unique_ptr cast; }; struct BinaryExpressionTail { diff --git a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp index 1d5f222e6..f8a55ed8f 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp @@ -1,5 +1,6 @@ #pragma once +#include "duckdb/common/pair.hpp" #include "duckdb/parser/peg/matcher.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/peg/parsed_grammar.hpp" @@ -41,7 +42,7 @@ struct CompiledGrammar { static shared_ptr DefaultGrammar(); //! Compile the base DuckDB grammar. static shared_ptr Create(); - //! Compile a grammar for the selected extensions without changing the client configuration. + //! Get the (cached) grammar for the selected extensions without changing the client configuration. static shared_ptr Create(const ClientContext &context, const vector &active_extensions); private: @@ -53,19 +54,27 @@ struct CompiledGrammar { const Matcher &top_level_statement_matcher; }; -//! Per-database holder for the compiled base grammar. +//! Per-database holder for the compiled base grammar and the grammars compiled for active grammar extensions. struct ParserCache { public: ParserCache(); ~ParserCache(); + //! The maximum number of grammars compiled for grammar extensions that are kept + static constexpr idx_t MAX_CACHED_EXTENSION_MATCHERS = 16; + shared_ptr GetMatcher(); + //! The grammar compiled for the given grammar extensions, in order - shared by all connections that use them + shared_ptr GetMatcher(const ClientContext &context, const vector &active_extensions); //! The grammar that forwards statements instead of interpreting them, used while CONNECT-ed shared_ptr GetPassthroughMatcher(const ClientContext &context); private: std::mutex mutex; shared_ptr matcher; + std::mutex extension_mutex; + //! The compiled grammars with the names of their grammar extensions in order, from least to most recently used + vector, shared_ptr>> extension_matchers; std::mutex passthrough_mutex; unique_ptr passthrough_dialect; }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp index 5032d4713..0b57885bf 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp @@ -155,13 +155,14 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 84, 121, 112, 101, 77, 111, 100, 105, 102, 105, 101, 114, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 63, 41, 10, + 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, 82, 111, 119, 84, 121, 112, 101, 32, 60, 45, 32, 82, 111, 119, 79, 114, 83, 116, 114, 117, 99, 116, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 63, 10, - 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 84, 121, 112, 101, 10, + 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, 85, 110, 105, 111, 110, 84, 121, 112, 101, 32, 60, 45, 32, 39, 85, 78, 73, 79, 78, 39, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 10, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 84, 121, 112, 101, 41, 41, 10, - 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 63, 10, - 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 10, - 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 84, 121, 112, 101, 10, + 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 63, 10, + 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 10, + 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 47, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 32, 47, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 10, @@ -179,6 +180,364 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 37, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 32, 60, 45, 32, 91, 32, 92, 116, 92, 110, 92, 114, 93, 42, 10, 76, 105, 115, 116, 40, 68, 41, 32, 60, 45, 32, 68, 32, 40, 39, 44, 39, 32, 68, 41, 42, 32, 39, 44, 39, 63, 10, 80, 97, 114, 101, 110, 115, 40, 68, 41, 32, 60, 45, 32, 39, 40, 39, 32, 68, 32, 39, 41, 39, 10, + 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 97, 98, 111, 114, 116, 39, 32, 47, 10, + 39, 97, 98, 115, 111, 108, 117, 116, 101, 39, 32, 47, 10, + 39, 97, 99, 99, 101, 115, 115, 39, 32, 47, 10, + 39, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 97, 100, 100, 39, 32, 47, 10, + 39, 97, 100, 109, 105, 110, 39, 32, 47, 10, + 39, 97, 102, 116, 101, 114, 39, 32, 47, 10, + 39, 97, 103, 103, 114, 101, 103, 97, 116, 101, 39, 32, 47, 10, + 39, 97, 108, 115, 111, 39, 32, 47, 10, + 39, 97, 108, 116, 101, 114, 39, 32, 47, 10, + 39, 97, 108, 119, 97, 121, 115, 39, 32, 47, 10, + 39, 97, 112, 112, 114, 111, 120, 39, 32, 47, 10, + 39, 97, 115, 115, 101, 114, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 97, 115, 115, 105, 103, 110, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 97, 116, 116, 97, 99, 104, 39, 32, 47, 10, + 39, 97, 116, 116, 114, 105, 98, 117, 116, 101, 39, 32, 47, 10, + 39, 98, 97, 99, 107, 119, 97, 114, 100, 39, 32, 47, 10, + 39, 98, 101, 102, 111, 114, 101, 39, 32, 47, 10, + 39, 98, 101, 103, 105, 110, 39, 32, 47, 10, + 39, 98, 101, 116, 119, 101, 101, 110, 39, 32, 47, 10, + 39, 98, 105, 103, 105, 110, 116, 39, 32, 47, 10, + 39, 98, 105, 116, 39, 32, 47, 10, + 39, 98, 111, 111, 108, 101, 97, 110, 39, 32, 47, 10, + 39, 99, 97, 99, 104, 101, 39, 32, 47, 10, + 39, 99, 97, 108, 108, 39, 32, 47, 10, + 39, 99, 97, 108, 108, 101, 100, 39, 32, 47, 10, + 39, 99, 97, 115, 99, 97, 100, 101, 39, 32, 47, 10, + 39, 99, 97, 115, 99, 97, 100, 101, 100, 39, 32, 47, 10, + 39, 99, 97, 116, 97, 108, 111, 103, 39, 32, 47, 10, + 39, 99, 104, 97, 105, 110, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, + 39, 99, 104, 101, 99, 107, 112, 111, 105, 110, 116, 39, 32, 47, 10, + 39, 99, 108, 97, 115, 115, 39, 32, 47, 10, + 39, 99, 108, 111, 115, 101, 39, 32, 47, 10, + 39, 99, 108, 117, 115, 116, 101, 114, 39, 32, 47, 10, + 39, 99, 111, 97, 108, 101, 115, 99, 101, 39, 32, 47, 10, + 39, 99, 111, 108, 117, 109, 110, 115, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 101, 110, 116, 115, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 105, 116, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, + 39, 99, 111, 109, 112, 114, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 102, 108, 105, 99, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 115, 116, 114, 97, 105, 110, 116, 115, 39, 32, 47, 10, + 39, 99, 111, 110, 116, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 116, 105, 110, 117, 101, 39, 32, 47, 10, + 39, 99, 111, 110, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 112, 121, 39, 32, 47, 10, + 39, 99, 111, 115, 116, 39, 32, 47, 10, + 39, 99, 115, 118, 39, 32, 47, 10, + 39, 99, 117, 98, 101, 39, 32, 47, 10, + 39, 99, 117, 114, 114, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 117, 114, 115, 111, 114, 39, 32, 47, 10, + 39, 99, 121, 99, 108, 101, 39, 32, 47, 10, + 39, 100, 97, 116, 97, 39, 32, 47, 10, + 39, 100, 97, 116, 97, 98, 97, 115, 101, 39, 32, 47, 10, + 39, 100, 101, 97, 108, 108, 111, 99, 97, 116, 101, 39, 32, 47, 10, + 39, 100, 101, 99, 39, 32, 47, 10, + 39, 100, 101, 99, 105, 109, 97, 108, 39, 32, 47, 10, + 39, 100, 101, 99, 108, 97, 114, 101, 39, 32, 47, 10, + 39, 100, 101, 102, 97, 117, 108, 116, 115, 39, 32, 47, 10, + 39, 100, 101, 102, 101, 114, 114, 101, 100, 39, 32, 47, 10, + 39, 100, 101, 102, 105, 110, 101, 114, 39, 32, 47, 10, + 39, 100, 101, 108, 101, 116, 101, 39, 32, 47, 10, + 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 39, 32, 47, 10, + 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 115, 39, 32, 47, 10, + 39, 100, 101, 112, 101, 110, 100, 115, 39, 32, 47, 10, + 39, 100, 101, 115, 116, 114, 111, 121, 39, 32, 47, 10, + 39, 100, 101, 116, 97, 99, 104, 39, 32, 47, 10, + 39, 100, 105, 99, 116, 105, 111, 110, 97, 114, 121, 39, 32, 47, 10, + 39, 100, 105, 115, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 100, 105, 115, 99, 97, 114, 100, 39, 32, 47, 10, + 39, 100, 105, 115, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, + 39, 100, 105, 115, 116, 97, 110, 99, 101, 39, 32, 47, 10, + 39, 100, 111, 99, 117, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 100, 111, 109, 97, 105, 110, 39, 32, 47, 10, + 39, 100, 111, 117, 98, 108, 101, 39, 32, 47, 10, + 39, 100, 114, 111, 112, 39, 32, 47, 10, + 39, 101, 97, 99, 104, 39, 32, 47, 10, + 39, 101, 110, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 101, 110, 99, 111, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, + 39, 101, 110, 117, 109, 39, 32, 47, 10, + 39, 101, 114, 114, 111, 114, 39, 32, 47, 10, + 39, 101, 118, 101, 110, 116, 39, 32, 47, 10, + 39, 101, 120, 97, 99, 116, 39, 32, 47, 10, + 39, 101, 120, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 101, 120, 99, 108, 117, 115, 105, 118, 101, 39, 32, 47, 10, + 39, 101, 120, 101, 99, 117, 116, 101, 39, 32, 47, 10, + 39, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, + 39, 101, 120, 112, 108, 97, 105, 110, 39, 32, 47, 10, + 39, 101, 120, 112, 111, 114, 116, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 114, 110, 97, 108, 39, 32, 47, 10, + 39, 101, 120, 116, 114, 97, 99, 116, 39, 32, 47, 10, + 39, 102, 97, 109, 105, 108, 121, 39, 32, 47, 10, + 39, 102, 105, 114, 115, 116, 39, 32, 47, 10, + 39, 102, 108, 111, 97, 116, 39, 32, 47, 10, + 39, 102, 111, 108, 108, 111, 119, 105, 110, 103, 39, 32, 47, 10, + 39, 102, 111, 114, 99, 101, 39, 32, 47, 10, + 39, 102, 111, 114, 119, 97, 114, 100, 39, 32, 47, 10, + 39, 102, 117, 110, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 102, 117, 110, 99, 116, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 103, 101, 110, 101, 114, 97, 116, 101, 100, 39, 32, 47, 10, + 39, 103, 108, 111, 98, 97, 108, 39, 32, 47, 10, + 39, 103, 114, 97, 110, 116, 39, 32, 47, 10, + 39, 103, 114, 97, 110, 116, 101, 100, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 105, 110, 103, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 105, 110, 103, 95, 105, 100, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 115, 39, 32, 47, 10, + 39, 104, 97, 110, 100, 108, 101, 114, 39, 32, 47, 10, + 39, 104, 101, 97, 100, 101, 114, 39, 32, 47, 10, + 39, 104, 111, 108, 100, 39, 32, 47, 10, + 39, 105, 100, 101, 110, 116, 105, 116, 121, 39, 32, 47, 10, + 39, 105, 102, 39, 32, 47, 10, + 39, 105, 109, 109, 101, 100, 105, 97, 116, 101, 39, 32, 47, 10, + 39, 105, 109, 109, 117, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 105, 109, 112, 108, 105, 99, 105, 116, 39, 32, 47, 10, + 39, 105, 109, 112, 111, 114, 116, 39, 32, 47, 10, + 39, 105, 110, 99, 108, 117, 100, 101, 39, 32, 47, 10, + 39, 105, 110, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 105, 110, 99, 114, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 105, 110, 100, 101, 120, 39, 32, 47, 10, + 39, 105, 110, 100, 101, 120, 101, 115, 39, 32, 47, 10, + 39, 105, 110, 104, 101, 114, 105, 116, 39, 32, 47, 10, + 39, 105, 110, 104, 101, 114, 105, 116, 115, 39, 32, 47, 10, + 39, 105, 110, 108, 105, 110, 101, 39, 32, 47, 10, + 39, 105, 110, 111, 117, 116, 39, 32, 47, 10, + 39, 105, 110, 112, 117, 116, 39, 32, 47, 10, + 39, 105, 110, 115, 101, 110, 115, 105, 116, 105, 118, 101, 39, 32, 47, 10, + 39, 105, 110, 115, 101, 114, 116, 39, 32, 47, 10, + 39, 105, 110, 115, 116, 97, 108, 108, 39, 32, 47, 10, + 39, 105, 110, 115, 116, 101, 97, 100, 39, 32, 47, 10, + 39, 105, 110, 116, 39, 32, 47, 10, + 39, 105, 110, 116, 101, 103, 101, 114, 39, 32, 47, 10, + 39, 105, 110, 116, 101, 114, 118, 97, 108, 39, 32, 47, 10, + 39, 105, 110, 118, 111, 107, 101, 114, 39, 32, 47, 10, + 39, 105, 115, 111, 108, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 106, 115, 111, 110, 39, 32, 47, 10, + 39, 107, 101, 121, 39, 32, 47, 10, + 39, 108, 97, 98, 101, 108, 39, 32, 47, 10, + 39, 108, 97, 110, 103, 117, 97, 103, 101, 39, 32, 47, 10, + 39, 108, 97, 114, 103, 101, 39, 32, 47, 10, + 39, 108, 97, 115, 116, 39, 32, 47, 10, + 39, 108, 101, 97, 107, 112, 114, 111, 111, 102, 39, 32, 47, 10, + 39, 108, 101, 118, 101, 108, 39, 32, 47, 10, + 39, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, + 39, 108, 111, 97, 100, 39, 32, 47, 10, + 39, 108, 111, 99, 97, 108, 39, 32, 47, 10, + 39, 108, 111, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 108, 111, 99, 107, 39, 32, 47, 10, + 39, 108, 111, 99, 107, 101, 100, 39, 32, 47, 10, + 39, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 99, 114, 111, 39, 32, 47, 10, + 39, 109, 97, 112, 39, 32, 47, 10, + 39, 109, 97, 112, 112, 105, 110, 103, 39, 32, 47, 10, + 39, 109, 97, 116, 99, 104, 39, 32, 47, 10, + 39, 109, 97, 116, 99, 104, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 116, 101, 114, 105, 97, 108, 105, 122, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 120, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 109, 101, 114, 103, 101, 39, 32, 47, 10, + 39, 109, 101, 116, 104, 111, 100, 39, 32, 47, 10, + 39, 109, 105, 110, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 109, 111, 100, 101, 39, 32, 47, 10, + 39, 109, 111, 118, 101, 39, 32, 47, 10, + 39, 110, 97, 109, 101, 39, 32, 47, 10, + 39, 110, 97, 109, 101, 115, 39, 32, 47, 10, + 39, 110, 97, 116, 105, 111, 110, 97, 108, 39, 32, 47, 10, + 39, 110, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 110, 101, 97, 114, 101, 115, 116, 39, 32, 47, 10, + 39, 110, 101, 119, 39, 32, 47, 10, + 39, 110, 101, 120, 116, 39, 32, 47, 10, + 39, 110, 111, 39, 32, 47, 10, + 39, 110, 111, 110, 101, 39, 32, 47, 10, + 39, 110, 111, 116, 104, 105, 110, 103, 39, 32, 47, 10, + 39, 110, 111, 116, 105, 102, 121, 39, 32, 47, 10, + 39, 110, 111, 119, 97, 105, 116, 39, 32, 47, 10, + 39, 110, 117, 108, 108, 105, 102, 39, 32, 47, 10, + 39, 110, 117, 108, 108, 115, 39, 32, 47, 10, + 39, 110, 117, 109, 101, 114, 105, 99, 39, 32, 47, 10, + 39, 111, 98, 106, 101, 99, 116, 39, 32, 47, 10, + 39, 111, 102, 39, 32, 47, 10, + 39, 111, 102, 102, 39, 32, 47, 10, + 39, 111, 105, 100, 115, 39, 32, 47, 10, + 39, 111, 108, 100, 39, 32, 47, 10, + 39, 111, 112, 101, 114, 97, 116, 111, 114, 39, 32, 47, 10, + 39, 111, 112, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 111, 112, 116, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 111, 114, 100, 105, 110, 97, 108, 105, 116, 121, 39, 32, 47, 10, + 39, 111, 116, 104, 101, 114, 115, 39, 32, 47, 10, + 39, 111, 117, 116, 39, 32, 47, 10, + 39, 111, 118, 101, 114, 108, 97, 121, 39, 32, 47, 10, + 39, 111, 118, 101, 114, 114, 105, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 111, 119, 110, 101, 100, 39, 32, 47, 10, + 39, 111, 119, 110, 101, 114, 39, 32, 47, 10, + 39, 112, 97, 114, 97, 108, 108, 101, 108, 39, 32, 47, 10, + 39, 112, 97, 114, 115, 101, 114, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 97, 108, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 39, 32, 47, 10, + 39, 112, 97, 115, 115, 105, 110, 103, 39, 32, 47, 10, + 39, 112, 97, 115, 115, 119, 111, 114, 100, 39, 32, 47, 10, + 39, 112, 101, 114, 99, 101, 110, 116, 39, 32, 47, 10, + 39, 112, 101, 114, 115, 105, 115, 116, 101, 110, 116, 39, 32, 47, 10, + 39, 112, 108, 97, 110, 115, 39, 32, 47, 10, + 39, 112, 111, 108, 105, 99, 121, 39, 32, 47, 10, + 39, 112, 111, 115, 105, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 112, 114, 97, 103, 109, 97, 39, 32, 47, 10, + 39, 112, 114, 101, 99, 101, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 112, 114, 101, 112, 97, 114, 101, 39, 32, 47, 10, + 39, 112, 114, 101, 112, 97, 114, 101, 100, 39, 32, 47, 10, + 39, 112, 114, 101, 115, 101, 114, 118, 101, 39, 32, 47, 10, + 39, 112, 114, 105, 111, 114, 39, 32, 47, 10, + 39, 112, 114, 105, 118, 105, 108, 101, 103, 101, 115, 39, 32, 47, 10, + 39, 112, 114, 111, 99, 101, 100, 117, 114, 97, 108, 39, 32, 47, 10, + 39, 112, 114, 111, 99, 101, 100, 117, 114, 101, 39, 32, 47, 10, + 39, 112, 114, 111, 103, 114, 97, 109, 39, 32, 47, 10, + 39, 112, 117, 98, 108, 105, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 113, 117, 111, 116, 101, 39, 32, 47, 10, + 39, 114, 97, 110, 103, 101, 39, 32, 47, 10, + 39, 114, 101, 97, 100, 39, 32, 47, 10, + 39, 114, 101, 97, 108, 39, 32, 47, 10, + 39, 114, 101, 97, 115, 115, 105, 103, 110, 39, 32, 47, 10, + 39, 114, 101, 99, 104, 101, 99, 107, 39, 32, 47, 10, + 39, 114, 101, 99, 117, 114, 115, 105, 118, 101, 39, 32, 47, 10, + 39, 114, 101, 102, 39, 32, 47, 10, + 39, 114, 101, 102, 101, 114, 101, 110, 99, 105, 110, 103, 39, 32, 47, 10, + 39, 114, 101, 102, 114, 101, 115, 104, 39, 32, 47, 10, + 39, 114, 101, 103, 105, 115, 116, 101, 114, 39, 32, 47, 10, + 39, 114, 101, 105, 110, 100, 101, 120, 39, 32, 47, 10, + 39, 114, 101, 108, 97, 116, 105, 118, 101, 39, 32, 47, 10, + 39, 114, 101, 108, 101, 97, 115, 101, 39, 32, 47, 10, + 39, 114, 101, 112, 101, 97, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 114, 101, 112, 108, 105, 99, 97, 39, 32, 47, 10, + 39, 114, 101, 115, 101, 116, 39, 32, 47, 10, + 39, 114, 101, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, + 39, 114, 101, 115, 111, 117, 114, 99, 101, 115, 39, 32, 47, 10, + 39, 114, 101, 115, 116, 97, 114, 116, 39, 32, 47, 10, + 39, 114, 101, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, + 39, 114, 101, 116, 117, 114, 110, 115, 39, 32, 47, 10, + 39, 114, 101, 118, 111, 107, 101, 39, 32, 47, 10, + 39, 114, 111, 108, 101, 39, 32, 47, 10, + 39, 114, 111, 108, 108, 98, 97, 99, 107, 39, 32, 47, 10, + 39, 114, 111, 108, 108, 117, 112, 39, 32, 47, 10, + 39, 114, 111, 119, 39, 32, 47, 10, + 39, 114, 111, 119, 115, 39, 32, 47, 10, + 39, 114, 117, 108, 101, 39, 32, 47, 10, + 39, 115, 97, 109, 112, 108, 101, 39, 32, 47, 10, + 39, 115, 97, 118, 101, 112, 111, 105, 110, 116, 39, 32, 47, 10, + 39, 115, 99, 104, 101, 109, 97, 39, 32, 47, 10, + 39, 115, 99, 104, 101, 109, 97, 115, 39, 32, 47, 10, + 39, 115, 99, 111, 112, 101, 39, 32, 47, 10, + 39, 115, 99, 114, 111, 108, 108, 39, 32, 47, 10, + 39, 115, 101, 97, 114, 99, 104, 39, 32, 47, 10, + 39, 115, 101, 99, 114, 101, 116, 39, 32, 47, 10, + 39, 115, 101, 99, 117, 114, 101, 39, 32, 47, 10, + 39, 115, 101, 99, 117, 114, 105, 116, 121, 39, 32, 47, 10, + 39, 115, 101, 113, 117, 101, 110, 99, 101, 39, 32, 47, 10, + 39, 115, 101, 113, 117, 101, 110, 99, 101, 115, 39, 32, 47, 10, + 39, 115, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 115, 101, 114, 118, 101, 114, 39, 32, 47, 10, + 39, 115, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 115, 101, 116, 39, 32, 47, 10, + 39, 115, 101, 116, 111, 102, 39, 32, 47, 10, + 39, 115, 101, 116, 115, 39, 32, 47, 10, + 39, 115, 104, 97, 114, 101, 39, 32, 47, 10, + 39, 115, 105, 109, 105, 108, 97, 114, 105, 116, 121, 39, 32, 47, 10, + 39, 115, 105, 109, 112, 108, 101, 39, 32, 47, 10, + 39, 115, 107, 105, 112, 39, 32, 47, 10, + 39, 115, 109, 97, 108, 108, 105, 110, 116, 39, 32, 47, 10, + 39, 115, 110, 97, 112, 115, 104, 111, 116, 39, 32, 47, 10, + 39, 115, 111, 114, 116, 101, 100, 39, 32, 47, 10, + 39, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, + 39, 115, 113, 108, 39, 32, 47, 10, + 39, 115, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 115, 116, 97, 110, 100, 97, 108, 111, 110, 101, 39, 32, 47, 10, + 39, 115, 116, 97, 114, 116, 39, 32, 47, 10, + 39, 115, 116, 97, 116, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 115, 116, 97, 116, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, + 39, 115, 116, 100, 105, 110, 39, 32, 47, 10, + 39, 115, 116, 100, 111, 117, 116, 39, 32, 47, 10, + 39, 115, 116, 111, 114, 97, 103, 101, 39, 32, 47, 10, + 39, 115, 116, 111, 114, 101, 100, 39, 32, 47, 10, + 39, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, + 39, 115, 116, 114, 105, 112, 39, 32, 47, 10, + 39, 115, 116, 114, 117, 99, 116, 39, 32, 47, 10, + 39, 115, 117, 98, 115, 99, 114, 105, 112, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 115, 117, 98, 115, 116, 114, 105, 110, 103, 39, 32, 47, 10, + 39, 115, 121, 115, 105, 100, 39, 32, 47, 10, + 39, 115, 121, 115, 116, 101, 109, 39, 32, 47, 10, + 39, 116, 97, 98, 108, 101, 115, 39, 32, 47, 10, + 39, 116, 97, 98, 108, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, + 39, 116, 97, 114, 103, 101, 116, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 108, 97, 116, 101, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 111, 114, 97, 114, 121, 39, 32, 47, 10, + 39, 116, 101, 120, 116, 39, 32, 47, 10, + 39, 116, 105, 101, 115, 39, 32, 47, 10, + 39, 116, 105, 109, 101, 39, 32, 47, 10, + 39, 116, 105, 109, 101, 115, 116, 97, 109, 112, 39, 32, 47, 10, + 39, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 116, 114, 97, 110, 115, 102, 111, 114, 109, 39, 32, 47, 10, + 39, 116, 114, 101, 97, 116, 39, 32, 47, 10, + 39, 116, 114, 105, 103, 103, 101, 114, 39, 32, 47, 10, + 39, 116, 114, 105, 109, 39, 32, 47, 10, + 39, 116, 114, 117, 110, 99, 97, 116, 101, 39, 32, 47, 10, + 39, 116, 114, 117, 115, 116, 101, 100, 39, 32, 47, 10, + 39, 116, 114, 121, 95, 99, 97, 115, 116, 39, 32, 47, 10, + 39, 116, 117, 112, 108, 101, 39, 32, 47, 10, + 39, 116, 121, 112, 101, 39, 32, 47, 10, + 39, 116, 121, 112, 101, 115, 39, 32, 47, 10, + 39, 117, 110, 98, 111, 117, 110, 100, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 107, 110, 111, 119, 110, 39, 32, 47, 10, + 39, 117, 110, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, + 39, 117, 110, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 116, 105, 108, 39, 32, 47, 10, + 39, 117, 112, 100, 97, 116, 101, 39, 32, 47, 10, + 39, 117, 115, 101, 39, 32, 47, 10, + 39, 117, 115, 101, 114, 39, 32, 47, 10, + 39, 118, 97, 99, 117, 117, 109, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 97, 116, 101, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 97, 116, 111, 114, 39, 32, 47, 10, + 39, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 118, 97, 108, 117, 101, 115, 39, 32, 47, 10, + 39, 118, 97, 114, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 118, 97, 114, 105, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 118, 105, 101, 119, 39, 32, 47, 10, + 39, 118, 105, 101, 119, 115, 39, 32, 47, 10, + 39, 118, 105, 114, 116, 117, 97, 108, 39, 32, 47, 10, + 39, 118, 111, 108, 97, 116, 105, 108, 101, 39, 32, 47, 10, + 39, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, + 39, 119, 111, 114, 107, 39, 32, 47, 10, + 39, 119, 114, 97, 112, 112, 101, 114, 39, 32, 47, 10, + 39, 119, 114, 105, 116, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 39, 32, 47, 10, + 39, 120, 109, 108, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 99, 111, 110, 99, 97, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 101, 108, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 102, 111, 114, 101, 115, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 110, 97, 109, 101, 115, 112, 97, 99, 101, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 112, 97, 114, 115, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 112, 105, 39, 32, 47, 10, + 39, 120, 109, 108, 114, 111, 111, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 115, 101, 114, 105, 97, 108, 105, 122, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 121, 101, 115, 39, 32, 47, 10, + 39, 122, 111, 110, 101, 39, 10, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 66, 69, 84, 87, 69, 69, 78, 39, 32, 47, 10, 39, 66, 73, 71, 73, 78, 84, 39, 32, 47, 10, 39, 66, 73, 84, 39, 32, 47, 10, @@ -718,7 +1077,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 83, 116, 109, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 76, 105, 115, 116, 40, 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 41, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 83, 116, 109, 116, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, - 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 104, 97, 110, 103, 101, 78, 117, 108, 108, 97, 98, 105, 108, 105, 116, 121, 32, 47, 10, + 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 82, 101, 110, 97, 109, 101, 67, 111, 108, 117, 109, 110, 32, 47, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 65, 68, 68, 39, 32, 84, 111, 112, 76, 101, 118, 101, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 68, 82, 79, 80, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, @@ -791,7 +1150,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 83, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, 67, 111, 112, 121, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 67, 79, 80, 89, 39, 32, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 10, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 60, 45, 32, 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 47, 32, 67, 111, 112, 121, 83, 101, 108, 101, 99, 116, 32, 47, 32, 67, 111, 112, 121, 70, 114, 111, 109, 68, 97, 116, 97, 98, 97, 115, 101, 10, - 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, + 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, 70, 114, 111, 109, 79, 114, 84, 111, 32, 60, 45, 32, 67, 111, 112, 121, 70, 114, 111, 109, 32, 47, 32, 67, 111, 112, 121, 84, 111, 10, 67, 111, 112, 121, 70, 114, 111, 109, 32, 60, 45, 32, 39, 70, 82, 79, 77, 39, 10, 67, 111, 112, 121, 84, 111, 32, 60, 45, 32, 39, 84, 79, 39, 10, @@ -803,6 +1162,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 10, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 43, 10, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 32, 60, 45, 32, 39, 46, 39, 32, 67, 111, 108, 73, 100, 10, + 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 63, 32, 39, 68, 69, 76, 73, 77, 73, 84, 69, 82, 83, 39, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 67, 111, 112, 121, 71, 101, 110, 101, 114, 105, 99, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 47, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 84, 97, 105, 108, 42, 10, @@ -984,6 +1344,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 82, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 85, 110, 114, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 70, 117, 110, 99, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 84, 121, 112, 101, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 10, + 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 63, 32, 39, 65, 83, 39, 32, 80, 97, 114, 101, 110, 115, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 84, 121, 112, 101, 63, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 65, 108, 119, 97, 121, 115, 32, 47, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 66, 121, 68, 101, 102, 97, 117, 108, 116, 10, @@ -1020,14 +1381,18 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 32, 60, 45, 32, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 47, 32, 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 47, 32, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 10, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 10, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 41, 10, - 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 10, + 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, 67, 114, 101, 97, 116, 101, 86, 105, 101, 119, 83, 116, 109, 116, 32, 60, 45, 32, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 63, 32, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 63, 32, 39, 86, 73, 69, 87, 39, 32, 73, 102, 78, 111, 116, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 87, 105, 116, 104, 76, 105, 115, 116, 63, 32, 39, 65, 83, 39, 32, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 10, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 32, 60, 45, 32, 39, 82, 69, 67, 85, 82, 83, 73, 86, 69, 39, 10, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 32, 60, 45, 32, 39, 83, 69, 67, 85, 82, 69, 39, 10, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 68, 69, 65, 76, 76, 79, 67, 65, 84, 69, 39, 32, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 63, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 10, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 32, 60, 45, 32, 39, 80, 82, 69, 80, 65, 82, 69, 39, 10, 68, 101, 108, 101, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 68, 69, 76, 69, 84, 69, 39, 32, 39, 70, 82, 79, 77, 39, 32, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 63, 10, + 35, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 102, 105, 114, 115, 116, 44, 32, 115, 111, 32, 67, 65, 83, 67, 65, 68, 69, 47, 82, 69, 83, 84, 82, 73, 67, 84, 32, 97, 114, 101, 32, 110, 111, 116, 32, 99, 111, 110, 115, 117, 109, 101, 100, 32, 97, 115, 32, 97, 110, 32, 97, 108, 105, 97, 115, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 47, 32, 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 60, 45, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 10, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 63, 10, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 10, @@ -1080,7 +1445,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 9, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 10, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 32, 60, 45, 32, 39, 84, 82, 73, 71, 71, 69, 82, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 84, 114, 105, 103, 103, 101, 114, 78, 97, 109, 101, 32, 39, 79, 78, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 68, 114, 111, 112, 84, 97, 98, 108, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 79, 114, 86, 105, 101, 119, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 41, 10, - 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 41, 10, + 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 41, 10, 68, 114, 111, 112, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 70, 117, 110, 99, 116, 105, 111, 110, 84, 121, 112, 101, 77, 97, 99, 114, 111, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 41, 10, 68, 114, 111, 112, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 41, 10, 68, 114, 111, 112, 73, 110, 100, 101, 120, 32, 60, 45, 32, 39, 73, 78, 68, 69, 88, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 73, 110, 100, 101, 120, 78, 97, 109, 101, 41, 10, @@ -1299,11 +1664,12 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 35, 32, 76, 69, 86, 69, 76, 32, 52, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 63, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, + 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 10, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 10, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 84, 97, 105, 108, 10, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 10, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, + 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 10, 73, 115, 84, 101, 115, 116, 32, 60, 45, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 111, 116, 78, 117, 108, 108, 32, 47, 32, 73, 115, 78, 117, 108, 108, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 32, 60, 45, 32, 84, 114, 117, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 70, 97, 108, 115, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 117, 108, 108, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 85, 110, 107, 110, 111, 119, 110, 76, 105, 116, 101, 114, 97, 108, 10, @@ -1319,7 +1685,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 79, 112, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 39, 68, 73, 83, 84, 73, 78, 67, 84, 39, 32, 39, 70, 82, 79, 77, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 54, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 10, + 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 47, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 35, 32, 78, 79, 84, 32, 97, 102, 116, 101, 114, 32, 97, 32, 99, 111, 109, 112, 97, 114, 105, 115, 111, 110, 32, 111, 112, 101, 114, 97, 116, 111, 114, 32, 98, 105, 110, 100, 115, 32, 101, 118, 101, 114, 121, 116, 104, 105, 110, 103, 32, 117, 112, 32, 116, 111, 32, 116, 104, 101, 32, 73, 83, 32, 108, 101, 118, 101, 108, 58, 32, 97, 32, 60, 32, 78, 79, 84, 32, 98, 32, 73, 83, 32, 78, 85, 76, 76, 32, 105, 115, 32, 97, 32, 60, 32, 40, 78, 79, 84, 32, 40, 98, 32, 73, 83, 32, 78, 85, 76, 76, 41, 41, 10, + 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 69, 113, 117, 97, 108, 32, 47, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 78, 111, 116, 69, 113, 117, 97, 108, 32, 47, 10, @@ -1335,7 +1704,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 112, 101, 114, 97, 116, 111, 114, 71, 114, 101, 97, 116, 101, 114, 84, 104, 97, 110, 69, 113, 117, 97, 108, 115, 32, 60, 45, 32, 39, 62, 61, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 55, 10, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 42, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 63, 10, - 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 10, + 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 42, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 10, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 76, 105, 107, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 69, 115, 99, 97, 112, 101, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1421,10 +1790,11 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 77, 101, 116, 104, 111, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 68, 105, 115, 116, 105, 110, 99, 116, 79, 114, 65, 108, 108, 63, 32, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 63, 32, 79, 114, 100, 101, 114, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 73, 103, 110, 111, 114, 101, 79, 114, 82, 101, 115, 112, 101, 99, 116, 78, 117, 108, 108, 115, 63, 10, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 10, 83, 108, 105, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 91, 39, 32, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 39, 93, 39, 10, - 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 63, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 32, 60, 45, 32, 39, 45, 39, 10, + 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 63, 10, + 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 32, 60, 45, 32, 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 47, 32, 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, + 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 39, 45, 39, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, + 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, + 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 80, 111, 115, 116, 102, 105, 120, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 32, 39, 33, 39, 10, 83, 112, 101, 99, 105, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 97, 108, 101, 115, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 85, 110, 112, 97, 99, 107, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 67, 111, 108, 117, 109, 110, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 116, 114, 97, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 76, 97, 109, 98, 100, 97, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 78, 117, 108, 108, 73, 102, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 82, 111, 119, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 83, 117, 98, 115, 116, 114, 105, 110, 103, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 105, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 79, 118, 101, 114, 108, 97, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, @@ -1491,7 +1861,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 83, 111, 117, 114, 99, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 47, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 78, 69, 87, 39, 32, 39, 84, 69, 77, 80, 79, 82, 65, 82, 89, 39, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 105, 111, 110, 79, 112, 116, 105, 111, 110, 115, 63, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 67, 111, 108, 73, 100, 10, - 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 79, 114, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 47, 32, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 10, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 82, 69, 80, 76, 65, 67, 69, 39, 10, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 73, 71, 78, 79, 82, 69, 39, 10, @@ -1500,6 +1870,8 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 79, 114, 100, 101, 114, 32, 60, 45, 32, 39, 66, 89, 39, 32, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 10, 73, 110, 115, 101, 114, 116, 66, 121, 78, 97, 109, 101, 32, 60, 45, 32, 39, 78, 65, 77, 69, 39, 10, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 32, 60, 45, 32, 39, 80, 79, 83, 73, 84, 73, 79, 78, 39, 10, + 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 79, 86, 69, 82, 82, 73, 68, 73, 78, 71, 39, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 39, 86, 65, 76, 85, 69, 39, 10, + 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 60, 45, 32, 39, 85, 83, 69, 82, 39, 32, 47, 32, 39, 83, 89, 83, 84, 69, 77, 39, 10, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 63, 10, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 41, 10, @@ -1512,7 +1884,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 73, 100, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 73, 110, 100, 101, 120, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 39, 79, 78, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 47, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 10, - 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 82, 69, 84, 85, 82, 78, 73, 78, 71, 39, 32, 84, 97, 114, 103, 101, 116, 76, 105, 115, 116, 10, 76, 111, 97, 100, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 76, 79, 65, 68, 39, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 70, 114, 111, 109, 83, 111, 117, 114, 99, 101, 63, 32, 69, 120, 116, 101, 110, 115, 105, 111, 110, 65, 108, 105, 97, 115, 63, 10, @@ -1547,7 +1919,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 111, 78, 111, 116, 104, 105, 110, 103, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 69, 114, 114, 111, 114, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 69, 82, 82, 79, 82, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 10, - 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, + 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 65, 78, 68, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 111, 116, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 72, 69, 78, 39, 32, 39, 78, 79, 84, 39, 32, 39, 77, 65, 84, 67, 72, 69, 68, 39, 32, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 63, 32, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 39, 84, 72, 69, 78, 39, 32, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 65, 99, 116, 105, 111, 110, 10, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 121, 83, 111, 117, 114, 99, 101, 32, 47, 32, 66, 121, 84, 97, 114, 103, 101, 116, 10, @@ -1606,7 +1978,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 84, 97, 98, 108, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 79, 112, 116, 105, 111, 110, 97, 108, 80, 97, 114, 101, 110, 115, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 47, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 10, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 41, 10, - 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 47, 32, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 10, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1627,10 +1999,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 105, 115, 116, 105, 110, 99, 116, 79, 110, 84, 97, 114, 103, 101, 116, 115, 32, 60, 45, 32, 39, 79, 78, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 47, 32, 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 47, 32, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 10, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 42, 10, - 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 65, 116, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 39, 58, 39, 10, - 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 32, 60, 45, 32, 74, 111, 105, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 85, 110, 112, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 10, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 80, 73, 86, 79, 84, 39, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 66, 111, 100, 121, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, @@ -1653,13 +2025,13 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 97, 98, 108, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 10, - 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 32, 39, 79, 82, 68, 73, 78, 65, 76, 73, 84, 89, 39, 10, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 63, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 42, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 63, 41, 10, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 10, - 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 10, + 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 84, 121, 112, 101, 63, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 60, 45, 32, 39, 58, 61, 39, 32, 47, 32, 39, 61, 62, 39, 10, @@ -1714,7 +2086,8 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 71, 82, 79, 85, 80, 39, 32, 39, 66, 89, 39, 32, 71, 114, 111, 117, 112, 66, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 10, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 72, 65, 86, 73, 78, 71, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 81, 85, 65, 76, 73, 70, 89, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 40, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 47, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 41, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, + 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, + 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 39, 83, 65, 77, 80, 76, 69, 39, 10, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 83, 65, 77, 80, 76, 69, 39, 10, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 73, 78, 68, 79, 87, 39, 32, 76, 105, 115, 116, 40, 87, 105, 110, 100, 111, 119, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 41, 10, @@ -1770,7 +2143,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 105, 97, 115, 101, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 39, 65, 83, 39, 32, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 10, - 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 63, 10, + 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 63, 10, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 86, 65, 76, 85, 69, 83, 39, 32, 76, 105, 115, 116, 40, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 41, 10, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 83, 101, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 83, 101, 116, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 79, 114, 84, 105, 109, 101, 90, 111, 110, 101, 10, @@ -1809,20 +2182,20 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 82, 101, 97, 100, 87, 114, 105, 116, 101, 32, 60, 45, 32, 39, 87, 82, 73, 84, 69, 39, 10, 65, 98, 111, 114, 116, 79, 114, 82, 111, 108, 108, 98, 97, 99, 107, 32, 60, 45, 32, 39, 65, 66, 79, 82, 84, 39, 32, 47, 32, 39, 82, 79, 76, 76, 66, 65, 67, 75, 39, 10, 67, 111, 109, 109, 105, 116, 79, 114, 69, 110, 100, 32, 60, 45, 32, 39, 67, 79, 77, 77, 73, 84, 39, 32, 47, 32, 39, 69, 78, 68, 39, 10, - 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 39, 83, 69, 84, 39, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 63, 32, 39, 83, 69, 84, 39, 10, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 41, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 41, 41, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 41, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 85, 115, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 85, 83, 69, 39, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 47, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 47, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, - 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 10, - 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, + 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, + 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 39, 46, 39, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 39, 46, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 86, 97, 99, 117, 117, 109, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 86, 65, 67, 85, 85, 77, 39, 32, 86, 97, 99, 117, 117, 109, 79, 112, 116, 105, 111, 110, 115, 63, 32, 65, 110, 97, 108, 121, 122, 101, 84, 97, 114, 103, 101, 116, 63, 10, diff --git a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp index 1c3d2d9bd..2cd19d5a1 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp @@ -24,6 +24,8 @@ class DefaultKeywordMaps { case_insensitive_set_t colname_keyword_map; case_insensitive_set_t typefunc_keyword_map; case_insensitive_set_t typename_keyword_map; + //! Keywords that can be used as a column alias without AS (orthogonal to the other categories) + case_insensitive_set_t bare_label_keyword_map; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp index 758766438..b4150dd88 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp @@ -46,7 +46,8 @@ enum class SuggestionState : uint8_t { SUGGEST_TABLE_FUNCTION_NAME, SUGGEST_PRAGMA_NAME, SUGGEST_SETTING_NAME, - SUGGEST_RESERVED_VARIABLE + SUGGEST_RESERVED_VARIABLE, + SUGGEST_BARE_LABEL }; enum class CandidateType { KEYWORD, IDENTIFIER, LITERAL }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp index f37612674..a8163c699 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp @@ -54,6 +54,10 @@ class IdentifierMatcher : public AtomicMatcher { if (Tokenizer::CharacterIsInitialNumber(text[0])) { return false; } + if (text.back() == '\'') { + // a prefixed string literal such as x'41' or e'ab' + return false; + } return Tokenizer::CharacterIsKeyword(text[0]); } diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp index b7358cb14..64936d1c2 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp @@ -61,7 +61,15 @@ class NumberLiteralMatcher : public AtomicMatcher { return false; } bool scientific_notation = false; + bool has_dot = token_text[0] == '.'; for (idx_t i = 1; i < token_text.size(); i++) { + if (token_text[i] == '.') { + // a number has at most one dot, and none in its exponent + if (has_dot || scientific_notation) { + return false; + } + has_dot = true; + } if (Tokenizer::CharacterIsScientific(token_text[i])) { if (scientific_notation) { throw ParserException("Already found scientific notation"); diff --git a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp index 66714802d..7fa82e018 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp @@ -165,11 +165,8 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.arena_ptr>", case_insensitive_map_t>); -DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.child_list_t", child_list_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.int64_t", int64_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.optional_idx", optional_idx); -DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair", - pair); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", pair>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", @@ -238,6 +235,8 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); +DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>>", + vector>>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>", vector>); @@ -1139,6 +1138,9 @@ class PEGTransformerFactory { static void InitializeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeNestedTypeTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1265,6 +1267,9 @@ class PEGTransformerFactory { static void InitializeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1969,6 +1974,17 @@ class PEGTransformerFactory { static void InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2631,6 +2647,9 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeIsCastTailTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2676,6 +2695,13 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeComparisonOperandTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2940,15 +2966,19 @@ class PEGTransformerFactory { static void InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeEndSliceValueTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -3799,6 +3829,9 @@ class PEGTransformerFactory { static void InitializeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4081,16 +4114,16 @@ class PEGTransformerFactory { static void InitializeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static void InitializeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4353,19 +4386,26 @@ class PEGTransformerFactory { const Identifier &reserved_type_name); static vector> TransformTypeModifiers(PEGTransformer &transformer, optional>> expression); - static unique_ptr TransformRowType(PEGTransformer &transformer, - const optional> &col_id_type_list); - static unique_ptr TransformSetofType(PEGTransformer &transformer, const LogicalType &type); - static unique_ptr TransformUnionType(PEGTransformer &transformer, - const child_list_t &col_id_type_list); - static child_list_t TransformColIdTypeList(PEGTransformer &transformer, - const vector> &col_id_type); + static unique_ptr TransformNestedType(PEGTransformer &transformer, + unique_ptr type_variations, + const optional> &array_bounds); + static unique_ptr + TransformRowType(PEGTransformer &transformer, + optional>>> col_id_type_list); + static unique_ptr TransformSetofType(PEGTransformer &transformer, + unique_ptr nested_type); + static unique_ptr + TransformUnionType(PEGTransformer &transformer, + vector>> col_id_type_list); + static vector>> + TransformColIdTypeList(PEGTransformer &transformer, + vector>> col_id_type); static unique_ptr TransformMapType(PEGTransformer &transformer, - const optional> &type); + optional>> nested_type); static unique_ptr TransformTupleType(PEGTransformer &transformer, - const vector &type); - static pair TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, - const LogicalType &type); + vector> nested_type); + static pair> + TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, unique_ptr nested_type); static int64_t TransformArrayKeyword(PEGTransformer &transformer); static int64_t TransformArrayKeywordWithBounds(PEGTransformer &transformer, const int64_t &square_brackets_array); static int64_t TransformSquareBracketsArray(PEGTransformer &transformer, @@ -4390,12 +4430,11 @@ class PEGTransformerFactory { const Identifier &catalog_name); static unique_ptr TransformCopyStatement(PEGTransformer &transformer, unique_ptr copy_variations); - static unique_ptr TransformCopyTable(PEGTransformer &transformer, - unique_ptr base_table_name, - const optional> &insert_column_list, - const bool &from_or_to, - unique_ptr copy_file_name, - const optional> ©_options); + static unique_ptr + TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, + const optional> &insert_column_list, const bool &from_or_to, + unique_ptr copy_file_name, const optional &legacy_option, + const optional> ©_options); static bool TransformCopyFrom(PEGTransformer &transformer); static bool TransformCopyTo(PEGTransformer &transformer); static unique_ptr TransformCopySelect(PEGTransformer &transformer, @@ -4410,6 +4449,8 @@ class PEGTransformerFactory { const Identifier &identifier_col_id); static Identifier TransformIdentifierColId(PEGTransformer &transformer, const Identifier &identifier, const vector ©_file_name_suffix); + static GenericCopyOption TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, + const string &delimiter); static vector TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list); static vector @@ -4716,7 +4757,8 @@ class PEGTransformerFactory { static unique_ptr TransformEnumSelectType(PEGTransformer &transformer, unique_ptr select_statement_internal); static unique_ptr TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal); + const optional> &string_literal, + const optional> &array_bounds); static unique_ptr TransformCreateViewStmt(PEGTransformer &transformer, const optional &create_secure, const optional &create_recursive, const optional &if_not_exists, @@ -4736,7 +4778,11 @@ class PEGTransformerFactory { optional> where_clause, optional>> returning_clause); static unique_ptr TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name); + unique_ptr base_table_name, + optional truncate_options); + static bool TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, + optional truncate_drop_behavior); + static bool TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior); static unique_ptr TransformTargetOptAlias(PEGTransformer &transformer, unique_ptr base_table_name, const optional &target_alias); @@ -4786,7 +4832,7 @@ class PEGTransformerFactory { static unique_ptr TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name); + const vector &table_function_name); static unique_ptr TransformDropFunction(PEGTransformer &transformer, const bool &function_type_macro, const optional &if_exists, const vector &function_identifier); @@ -5086,7 +5132,7 @@ class PEGTransformerFactory { TransformColDefAndExpressionTail(PEGTransformer &transformer, unique_ptr is_distinct_from_expression); static unique_ptr TransformLogicalNotExpression(PEGTransformer &transformer, - optional> not_expression, + const optional> ¬_expression, unique_ptr is_expression); static vector TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword); static bool TransformNotKeyword(PEGTransformer &transformer); @@ -5103,6 +5149,8 @@ class PEGTransformerFactory { ComparisonExpressionTail comparison_expression_tail); static IsExpressionTail TransformIsOtherOperatorTail(PEGTransformer &transformer, OtherOperatorTail other_operator_tail); + static IsExpressionTail TransformIsCastTail(PEGTransformer &transformer, + unique_ptr cast_operator); static unique_ptr TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value); static Value TransformUnknownLiteral(PEGTransformer &transformer); @@ -5121,10 +5169,12 @@ class PEGTransformerFactory { static unique_ptr TransformComparisonExpression(PEGTransformer &transformer, unique_ptr between_in_like_expression, optional> comparison_expression_tail); - static ComparisonExpressionTail - TransformComparisonExpressionTail(PEGTransformer &transformer, const ExpressionType &comparison_operator, - optional> not_expression, - unique_ptr between_in_like_expression); + static ComparisonExpressionTail TransformComparisonExpressionTail(PEGTransformer &transformer, + const ExpressionType &comparison_operator, + unique_ptr comparison_operand); + static unique_ptr TransformNotComparisonOperand(PEGTransformer &transformer, + const vector ¬_expression, + unique_ptr is_expression); static ExpressionType TransformOperatorEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorNotEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorLessThan(PEGTransformer &transformer); @@ -5135,7 +5185,8 @@ class PEGTransformerFactory { PEGTransformer &transformer, unique_ptr other_operator_expression, optional> in_predicate, optional between_like_op); static BetweenInLikeOperator TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause); + unique_ptr in_clause, + optional>> cast_operator); static BetweenInLikeOperator TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression); static unique_ptr TransformLikeClause(PEGTransformer &transformer, const string &like_variations, @@ -5246,11 +5297,14 @@ class PEGTransformerFactory { vector> slice_bound); static vector> TransformSliceBound(PEGTransformer &transformer, optional> expression, - optional> end_slice_bound, - optional> step_slice_bound); + optional>> slice_upper_bound); + static vector> + TransformMinusStepSliceBound(PEGTransformer &transformer, unique_ptr step_slice_bound); + static vector> + TransformEndStepSliceBound(PEGTransformer &transformer, unique_ptr end_slice_bound, + optional> step_slice_bound); static unique_ptr TransformEndSliceBound(PEGTransformer &transformer, - optional> end_slice_value); - static unique_ptr TransformEndSliceMinus(PEGTransformer &transformer); + optional> expression); static unique_ptr TransformStepSliceBound(PEGTransformer &transformer, optional> expression); static unique_ptr TransformPostfixOperator(PEGTransformer &transformer); @@ -5351,8 +5405,8 @@ class PEGTransformerFactory { TransformInsertStatement(PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, - const optional> &insert_column_list, InsertValues insert_values, - optional> on_conflict_clause, + const optional> &insert_column_list, bool has_overriding_clause, + InsertValues insert_values, optional> on_conflict_clause, optional>> returning_clause); static OnConflictAction TransformInsertOrReplace(PEGTransformer &transformer); static OnConflictAction TransformInsertOrIgnore(PEGTransformer &transformer); @@ -5557,11 +5611,10 @@ class PEGTransformerFactory { optional>> distinct_on_targets); static vector> TransformDistinctOnTargets(PEGTransformer &transformer, vector> expression); - static unique_ptr TransformTableSubquery(PEGTransformer &transformer, - const optional &table_alias_colon, - const optional &lateral, - unique_ptr subquery_reference, - const optional &table_alias); + static unique_ptr + TransformTableSubquery(PEGTransformer &transformer, const optional &table_alias_colon, + const optional &lateral, unique_ptr subquery_reference, + const optional &table_alias, optional> sample_clause); static unique_ptr TransformBaseTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr base_table_name, const optional &table_alias, @@ -5570,7 +5623,8 @@ class PEGTransformerFactory { static unique_ptr TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias); + const optional &table_alias, + optional> sample_clause); static unique_ptr TransformParensTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr table_ref, @@ -5615,12 +5669,10 @@ class PEGTransformerFactory { TransformCatalogReservedSchemaTable(PEGTransformer &transformer, const Identifier &catalog_qualification, const vector &reserved_schema_qualification, const Identifier &reserved_table_name); - static unique_ptr TransformTableFunctionLateralOpt(PEGTransformer &transformer, - const optional &lateral, - const QualifiedName &qualified_table_function, - vector table_function_arguments, - const optional &with_ordinality, - const optional &table_alias); + static unique_ptr TransformTableFunctionLateralOpt( + PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, + vector table_function_arguments, const optional &with_ordinality, + const optional &table_alias, optional> sample_clause); static unique_ptr TransformTableFunctionAliasColon(PEGTransformer &transformer, const Identifier &table_alias_colon, const QualifiedName &qualified_table_function, @@ -5634,7 +5686,9 @@ class PEGTransformerFactory { const Identifier &table_function_name); static vector TransformTableFunctionArguments(PEGTransformer &transformer, optional> function_argument); - static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, MacroParameter named_parameter); + static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, + const Identifier &type_func_name, + unique_ptr expression); static FunctionArgument TransformPositionalFunctionArgument(PEGTransformer &transformer, unique_ptr expression); static MacroParameter TransformNamedParameter(PEGTransformer &transformer, const Identifier &type_func_name, @@ -5712,6 +5766,8 @@ class PEGTransformerFactory { unique_ptr expression); static unique_ptr TransformSampleClause(PEGTransformer &transformer, unique_ptr sample_entry); + static unique_ptr TransformSelectSampleClause(PEGTransformer &transformer, + unique_ptr sample_entry); static vector> TransformWindowClause(PEGTransformer &transformer, vector> window_definition); static unique_ptr @@ -5837,20 +5893,19 @@ class PEGTransformerFactory { const optional &update_alias); static Identifier TransformUpdateAlias(PEGTransformer &transformer, const bool &has_result, const Identifier &col_id); + static unique_ptr TransformUpdateSetClauseList(PEGTransformer &transformer, + vector> update_set_info); static unique_ptr TransformUpdateSetTuple(PEGTransformer &transformer, const vector &column_name, unique_ptr expression); - static unique_ptr - TransformUpdateSetElementList(PEGTransformer &transformer, - vector>> update_set_element); - static pair> - TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, - unique_ptr expression); + static unique_ptr TransformUpdateSetElement(PEGTransformer &transformer, + const string &update_set_column_target, + unique_ptr expression); static string TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, const optional> &dot_identifier); static unique_ptr TransformUseStatement(PEGTransformer &transformer, const QualifiedName &use_target); - static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const Identifier &schema_name); - static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const Identifier &catalog_name); + static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const string &schema_name); + static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const string &catalog_name); static QualifiedName TransformUseTargetCatalogSchema(PEGTransformer &transformer, const Identifier &catalog_name, const Identifier &reserved_schema_name, const optional> &dot_identifier); diff --git a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp index 28ab414a8..2745d3e32 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp @@ -112,6 +112,8 @@ class ExpressionBinder { optional_ptr macro_binding; optional_ptr> lambda_bindings; + //! The number of (outer) lambda bindings whose parameters can not be referenced, e.g. inside an aggregate + idx_t inaccessible_lambda_binding_count = 0; public: unique_ptr Bind(unique_ptr &expr, optional_ptr result_type = nullptr, @@ -233,6 +235,9 @@ class ExpressionBinder { //! Returns true if the function name is an alias for the UNNEST function static bool IsUnnestFunction(const Identifier &function_name); + //! Continues the expression depth of the binder this one binds a nested expression for + void ContinueStackDepth(const ExpressionBinder &parent); + private: //! Current stack depth idx_t stack_depth = DConstants::INVALID_INDEX; diff --git a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp index bdd01b0fd..ef1494222 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp @@ -20,7 +20,7 @@ class BloomFilter; //! DEPRECATED - only preserved for backwards-compatible expression conversion class LegacyBFTableFilter final : public TableFilter { private: - optional_ptr filter; + shared_ptr filter; bool filters_null_values; string key_column_name; @@ -30,9 +30,9 @@ class LegacyBFTableFilter final : public TableFilter { static constexpr auto TYPE = TableFilterType::LEGACY_BLOOM_FILTER; public: - explicit LegacyBFTableFilter(optional_ptr filter_p, const bool filters_null_values_p, + explicit LegacyBFTableFilter(shared_ptr filter_p, const bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p) - : TableFilter(TYPE), filter(filter_p), filters_null_values(filters_null_values_p), + : TableFilter(TYPE), filter(std::move(filter_p)), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p) { } diff --git a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp index 0c2e3c147..aa07f4491 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp @@ -9,6 +9,7 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/mutex.hpp" #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/enums/expression_type.hpp" #include "duckdb/common/enums/filter_propagate_result.hpp" @@ -87,44 +88,70 @@ struct TableFilterFunctions { }; //! Runtime bloom-filter state used by join pushdown and internal tablefilter functions. +//! A filter is first built (Initialize, then InsertHashes) and afterwards sealed. Only a sealed filter +//! describes a complete key set, so only a sealed filter may be looked up or handed to a scan. class BloomFilter { public: BloomFilter() = default; void Initialize(ClientContext &context_p, idx_t number_of_rows); - void InsertHashes(const Vector &hashes_v) const; + //! VectorOperations::Hash writes at the source row positions, so the hashes to insert are the ones sel + //! points at rather than the first count entries + void InsertHashes(const Vector &hashes_v, const SelectionVector &sel, idx_t count); + //! Inserts every hash the vector carries + void InsertHashes(const Vector &hashes_v); idx_t LookupHashes(const Vector &hashes_v, SelectionVector &result_sel, idx_t count) const; //! result_sel contains local positions into sel rather than source row ids. idx_t LookupHashes(const Vector &hashes_v, const SelectionVector &sel, SelectionVector &result_sel, idx_t count) const; - void InsertOne(hash_t hash) const; + void InsertOne(hash_t hash); bool LookupOne(hash_t hash) const; - void Merge(const BloomFilter &other); - void Reset(); + //! Folds the filter down to the smallest size that still meets the target false positive rate, releases the + //! memory that folding freed up, and marks the filter complete. + void Seal(ClientContext &context); bool IsInitialized() const { return initialized; } + bool IsSealed() const { + return sealed; + } + //! Whether the sealed filter still rejects enough for pushing it into a scan to pay off + bool IsUseful() const { + D_ASSERT(sealed); + return useful; + } static idx_t GetNumberOfSectors(idx_t number_of_rows); +private: + //! Halves the filter until only new_num_sectors sectors remain. Bit-exact with a filter that was built at + //! the smaller size from the start, because the sector index is the low bits of the hash and the bits set + //! within a sector are taken from the high bits. + void Fold(idx_t new_num_sectors); + //! Fraction of bits that are set, over the sectors currently in use + double Density() const; + private: idx_t num_sectors; uint64_t bitmask; // num_sectors - 1 -> used to get the sector offset bool initialized = false; + bool sealed = false; + bool useful = false; AllocatedData buf_; uint64_t *bf; }; //! FunctionData for bloom filter internal function struct BloomFilterFunctionData : public FunctionData { - BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, + BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p); - optional_ptr filter; + //! Published filters are shared and immutable: the hash table may drop its own reference at any time + shared_ptr filter; bool filters_null_values; string key_column_name; LogicalType key_type; @@ -193,9 +220,9 @@ struct DynamicFilterData { public: DynamicFilterData(ExpressionType comparison_type, Value constant); - mutex lock; + mutable annotated_shared_mutex lock; ExpressionType comparison_type; - Value constant; + Value constant DUCKDB_GUARDED_BY(lock); atomic initialized = {false}; unique_ptr ToExpression(const Expression &column) const; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp index 7a67a26d9..3e0a7a6a4 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp @@ -31,6 +31,10 @@ class LogicalJoin : public LogicalOperator { static void GetExpressionBindings(const Expression &expr, unordered_set &bindings); //! Moves the state shared by all logical joins when replacing one join operator with another static void MoveJoinState(LogicalJoin &source, LogicalJoin &target); + //! Replaces a join by one of its sides while keeping the join's output layout: the side's projection map, if + //! any, is carried by a filter without conditions so that operators above the join keep seeing the same columns + static unique_ptr PromoteJoinSide(unique_ptr child, + vector projection_map); bool HasProjectionMap() const override { return !left_projection_map.empty() || !right_projection_map.empty(); diff --git a/src/duckdb/src/include/duckdb/planner/sql_export/bound_expression_sql_exporter_internal.hpp b/src/duckdb/src/include/duckdb/planner/sql_export/bound_expression_sql_exporter_internal.hpp index 76f50e335..8d62721e9 100644 --- a/src/duckdb/src/include/duckdb/planner/sql_export/bound_expression_sql_exporter_internal.hpp +++ b/src/duckdb/src/include/duckdb/planner/sql_export/bound_expression_sql_exporter_internal.hpp @@ -121,8 +121,9 @@ class BoundExpressionSQLExportState { BoundExpressionSQLExportResult RestoreResultType(const LogicalType &type, unique_ptr result, const LogicalPlanVerificationPath &path); static bool RequiresConstantConstructor(const LogicalType &type); - BoundExpressionSQLExportResult CastToConstructedType(const LogicalType &type, unique_ptr child, - const LogicalPlanVerificationPath &path); + static BoundExpressionSQLExportResult CastToConstructedType(const LogicalType &type, + unique_ptr child, + const LogicalPlanVerificationPath &path); BoundExpressionSQLExportResult ExportConstant(const BoundConstantExpression &expression, const LogicalPlanVerificationPath &path); BoundExpressionSQLExportResult ExportReference(const BoundReferenceExpression &expression, diff --git a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp index 84e7d68cc..211a6158b 100644 --- a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp +++ b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp @@ -14,6 +14,7 @@ #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/common/type_visitor.hpp" +#include "duckdb/planner/expression.hpp" #include "duckdb/planner/logical_plan_verification_result.hpp" namespace duckdb { @@ -67,6 +68,12 @@ struct SQLExportHelpers { return issue; } + //! Constant ORDER BY keys have no effect, and a literal key would be bound as a position or rejected + static bool IsConstantOrderKey(const Expression &bound, const ParsedExpression &exported) { + return bound.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT || + exported.GetExpressionClass() == ExpressionClass::CONSTANT; + } + static unique_ptr OrderExpression(const LogicalType &type, unique_ptr expression) { if (type.id() == LogicalTypeId::VARCHAR) { diff --git a/src/duckdb/src/include/duckdb/planner/statement_preprocessor.hpp b/src/duckdb/src/include/duckdb/planner/statement_preprocessor.hpp index 6ae253b03..580cc4ec4 100644 --- a/src/duckdb/src/include/duckdb/planner/statement_preprocessor.hpp +++ b/src/duckdb/src/include/duckdb/planner/statement_preprocessor.hpp @@ -11,32 +11,35 @@ #include "duckdb/common/common.hpp" #include "duckdb/common/vector.hpp" #include "duckdb/parser/statement/pragma_statement.hpp" -#include "duckdb/transaction/transaction_context.hpp" -#include "duckdb/common/enums/current_transaction_state.hpp" namespace duckdb { class ClientContext; class ClientContextLock; class SQLStatement; struct PragmaInfo; -//! Preprocesses parsed statements: expands pragmas, unpacks multi-statements, and wraps in transactions + +//! The statements one user statement expands into +struct PreprocessedStatement { + vector> statements; +}; + +//! Expands a parsed user statement: reparses pragmas and unpacks multi-statements class StatementPreprocessor { public: explicit StatementPreprocessor(ClientContext &context); - void Preprocess(ClientContextLock &lock, vector> &statements, - CurrentTransactionState transaction_context_state); - void PreprocessInternal(ClientContextLock &lock, vector> &statements, - CurrentTransactionState transaction_context_state); + + //! Expands one user statement, in a transaction of its own unless one is active + PreprocessedStatement PreprocessStatement(ClientContextLock &lock, unique_ptr statement); + //! Expands one user statement, for a caller that already runs inside a transaction + PreprocessedStatement ExpandStatement(unique_ptr statement) const; private: ClientContext &context; - //! Transaction state implied by explicit transaction statements seen so far. Kept across Preprocess calls, - //! because statements may all be preprocessed before any of them is executed. - CurrentTransactionState chained_transaction_state = NOT_IN_ACTIVE_TRANSACTION; private: //! Handles a pragma statement, determines whether the statement needs reparsing, if it does, it returns the //! statement(s) to replace the current one. Otherwise, it just returns back the original statement in a vector. vector> TryReparsePragma(unique_ptr statement) const; }; + } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp index 0a5dff2c1..329c24704 100644 --- a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp +++ b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp @@ -46,10 +46,10 @@ class BlockAllocator { bool SupportsFlush() const; void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count) const; void FlushAll(optional_idx extra_memory = optional_idx()) const; - //! Memory freed since the last flush, which the allocator may still hold - idx_t GetDeallocatedSinceFlush() const; - //! Flush if at least threshold bytes were freed since the last flush, returns whether a flush happened - bool TryFlushDeallocated(idx_t threshold) const; + //! Estimated memory freed but still held by the allocator + idx_t GetEstimatedUnflushedMemory() const; + //! Flush if at least threshold bytes were freed since the last reclamation check, returns whether a flush happened + bool TryFlushDeallocated(idx_t threshold, idx_t memory_limit, idx_t used_memory) const; private: bool IsActive() const; @@ -72,8 +72,8 @@ class BlockAllocator { const hugeint_t uuid; //! Fallback allocator Allocator &allocator; - //! Memory freed since the last flush - mutable atomic deallocated_since_flush {0}; + //! Estimated memory freed but still held by the allocator + mutable atomic estimated_unflushed_memory {0}; //! Block size (power of two) const idx_t block_size; diff --git a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp index 0af0858c3..25a39088e 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp @@ -26,7 +26,7 @@ struct CompressedStringScanState : public SegmentScanState { void ScanToDictionaryVector(ColumnSegment &segment, Vector &result, idx_t result_offset, idx_t start, idx_t scan_count); const SelectionVector &GetSelVec(idx_t start, idx_t scan_count); - void Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count); + void Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, idx_t sel_count); bool AllowDictionaryScan(idx_t scan_count); diff --git a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp index 3d13ccad0..099a2d2f6 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp @@ -98,7 +98,7 @@ class EmptyValidityCompression { TableFilterState &filter_state) { } static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { } }; diff --git a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp index 2befc2e99..df0b6ac82 100644 --- a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp +++ b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp @@ -120,7 +120,7 @@ struct UncompressedStringStorage { idx_t result_offset); static void StringScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp index 47b8893ea..8456d63dd 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp @@ -247,7 +247,7 @@ class ColumnData : public enable_shared_from_this { idx_t ScanVector(TransactionData transaction, idx_t vector_index, ColumnScanState &state, Vector &result, idx_t target_scan, UpdateScanType update_type); void SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count); + idx_t sel_count, ScanVectorType scan_type); void FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp index aac02c130..25a5cf012 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp @@ -63,7 +63,8 @@ class ColumnSegment : public SegmentBase { //! Scan one vector from this segment void Scan(ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset, ScanVectorType scan_type); //! Scan a subset of a vector (defined by the selection vector) - void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count); + void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count, + idx_t result_offset, ScanVectorType scan_type); //! Scan one vector while applying a filter to the vector, returning only the matching elements void Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/include/duckdb_v2.h b/src/duckdb/src/include/duckdb_v2.h index efceccf75..2cede3650 100644 --- a/src/duckdb/src/include/duckdb_v2.h +++ b/src/duckdb/src/include/duckdb_v2.h @@ -538,6 +538,12 @@ typedef enum DUCKDB_V2_ERROR { //! An object exceeded its maximum permitted size. DUCKDB_V2_ERROR_INPUT_OBJECT_SIZE = 2004, + /*! + * The metadata asked for is known only once execution binds the statement that produces it, because the statement + * expands into several. Step the result and ask again. + */ + DUCKDB_V2_ERROR_INPUT_METADATA_DEFERRED = 2005, + //! The requested resource is already in use. DUCKDB_V2_ERROR_RESOURCE_IN_USE = 3001, @@ -10038,8 +10044,9 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_iterator_next(duckdb_v2_stateme * Binding is read-only and does not disturb a live result: it begins no query, claims no cursor, reuses an active * transaction read-only, and runs alongside a paused stream. It is single-consumer like the stepping functions, so bind * concurrently only on a second connection. Prepare-time errors — binder, catalog, preprocessing — surface here. A - * statement that preprocessing expands into a group (a dynamic PIVOT, or statement-expanding DDL such as ALTER ADD - * COLUMN with a non-constant DEFAULT) cannot be bound and is rejected with ERROR_INPUT_INVALID; execute it instead. + * statement that expands into a group (a dynamic PIVOT, or statement-expanding DDL such as ALTER ADD COLUMN with a + * non-constant DEFAULT) has a schema known only at execution, and is reported with ERROR_INPUT_METADATA_DEFERRED; + * execute it instead. * * *out_schema and *out_parameters are set to NULL on failure. * @@ -10104,8 +10111,9 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_statement_add_collection(duckdb_v2_sql_st * * This gives the type before the statement-level rewrites that `duckdb_v2_statement_execute()` applies, if any. So a * PRAGMA reports PRAGMA even where execution rewrites it into a SELECT or a CALL. - * `duckdb_v2_result_get_statement_type()` on the executed result reports the rewritten type. A statement the parser - * expands into a group reports MULTI. Its parts are not visible here, and statement_bind rejects it. + * `duckdb_v2_result_get_statement_type()` on the executed result reports the rewritten type of a statement that + * rewrites into one. A statement the parser expands into a group reports MULTI, here and on its result. Its parts are + * not visible here, and statement_bind reports ERROR_INPUT_METADATA_DEFERRED for it. * * history: * - stable: v2.0.0 @@ -10825,6 +10833,11 @@ typedef struct _duckdb_v2_prepared_statement { * `ERROR_INPUT_INVALID` when the plan would not be reused, so a caller who wants the handle only for the speedup finds * out here rather than after silently taking the slow path. * + * A statement that DuckDB expands into several, such as a dynamic PIVOT, is kept as written and expanded again at each + * execution, in the transaction state of that execution. It runs nothing here, its plan is never reused, and its + * result's metadata is deferred as for `duckdb_v2_statement_execute()`. Parameters on such a statement are refused with + * `ERROR_INPUT_INVALID`. + * * Refuses with `ERROR_RESOURCE_IN_USE` while a statement on the connection is still running. Drain, destroy, or * interrupt that result first, or prepare on another connection. `*out_prepared` is set to NULL on failure. * @@ -11010,11 +11023,10 @@ typedef enum DUCKDB_V2_RESULT_STEP_STATUS { * parameters cannot be mixed within one statement. Parameters and statement expansion are mutually exclusive: passing * values for a statement that preprocesses into a group is rejected with ERROR_INPUT_INVALID. * - * Preprocessing can expand one statement into a group — a dynamic PIVOT, or statement-expanding DDL such as ALTER ... - * ADD COLUMN with a non-constant DEFAULT. The group executes as one result through the same steps, and the stream - * surfaces the first row-producing statement of the group, or the last statement when none produces rows. An expansion - * with more than one row-producing statement cannot be streamed as a single result and reports - * ERROR_QUERY_NOT_IMPLEMENTED; no known expansion produces one. + * DuckDB can expand one statement into a group: a dynamic PIVOT, or statement-expanding DDL such as ALTER ... ADD + * COLUMN with a non-constant DEFAULT. The group executes as one result through the same steps, and the stream surfaces + * the rows of the first row-producing statement of the group. A row-producing statement followed by another cannot be + * served as a single result and reports ERROR_QUERY_NOT_IMPLEMENTED; no known expansion produces one. * * One running statement per connection: this refuses with ERROR_RESOURCE_IN_USE while a statement on the connection is * still running. Drain, destroy, or interrupt that result first, or open another connection. @@ -11211,8 +11223,9 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_drain(duckdb_v2_result_handle resu * QUERY_RESULT for statements that produce rows (SELECT, RETURNING, EXPLAIN), CHANGED_ROWS for an INSERT, UPDATE, or * DELETE without RETURNING, NOTHING for DDL and other statements with no row output. * - * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a - * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. + * Prepare-time metadata: available from statement_execute on, except for a statement that expands into a group, where + * it fails with ERROR_INPUT_METADATA_DEFERRED until stepping has bound the row-producing statement, or the group ended + * without one. * * history: * - stable: v2.0.0 @@ -11229,8 +11242,8 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_result_type(duckdb_v2_result_h /*! * Returns the SQL statement type that produced the result. * - * Prepare-time metadata: available from statement_execute on, except for a statement that preprocessing expands into a - * group, where it fails with ERROR_INPUT_INVALID until stepping has prepared the row-producing fragment. + * Prepare-time metadata: available from statement_execute on. A statement that expands into a group reports its type as + * written: MULTI, or PRAGMA for a pragma such as IMPORT DATABASE. * * history: * - stable: v2.0.0 @@ -11248,9 +11261,9 @@ DUCKDB_C_API DUCKDB_V2_ERROR duckdb_v2_result_get_statement_type(duckdb_v2_resul * Returns the result's output schema as a single schema handle. * * Builds an owned schema of the result's column names and types into *out_schema; destroy it via schema_destroy. - * Prepare-time metadata, available before the first step, or for an expanding statement once stepping has prepared the - * row-producing fragment. Never empty: a non-SELECT reports a single status column, either a BIGINT changed-rows count - * or a BOOLEAN success. + * Prepare-time metadata, available before the first step, or for an expanding statement once stepping has bound the + * row-producing statement (before that it fails with ERROR_INPUT_METADATA_DEFERRED). Never empty: a non-SELECT reports + * a single status column, either a BIGINT changed-rows count or a BOOLEAN success. * * This schema is the authoritative source for the column types of the chunks the result produces; vectors do not carry * their own type. diff --git a/src/duckdb/src/logging/log_types.cpp b/src/duckdb/src/logging/log_types.cpp index ec4f514db..bb25617f0 100644 --- a/src/duckdb/src/logging/log_types.cpp +++ b/src/duckdb/src/logging/log_types.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/file_opener.hpp" #include "duckdb/main/http/http_util.hpp" +#include "duckdb/main/profiler/profiling_utils.hpp" #include "duckdb/execution/physical_operator.hpp" #include "duckdb/logging/file_system_logger.hpp" #include "duckdb/main/attached_database.hpp" @@ -362,14 +363,19 @@ LogicalType MetricsLogType::GetLogType() { child_list_t child_list = { {"metric", LogicalType::VARCHAR}, {"value", LogicalType::VARCHAR}, + {"start_time", LogicalType::TIMESTAMP_TZ}, + {"end_time", LogicalType::TIMESTAMP_TZ}, }; return LogicalType::STRUCT(child_list); } -string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value) { +string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value, + optional_ptr timing) { child_list_t child_list = { {"metric", metric}, {"value", value.ToString()}, + {"start_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->start_time) : Value()}, + {"end_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->end_time) : Value()}, }; return Value::STRUCT(std::move(child_list)).ToString(); } diff --git a/src/duckdb/src/main/capi/v1/arrow-c.cpp b/src/duckdb/src/main/capi/v1/arrow-c.cpp index e07b834e4..d2432e60c 100644 --- a/src/duckdb/src/main/capi/v1/arrow-c.cpp +++ b/src/duckdb/src/main/capi/v1/arrow-c.cpp @@ -138,17 +138,18 @@ duckdb_error_data duckdb_data_chunk_from_arrow(duckdb_connection connection, str auto &arrow_types = arrow_table->GetColumns(); dchunk->SetChildCardinality(duckdb::NumericCast(arrow_array->length)); + // All columns share ownership of the array, so it is only released once no column references it anymore + auto owned_data = duckdb::make_shared_ptr(); + owned_data->arrow_array = *arrow_array; + // We set it to nullptr to effectively transfer the ownership + arrow_array->release = nullptr; for (idx_t i = 0; i < dchunk->ColumnCount(); i++) { auto &parent_array = *arrow_array; auto &array = parent_array.children[i]; auto arrow_type = arrow_types.at(i); auto array_physical_type = arrow_type->GetPhysicalType(); auto array_state = duckdb::make_uniq(*conn->context); - // We need to make sure that our chunk will hold the ownership - array_state->owned_data = duckdb::make_shared_ptr(); - array_state->owned_data->arrow_array = *arrow_array; - // We set it to nullptr to effectively transfer the ownership - arrow_array->release = nullptr; + array_state->owned_data = owned_data; try { switch (array_physical_type) { case duckdb::ArrowArrayPhysicalType::DICTIONARY_ENCODED: diff --git a/src/duckdb/src/main/capi/v1/pending-c.cpp b/src/duckdb/src/main/capi/v1/pending-c.cpp index 1bf7ea633..3b2d4d2d7 100644 --- a/src/duckdb/src/main/capi/v1/pending-c.cpp +++ b/src/duckdb/src/main/capi/v1/pending-c.cpp @@ -26,8 +26,7 @@ duckdb_state duckdb_pending_prepared_internal(duckdb_prepared_statement prepared try { result->statement = wrapper->statement->Submit(wrapper->values); result->allow_streaming = - allow_streaming && duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), - result->statement->GetStatementProperties()); + allow_streaming && duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), *result->statement); if (!result->allow_streaming) { // Settled before the caller drives it, so each execute_task call stays one unit of work result->statement->Materialize(); diff --git a/src/duckdb/src/main/capi/v1/prepared-c.cpp b/src/duckdb/src/main/capi/v1/prepared-c.cpp index bf7a268cb..20b7fd984 100644 --- a/src/duckdb/src/main/capi/v1/prepared-c.cpp +++ b/src/duckdb/src/main/capi/v1/prepared-c.cpp @@ -447,8 +447,8 @@ duckdb_state duckdb_execute_prepared_streaming(duckdb_prepared_statement prepare try { auto result = wrapper->statement->Submit(wrapper->values); // Without an out_result the stream is dropped unread, which rolls back what the statement wrote - if (result->HasError() || !duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), - result->GetStatementProperties(), out_result != nullptr)) { + if (result->HasError() || + !duckdb::StreamsByDefault(wrapper->statement->GetStatementType(), *result, out_result != nullptr)) { result->Complete(); return DuckDBTranslateResult(std::move(result), out_result); } diff --git a/src/duckdb/src/main/capi/v2/capi_v2.cpp b/src/duckdb/src/main/capi/v2/capi_v2.cpp index 7e2ff1921..eeee4e7f4 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2.cpp @@ -255,6 +255,11 @@ auto RenderCaughtError(DUCKDB_V2_ERROR &code, string &text, optional &ra // The bare throw re-raises the exception currently being handled so the catch clauses can dispatch on its type. try { throw; + } catch (const MetadataDeferredException &ex) { + ErrorData error_data(ex); + code = DUCKDB_V2_ERROR_INPUT_METADATA_DEFERRED; + text = error_data.Message(); + raw_message = error_data.RawMessage(); } catch (const duckdb::Exception &ex) { ErrorData error_data(ex); code = GetErrorCodeFromExceptionType(error_data.Type()); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp b/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp index 061f6e071..d36b021ce 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_prepared_statement.cpp @@ -48,9 +48,8 @@ DUCKDB_V2_ERROR duckdb_v2_prepared_statement_create(duckdb_v2_connection_handle DUCKDB_CHECK_ARG(statement); return WithErrorHandler(err, [&]() { auto *connection = Convert(conn); - // The engine refuses a prepare while another statement on the connection is running - ThrowIfGroupRunning(*GetBusySlot(*connection->context)); - // Borrowed, not consumed: prepare a copy so the caller keeps the original. + // Borrowed, not consumed: prepare a copy so the caller keeps the original. The engine refuses a prepare while + // another statement on the connection is running. auto prepared = connection->context->Prepare(Convert(statement)->Copy()); if (prepared->HasError()) { // Prepare reports failure on the returned object rather than throwing; re-throw @@ -61,8 +60,8 @@ DUCKDB_V2_ERROR duckdb_v2_prepared_statement_create(duckdb_v2_connection_handle if (require_cacheable && !PreparedReusesPlanV2(prepared->GetStatementProperties())) { throw duckdb::InvalidInputException( "prepared_statement_create(require_cacheable): this plan is re-bound on every execution (an unresolved " - "parameter type, or a table scan), so executing it is no faster than statement_execute; prepare " - "without require_cacheable to accept that"); + "parameter type, a table scan, or a statement that expands into several), so executing it is no faster " + "than statement_execute; prepare without require_cacheable to accept that"); } auto wrapper = duckdb::make_uniq(); wrapper->context = connection->context; diff --git a/src/duckdb/src/main/capi/v2/capi_v2_result.cpp b/src/duckdb/src/main/capi/v2/capi_v2_result.cpp index b9daeef94..c7b7391cc 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_result.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_result.cpp @@ -8,7 +8,6 @@ #include "duckdb/common/types/column/column_data_collection.hpp" #include "duckdb/common/enums/query_result_state.hpp" -#include "duckdb/parser/statement/transaction_statement.hpp" namespace duckdb::capiv2 { namespace { @@ -50,76 +49,66 @@ void ReadChangedRows(const ArrowArrayWrapper &unit, idx_t &rows_changed) { // ResultWrapperV2 state machine // --------------------------------------------------------------------------- -void ResultWrapperV2::BeginPending(unique_ptr next_handle, bool is_principal, StatementType written_type) { +void ResultWrapperV2::BeginPending(unique_ptr next_handle, StatementType written_type) { next_handle->ThrowIfError(); - principal_active = is_principal; - stream_when_ready = StreamsByDefault(written_type, next_handle->GetStatementProperties()); + stream_when_ready = StreamsByDefault(written_type, *next_handle); if (!stream_when_ready) { // Settled before the caller steps it, so every step stays one bounded unit of work next_handle->Materialize(); } - if (is_principal) { - types = next_handle->GetTypes(); - names = next_handle->GetNames(); - statement_type = next_handle->GetStatementType(); - properties = next_handle->GetStatementProperties(); - if (format && format->Is()) { - D_ASSERT(!arrow_schema.arrow_schema.release); - CopyArrowSchema(next_handle->FormatState().Schema(), arrow_schema.arrow_schema); - } - metadata_available = true; - } + statement_type = next_handle->GetStatementType(); handle = std::move(next_handle); state = State::PENDING; + CaptureMetadata(); } -void ResultWrapperV2::StartNextFragment() { - D_ASSERT(fragment_index < fragments.size()); - if (fragment_index > 0 && context->IsInterrupted()) { - // PendingQuery's InitialCleanup clears a pending interrupt flag; - // without this check an interrupt landing exactly at a fragment - // boundary would be silently swallowed and the group would keep - // running. (At fragment 0 the clearing is desired: a stale - // interrupt must not poison a new query.) - throw InterruptException(); - } - idx_t this_index = fragment_index; - auto stmt = std::move(fragments[fragment_index++]); - auto written_type = stmt->type; - bool is_last = fragment_index == fragments.size(); - // Parameters bind only to the first fragment (statement_execute rejects them on - // a statement that expands, so that fragment is the user's statement). Later - // fragments take the no-values path. - auto next_handle = (this_index == 0 && !param_values.empty()) - ? context->Submit(std::move(stmt), param_values, QueryParameters(format)) - : context->Submit(std::move(stmt), QueryParameters(format)); - // Principal selection is a property of the fragment group; compute it here, then - // hand the handle to the shared BeginPending seam. A HasError() handle is left - // for BeginPending to raise (return_type is meaningless on it). - bool has_result = !next_handle->HasError() && - next_handle->GetStatementProperties().return_type == StatementReturnType::QUERY_RESULT; - if (principal_seen && has_result) { - // ClientContext::Query would chain these as separate results with - // separate schemas; a single stream cannot. No known expansion - // produces two row-producing fragments. - throw NotImplementedException( - "statement expands to multiple row-producing statements, which cannot be streamed as a single result"); +void ResultWrapperV2::CaptureMetadata() { + if (metadata_available) { + return; } - // Result selection mirrors ClientContext::Query: the caller sees the - // first row-producing fragment, or the last fragment when none - // produces rows. - bool is_principal = has_result || (is_last && !principal_seen); - if (has_result) { - principal_seen = true; + const bool arrow = format && format->Is(); + if (stream) { + // A failed result reports empty metadata, which is not the result's + if (stream->HasError() || !stream->MetadataAvailable()) { + return; + } + types = stream->GetTypes(); + names = stream->GetNames(); + properties = stream->GetStatementProperties(); + if (arrow) { + auto &arrow_stream = static_cast &>(*stream); + CopyArrowSchema(arrow_stream.FormatState().Schema(), arrow_schema.arrow_schema); + } + } else if (handle) { + if (handle->HasError() || !handle->MetadataAvailable()) { + return; + } + types = handle->GetTypes(); + names = handle->GetNames(); + properties = handle->GetStatementProperties(); + if (arrow) { + CopyArrowSchema(handle->FormatState().Schema(), arrow_schema.arrow_schema); + } + } else { + return; } - BeginPending(std::move(next_handle), is_principal, written_type); + metadata_available = true; } -void ResultWrapperV2::RequireMetadata() const { - if (!metadata_available) { - throw InvalidInputException("result metadata is not yet available: the statement expands to a group of " - "statements and the result-producing one has not been prepared; step the result"); +void ResultWrapperV2::RequireMetadata() { + CaptureMetadata(); + if (metadata_available) { + return; } + // A result that ended before its metadata was known never gets it + if (state == State::ERRORED) { + error.Throw(); + } + if (state == State::CANCELLED) { + throw InterruptException(); + } + throw MetadataDeferredException("result metadata is not yet available: the statement expands into several, and " + "the one that produces the result has not been bound; step the result"); } DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::HandleExecutionError(ErrorData error_data) { @@ -131,13 +120,6 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::HandleExecutionError(ErrorData err busy_slot->cancel_requests.load() != cancel_requests_at_start; stream.reset(); handle.reset(); - fragments.clear(); - try { - AbandonIncompleteGroup(); - } catch (...) { - // Best effort; never mask the original error. - } - ReleaseBusySlot(); if (user_cancelled) { // In the step channel, cancellation is a status, not an error, // regardless of which phase the interrupt landed in. The status @@ -168,6 +150,7 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptrExecuteTask(); + CaptureMetadata(); } catch (std::exception &ex) { return HandleExecutionError(ErrorData(ex)); } @@ -192,6 +175,7 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptr>(std::move(handle)); } + CaptureMetadata(); } catch (std::exception &ex) { return HandleExecutionError(ErrorData(ex)); } @@ -207,6 +191,7 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptrExecuteTask(); + CaptureMetadata(); } catch (std::exception &ex) { // The stream records an interrupt as an error rather than // throwing; this is a guard for anything that escapes. @@ -241,30 +226,17 @@ DUCKDB_V2_RESULT_STEP_STATUS ResultWrapperV2::Step(unique_ptrGetErrorObject() : handle->GetErrorObject()); } + try { + CaptureMetadata(); + } catch (std::exception &ex) { + return HandleExecutionError(ErrorData(ex)); + } if (!unit) { stream.reset(); handle.reset(); - if (fragment_index < fragments.size()) { - // More fragments in the group: start the next one and keep - // reporting WAITING. FINISHED only after the whole group - // (including a trailing injected COMMIT) has executed. - try { - StartNextFragment(); - } catch (std::exception &ex) { - return HandleExecutionError(ErrorData(ex)); - } - return DUCKDB_V2_RESULT_STEP_STATUS_WAITING; - } state = State::FINISHED; - ReleaseBusySlot(); return DUCKDB_V2_RESULT_STEP_STATUS_FINISHED; } - if (!principal_active) { - // Output of a non-principal fragment (e.g. the internal UPDATE of - // an expanded ALTER): discard it, exactly as ClientContext::Query - // drops these results from its chain. - return DUCKDB_V2_RESULT_STEP_STATUS_WAITING; - } out_unit = std::move(unit); return DUCKDB_V2_RESULT_STEP_STATUS_CHUNK; } @@ -285,7 +257,9 @@ void ResultWrapperV2::Wait() { // safe: the engine may already have processed an execution error and // ended the query internally (waiting then dereferences the closed // query), and a ready or finished query has no task to wake on. - switch (handle->Poll()) { + auto polled = handle->Poll(); + CaptureMetadata(); + switch (polled) { case QueryResultState::BLOCKED: case QueryResultState::NO_TASKS_AVAILABLE: // No progress possible right now; this is the one case where @@ -360,14 +334,14 @@ void ResultWrapperV2::AdvanceToMetadata() { if (status == DUCKDB_V2_RESULT_STEP_STATUS_CHUNK) { throw InternalException("a row was produced before result metadata was available"); } - break; // FINISHED / CANCELLED: no row-producing fragment. + break; // FINISHED / CANCELLED } } template idx_t ResultWrapperV2::Drain() { - // CHANGED_ROWS results stream a single-row BIGINT Count unit. The principal fragment's metadata may only become - // available mid-drain (expanding statements), so check per unit: surfaced units always belong to the principal. + // CHANGED_ROWS results stream a single-row BIGINT Count unit. The metadata of a statement that expands into several + // may only become available mid-drain, so check per unit. idx_t rows_changed = 0; while (auto unit = FetchBlocking()) { if (metadata_available && properties.return_type == StatementReturnType::CHANGED_ROWS) { @@ -409,15 +383,10 @@ auto ExecutePreparedStatementV2(const shared_ptr &context, Prepar auto wrapper = make_uniq(); wrapper->busy_slot = GetBusySlot(*context); wrapper->cancel_requests_at_start = wrapper->busy_slot->cancel_requests.load(); - ThrowIfGroupRunning(*wrapper->busy_slot); wrapper->context = context; wrapper->format = std::move(format); - // A prepared statement is always one engine statement: preprocessing, expansion and - // the wrapping transaction all happened at prepare time, so this bypasses the fragment - // machinery and is always principal. fragment_count is 1 for metadata symmetry only. - wrapper->fragment_count = 1; // The engine refuses the submission while another statement on the connection is running - wrapper->BeginPending(prepared.Submit(values, QueryParameters(wrapper->format)), true, prepared.GetStatementType()); + wrapper->BeginPending(prepared.Submit(values, QueryParameters(wrapper->format)), prepared.GetStatementType()); // The engine runs a prepared statement through an internal EXECUTE, whose statement type // would otherwise be what the result reports. Report the type of the statement that was // prepared instead, so a prepared result is indistinguishable from a stateless one. @@ -433,60 +402,23 @@ auto ExecuteStatementV2(const shared_ptr &context, const SQLState // The busy slot lives in the context's registered-state map, so the connection handle stays a bare Connection * wrapper->busy_slot = GetBusySlot(*context); wrapper->cancel_requests_at_start = wrapper->busy_slot->cancel_requests.load(); - ThrowIfGroupRunning(*wrapper->busy_slot); - // Refused before preprocessing, which decides from the active transaction whether to wrap a group in its own - context->VerifyNoOpenResult(); // Borrowed, not consumed: execute a copy so the caller keeps the original. auto stmt = statement.Copy(); // Fold parameter values in as constants, keyed by name when parameter_names // supplies one and positionally ("1".."N") otherwise. - BuildParameterMap(parameter_names, parameter_values, parameter_count, function_name, wrapper->param_values); - // Statement-level preprocessing (pragma reparsing, expansion - // unpacking, transaction wrapping): one user statement can expand - // into a group of engine statements that the wrapper executes in - // order. parse_sql deliberately leaves this to statement_execute so - // parsing stays binder-free and a group is never split across the - // API boundary. - wrapper->fragments.push_back(std::move(stmt)); - context->PreprocessStatements(wrapper->fragments); - if (wrapper->fragments.empty()) { - throw InvalidInputException("statement preprocessing yielded no executable statements"); - } - wrapper->fragment_count = wrapper->fragments.size(); - // Reject parameters on a statement that expands into a group: the values would - // bind to the first fragment, an injected BEGIN, not the user's statement. (A - // statement can carry a parameter and still expand, e.g. a volatile DEFAULT.) - if (!wrapper->param_values.empty() && wrapper->fragments.size() > 1) { - throw InvalidInputException( - "parameters are not supported for a statement that expands into multiple engine statements"); - } - // Detect whether preprocessing wrapped this group in its own - // transaction (autocommit input expanded to BEGIN ... COMMIT). - // Preprocessing only injects the wrap for a multi-fragment group, as a - // leading BEGIN paired with a trailing COMMIT; a lone user-issued BEGIN - // is a single fragment the user owns and the bridge must not roll back. - if (wrapper->fragments.size() > 1 && wrapper->fragments.front()->type == StatementType::TRANSACTION_STATEMENT && - wrapper->fragments.back()->type == StatementType::TRANSACTION_STATEMENT) { - auto &front_stmt = wrapper->fragments.front()->Cast(); - auto &back_stmt = wrapper->fragments.back()->Cast(); - wrapper->owns_wrapping_transaction = front_stmt.info->type == TransactionType::BEGIN_TRANSACTION && - back_stmt.info->type == TransactionType::COMMIT; - } + identifier_map_t values; + BuildParameterMap(parameter_names, parameter_values, parameter_count, function_name, values); wrapper->context = context; wrapper->format = std::move(format); - if (wrapper->fragments.size() > 1) { - // On any failure below, the wrapper's destructor releases the claim - void *expected = nullptr; - if (!wrapper->busy_slot->owner.compare_exchange_strong(expected, wrapper.get())) { - ThrowConnectionInUse(); - } - } - // Prepare the first fragment. Lazy streaming execution: nothing - // executes until the result is stepped; for non-expanding statements - // (the common case) this also captures the metadata immediately. The engine refuses the - // submission while another statement on the connection is running. - wrapper->StartNextFragment(); + // Lazy streaming execution: nothing executes until the result is stepped. The engine expands the statement, + // running what it expands into as one query, and refuses the submission while another statement on the + // connection is running. + auto handle = values.empty() ? context->Submit(std::move(stmt), QueryParameters(wrapper->format)) + : context->Submit(std::move(stmt), values, QueryParameters(wrapper->format)); + // The result's type, so that a PRAGMA the engine reparses into a SELECT streams as that SELECT + const auto result_type = handle->GetStatementType(); + wrapper->BeginPending(std::move(handle), result_type); return wrapper; } @@ -661,11 +593,7 @@ DUCKDB_V2_ERROR duckdb_v2_result_get_statement_type(duckdb_v2_result_handle resu DUCKDB_CHECK_ARG(result); DUCKDB_CHECK_ARG(out_type); - return WithErrorHandler(err, [&]() { - auto *r = Convert(result); - r->RequireMetadata(); - *out_type = Convert(r->statement_type); - }); + return WithErrorHandler(err, [&]() { *out_type = Convert(Convert(result)->statement_type); }); } DUCKDB_V2_ERROR duckdb_v2_result_get_schema(duckdb_v2_result_handle result, duckdb_v2_schema_handle *out_schema, diff --git a/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp b/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp index b4cb9f7d1..8665c867c 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_statement.cpp @@ -287,21 +287,15 @@ DUCKDB_V2_ERROR duckdb_v2_statement_bind(duckdb_v2_connection_handle conn, duckd } auto *connection = Convert(conn); - // Borrowed, not consumed: bind a copy. Preprocess it so the schema matches - // what execution would bind (pragma reparse, expansion). - duckdb::vector> fragments; - fragments.push_back(Convert(statement)->Copy()); - connection->context->PreprocessStatements(fragments); - if (fragments.size() != 1) { - // A statement that expands into a group (a dynamic PIVOT, or - // statement-expanding DDL) is not bindable: bind binds one engine statement. - throw duckdb::InvalidInputException( - "statement expands into multiple engine statements; bind is not supported, execute it instead"); + // Borrowed, not consumed: bind a copy. BindStatement, not Prepare: bind phase only (read-only, no optimize / + // physical plan) and allowed while a result is live (Prepare is refused). It expands the statement as + // execution would, and throws on a bind error, routed through WithErrorHandler. + auto signature = connection->context->BindStatement(Convert(statement)->Copy()); + if (signature.metadata_deferred) { + throw MetadataDeferredException( + "the statement expands into several, so its schema is known only once execution binds the one that " + "produces the result; execute it instead"); } - // BindStatement, not Prepare: bind phase only (read-only, no optimize / physical - // plan) and allowed while a result is live (Prepare is refused). It - // throws on a bind error, routed through WithErrorHandler. - auto signature = connection->context->BindStatement(std::move(fragments[0])); // Assemble into locals, publish to the out-params only at the end once nothing // can throw, so a failure leaves them NULL. auto output = duckdb::make_uniq(); diff --git a/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp b/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp index f38219eab..74f634bb9 100644 --- a/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp +++ b/src/duckdb/src/main/capi/v2/capi_v2_vector.cpp @@ -1,5 +1,6 @@ #include "duckdb/main/capi_v2/capi_v2_internal.hpp" +#include "duckdb/common/exception/conversion_exception.hpp" #include "duckdb/common/types/vector.hpp" #include "duckdb/common/vector/array_vector.hpp" #include "duckdb/common/vector/constant_vector.hpp" diff --git a/src/duckdb/src/main/client_context.cpp b/src/duckdb/src/main/client_context.cpp index 5c36b0b47..62ae13551 100644 --- a/src/duckdb/src/main/client_context.cpp +++ b/src/duckdb/src/main/client_context.cpp @@ -42,7 +42,6 @@ #include "duckdb/planner/binder.hpp" #include "duckdb/planner/logical_plan_verifier.hpp" #include "duckdb/planner/planner.hpp" -#include "duckdb/common/enums/current_transaction_state.hpp" #include "duckdb/planner/statement_preprocessor.hpp" #include "duckdb/storage/data_table.hpp" #include "duckdb/transaction/meta_transaction.hpp" @@ -429,6 +428,15 @@ void ClientContext::CheckIfPreparedStatementIsExecutable(PreparedStatementData & } } +bool ClientContext::ErrorInvalidatesQueryTransaction(ExceptionType type) { + // A statement that expands into several and fails after one of its statements wrote has applied part of its + // effect, which only ending the transaction undoes + if (active_query && active_query->ExpandsIntoSeveral() && active_query->Wrote()) { + return true; + } + return ErrorInvalidatesTransaction(type); +} + bool ClientContext::ErrorInvalidatesTransaction(ExceptionType type) { switch (transaction.GetInvalidationPolicy()) { case TransactionInvalidationPolicy::STANDARD_POLICY: @@ -440,32 +448,9 @@ bool ClientContext::ErrorInvalidatesTransaction(ExceptionType type) { } StatementIterator ClientContext::IterateStatements(const string &query) { - // The iterator yields ready-to-execute (engine-facing) statements: PRAGMA reparse, - // MULTI_STATEMENT unpack and transaction wrapping per peel — matches the eager API users expect. - // Callers that want raw parse-facing statements and drive their own preprocessing construct a - // ParseIterator directly (e.g. Query / ParseStatementsInternal below, which hold the lock). return StatementIterator(ParseIterator(*this, query)); } -void ClientContext::PreprocessStatements(vector> &buffer, optional_ptr lock, - optional_ptr preprocessor) { - // Acquire our own lock if the caller doesn't hold one (e.g. the shell); own_lock keeps it alive - // for the duration of the preprocess pass. - unique_ptr own_lock; - if (!lock) { - own_lock = LockContext(); - lock = own_lock.get(); - } - unique_ptr own_preprocessor; - if (!preprocessor) { - own_preprocessor = make_uniq(*this); - preprocessor = own_preprocessor.get(); - } - const CurrentTransactionState transaction_state = - transaction.HasActiveTransaction() ? IN_ACTIVE_TRANSACTION : NOT_IN_ACTIVE_TRANSACTION; - preprocessor->Preprocess(*lock, buffer, transaction_state); -} - vector> ClientContext::ParseStatementsInternal(ClientContextLock &lock, const string &query) { try { QueryProfiler::Get(*this).StartQuery(query); @@ -474,11 +459,7 @@ vector> ClientContext::ParseStatementsInternal(ClientCo StatementIterator iterator {ParseIterator(*this, query)}; vector> result; while (iterator.Peek()) { - auto stmt = iterator.GetStatementForExecutionWithLock(lock); - if (!stmt) { - continue; // a peel that preprocessing swallowed - } - result.push_back(std::move(stmt)); + result.push_back(iterator.GetStatementForExecution()); } return result; } catch (std::exception &ex) { @@ -495,6 +476,8 @@ unique_ptr ClientContext::ExtractPlan(const string &query) { if (statements.size() != 1) { throw InvalidInputException("ExtractPlan can only prepare a single statement"); } + statements[0] = ExpandSingleStatementInternal(*lock, std::move(statements[0]), + "ExtractPlan can only prepare a single statement"); unique_ptr plan; RunFunctionInTransactionInternal(*lock, [&]() { @@ -537,6 +520,22 @@ static PreparedStatementInfo GetPreparedStatementInfo(PreparedStatementData &dat unique_ptr ClientContext::PrepareInternal(ClientContextLock &lock, unique_ptr statement) { + // Only a pragma or a multi-statement can expand, and only one that does not expand into exactly one statement + // keeps the statement as written, to expand it again at each execution + unique_ptr written_statement; + if (statement->type == StatementType::PRAGMA_STATEMENT || statement->type == StatementType::MULTI_STATEMENT) { + written_statement = statement->Copy(); + } + auto expanded = PreprocessStatementInternal(lock, std::move(statement)); + if (expanded.statements.size() != 1) { + D_ASSERT(written_statement); + if (!written_statement->named_param_map.empty()) { + throw InvalidInputException(EXPANDING_PARAMETERS_ERROR); + } + return make_uniq(shared_from_this(), std::move(written_statement)); + } + statement = std::move(expanded.statements[0]); + auto statement_query = statement->query; // prepare the statement under a generated name - the returned PreparedStatement only refers to that name auto name = "duckdb_prepare_internal_" + UUID::ToString(UUID::GenerateRandomUUID()); @@ -580,15 +579,34 @@ unique_ptr ClientContext::Prepare(unique_ptr st StatementSignature ClientContext::BindStatement(unique_ptr statement) { auto lock = LockContext(); - auto named_param_map = statement->named_param_map; StatementSignature signature; ErrorData bind_error; RunFunctionInTransactionInternal( *lock, [&]() { try { + // Expanded inside this transaction, so an expansion error leaves the caller's transaction as a bind + // error does + const bool has_parameters = !statement->named_param_map.empty(); + auto expanded = StatementPreprocessor(*this).ExpandStatement(std::move(statement)); + if (expanded.statements.empty()) { + signature.properties.return_type = StatementReturnType::NOTHING; + return; + } + if (expanded.statements.size() > 1) { + if (has_parameters) { + throw InvalidInputException(EXPANDING_PARAMETERS_ERROR); + } + // The placeholder the planner reports for a result known only at execution + signature.names = {"unknown"}; + signature.types = {LogicalTypeId::UNKNOWN}; + signature.metadata_deferred = true; + return; + } + auto bound = std::move(expanded.statements[0]); + auto named_param_map = bound->named_param_map; Planner planner(*this); - planner.CreatePlan(std::move(statement)); + planner.CreatePlan(std::move(bound)); signature.names = planner.names; signature.types = planner.types; signature.properties = std::move(planner.properties); @@ -646,7 +664,7 @@ unique_ptr ClientContext::Prepare(const string &query) { unique_ptr ClientContext::SubmitStatementInternal(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, Retention retention) { + const QueryParameters ¶meters) { // prepare the query for execution if (!statement->named_param_map.empty() && parameters.statement_args) { PreparedStatement::VerifyParameters(*parameters.statement_args, statement->named_param_map, this); @@ -662,7 +680,23 @@ unique_ptr ClientContext::SubmitStatementInternal(ClientContextLock } // execute the prepared statement CheckIfPreparedStatementIsExecutable(*prepared); - return SubmitPreparedStatementInternal(lock, std::move(prepared), parameters, retention); + return SubmitPreparedStatementInternal(lock, std::move(prepared), parameters); +} + +unique_ptr ClientContext::ExpandSingleStatementInternal(ClientContextLock &lock, + unique_ptr statement, + const char *refusal) { + auto expanded = PreprocessStatementInternal(lock, std::move(statement)); + if (expanded.statements.size() != 1) { + throw InvalidInputException(refusal); + } + return std::move(expanded.statements[0]); +} + +PreprocessedStatement ClientContext::PreprocessStatementInternal(ClientContextLock &lock, + unique_ptr statement) { + StatementPreprocessor preprocessor(*this); + return preprocessor.PreprocessStatement(lock, std::move(statement)); } unique_ptr ClientContext::RunStatementInternal(ClientContextLock &lock, unique_ptr statement, @@ -683,7 +717,8 @@ static bool HasBoundParameterValues(const SQLStatement &statement) { } unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, unique_ptr statement, - const QueryParameters ¶meters, Retention retention) { + const QueryParameters ¶meters, bool verify, + Retention retention) { // CONNECT chokepoint: when connected, non-control SQL is rewritten in place and falls through to // the normal pipeline. No recursion — the rewrite goes through SubmitStatementInternal, not back here. if (is_connected) { @@ -717,11 +752,37 @@ unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, // statement is now SELECT * FROM ; fall through. } } - // the statement is moved into SubmitStatementInternal - keep the source text for error reporting + // the statement is moved into the query - keep the source text for error reporting auto query = statement->query; - unique_ptr result; + const auto statement_type = statement->type; + const bool has_parameters = + !statement->named_param_map.empty() || (parameters.statement_args && !parameters.statement_args->empty()); + PreprocessedStatement preprocessed; try { - BeginQueryInternal(lock, *statement); + preprocessed = PreprocessStatementInternal(lock, std::move(statement)); + auto &statements = preprocessed.statements; + if (statements.size() > 1 && has_parameters) { + throw InvalidInputException(EXPANDING_PARAMETERS_ERROR); + } + if (verify) { + // A later statement binds only once the earlier ones ran, so a statement that expands is verified without + // running copies of it + for (auto &internal_statement : statements) { + StatementVerification(lock, internal_statement, parameters, statements.size() == 1); + } + } + } catch (std::exception &ex) { + // preserve extra error data (like query location) + return ErrorResult(ErrorData(ex), query); + } + if (preprocessed.statements.empty()) { + StatementProperties properties; + properties.return_type = StatementReturnType::NOTHING; + return EmptyResultInternal(statement_type, std::move(properties), {}, {}, parameters.format); + } + const bool expands = preprocessed.statements.size() > 1; + try { + BeginQueryInternal(lock, std::move(preprocessed), parameters, retention); } catch (std::exception &ex) { ErrorData error(ex); if (Exception::InvalidatesDatabase(error.Type())) { @@ -732,12 +793,26 @@ unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, return ErrorResult(std::move(error), query); } + unique_ptr handle; + unique_ptr result; bool invalidate_query = true; try { - result = SubmitStatementInternal(lock, std::move(statement), parameters, retention); + if (expands) { + handle = + make_uniq(shared_from_this(), statement_type, parameters.format, GetClientProperties()); + active_query->handle = handle.get(); + active_query->SetOpenResult(*handle); + } + result = SubmitStatementInternal(lock, active_query->TakeNextStatement(), parameters); + if (!result && ClientConfig::GetConfig(*this).get_result_collector) { + result = RunToResultStatementInternal(lock, *handle); + } + if (!result) { + result = std::move(handle); + } } catch (std::exception &ex) { ErrorData error(ex); - if (!ErrorInvalidatesTransaction(error.Type())) { + if (!ErrorInvalidatesQueryTransaction(error.Type())) { // standard exceptions do not invalidate the current transaction invalidate_query = false; } else if (Exception::InvalidatesDatabase(error.Type())) { @@ -748,6 +823,10 @@ unique_ptr ClientContext::SubmitStatement(ClientContextLock &lock, // other types of exceptions do invalidate the current transaction result = ErrorResult(std::move(error), query); } + if (handle) { + // A handle that is not handed out is released without the context lock its Close would take + handle->context.reset(); + } if (result->HasError()) { // query failed: abort now, unless a delegated collector's execution already ended it if (active_query) { @@ -847,20 +926,18 @@ unique_ptr ClientContext::Query(const string &query, QueryParameter optional_ptr last_result; bool last_had_result = false; while (has_current) { - // Get + preprocess the next engine-facing statement, reusing the lock we already hold. PRAGMA - // reparse / MULTI_STATEMENT unpacking happen inside GetStatementForExecutionWithLock, which sees the - // transaction state left by the previously executed statement. A peel can preprocess to - // nothing, in which case statement is null and there is nothing to execute. + // The next user statement. Its submission preprocesses it, seeing the transaction state the previous + // statement left, and runs what it expands into as one query unique_ptr statement; try { - statement = iterator.GetStatementForExecutionWithLock(*lock); + statement = iterator.GetStatementForExecution(); } catch (const std::exception &ex) { return ErrorResult(ErrorData(ex), query); } // Look ahead WITHOUT parsing: HasMore() only walks the token cursor, so it never parses (and // never throws) the next statement here. The next statement is parsed later, in this loop's - // next GetStatementForExecutionWithLock — after the current statement has executed. This lets a statement + // next GetStatementForExecution, after the current statement has executed. This lets a statement // register grammar (e.g. LOAD an extension) that a following statement then uses. bool has_next = iterator.HasMore(); @@ -873,7 +950,6 @@ unique_ptr ClientContext::Query(const string &query, QueryParameter // consumer's choice auto current_result = SubmitInternal(*lock, std::move(statement), query_parameters, true, Retention::RETAINED_AT_SUBMISSION); - auto has_result = current_result->GetStatementProperties().return_type == StatementReturnType::QUERY_RESULT; if (!current_result->HasError()) { current_result = CompleteInternal(*lock, std::move(current_result)); } @@ -886,7 +962,8 @@ unique_ptr ClientContext::Query(const string &query, QueryParameter interrupt_state = ClientInterruptState::NOT_INTERRUPTED; return current_result; } - // now append the result to the list of results + // now append the result to the list of results; a completed result knows its metadata + auto has_result = current_result->GetStatementProperties().return_type == StatementReturnType::QUERY_RESULT; if (!last_result || !last_had_result) { // first result of the query result = std::move(current_result); @@ -920,6 +997,7 @@ unique_ptr ClientContext::Submit(const string &query, const QueryPa return refusal; } + // The submission preprocesses the statement, which may expand into several that run as one query auto statements = ParseStatementsInternal(*lock, query); if (statements.empty()) { throw InvalidInputException("No statement to prepare!"); @@ -999,15 +1077,7 @@ unique_ptr ClientContext::SubmitInternalStatement(unique_ptr ClientContext::SubmitInternal(ClientContextLock &lock, unique_ptr statement, const QueryParameters ¶meters, bool verify, Retention retention) { - if (verify) { - try { - StatementVerification(lock, statement, parameters); - } catch (std::exception &ex) { - // preserve extra error data (like query location) - return ErrorResult(ErrorData(ex), statement->query); - } - } - return SubmitStatement(lock, std::move(statement), parameters, retention); + return SubmitStatement(lock, std::move(statement), parameters, verify, retention); } void ClientContext::Interrupt() { @@ -1037,11 +1107,25 @@ void ClientContext::InterruptCheck() const { } // Only check timeout every N calls to avoid expensive steady_clock::now() syscall if (query_deadline.IsValid() && ++timeout_check_counter % TIMEOUT_CHECK_INTERVAL == 0) { - auto now = NumericCast(duration_cast(steady_clock::now().time_since_epoch()).count()); - if (now >= query_deadline.GetIndex()) { - throw InterruptException("Query exceeded maximum execution time"); - } + BlockingInterruptCheck(); + } +} + +void ClientContext::BlockingInterruptCheck() const { + if (IsInterrupted()) { + throw InterruptException(); + } + if (QueryDeadlinePassed()) { + throw InterruptException(QUERY_DEADLINE_ERROR); + } +} + +bool ClientContext::QueryDeadlinePassed() const { + if (!query_deadline.IsValid()) { + return false; } + auto now = NumericCast(duration_cast(steady_clock::now().time_since_epoch()).count()); + return now >= query_deadline.GetIndex(); } void ClientContext::CancelTransaction() { @@ -1273,6 +1357,7 @@ unordered_set ClientContext::GetTableNames(const string &query, const bo if (statements.size() != 1) { throw InvalidInputException("Expected a single statement"); } + statements[0] = ExpandSingleStatementInternal(*lock, std::move(statements[0]), "Expected a single statement"); unordered_set result; RunFunctionInTransactionInternal(*lock, [&]() { diff --git a/src/duckdb/src/main/client_context_execution.cpp b/src/duckdb/src/main/client_context_execution.cpp index 8e2af94c1..2b204776e 100644 --- a/src/duckdb/src/main/client_context_execution.cpp +++ b/src/duckdb/src/main/client_context_execution.cpp @@ -30,22 +30,25 @@ namespace duckdb { -void ClientContext::BeginQueryInternal(ClientContextLock &lock, const SQLStatement &statement) { +void ClientContext::BeginQueryInternal(ClientContextLock &lock, PreprocessedStatement statements, + const QueryParameters ¶meters, Retention retention) { // check if we are on AutoCommit. In this case we should start a transaction D_ASSERT(!active_query); + D_ASSERT(!statements.statements.empty()); auto &db_inst = DatabaseInstance::GetDatabase(*this); if (ValidChecker::IsInvalidated(db_inst)) { throw ErrorManager::InvalidatedDatabase(*this, ValidChecker::InvalidatedMessage(db_inst)); } - active_query = make_uniq(); + auto query = statements.statements[0]->query; + active_query = make_uniq(std::move(statements)); + active_query->format = parameters.format; + if (retention == Retention::RETAINED_AT_SUBMISSION) { + active_query->retention = ResultLifetime::RETAINED; + } if (transaction.IsAutoCommit() && !transaction.HasActiveTransaction()) { transaction.BeginTransaction(); } - - transaction.SetActiveQuery(db->GetDatabaseManager().GetNewQueryNumber()); - auto &query = statement.query; - LogQueryInternal(lock, query); - active_query->query = query; + BeginStatementInternal(lock, query); query_progress.Initialize(); // Set query deadline if max_execution_time is configured @@ -66,7 +69,16 @@ void ClientContext::BeginQueryInternal(ClientContextLock &lock, const SQLStateme for (auto &state : registered_state->States()) { state->QueryBegin(*this); } + RefreshLoggerInternal(); +} + +void ClientContext::BeginStatementInternal(ClientContextLock &lock, const string &query) { + transaction.SetActiveQuery(db->GetDatabaseManager().GetNewQueryNumber()); + LogQueryInternal(lock, query); + active_query->query = query; +} +void ClientContext::RefreshLoggerInternal() { // Flush the old logger. logger->Flush(); @@ -76,7 +88,7 @@ void ClientContext::BeginQueryInternal(ClientContextLock &lock, const SQLStateme logging_context.transaction_id = transaction.ActiveTransaction().global_transaction_id; logging_context.query_id = transaction.GetActiveQuery(); logger = db->GetLogManager().CreateLogger(logging_context, true); - DUCKDB_LOG(*this, QueryLogType, query); + DUCKDB_LOG(*this, QueryLogType, active_query->query); } ErrorData ClientContext::EndQueryInternal(ClientContextLock &lock, bool success, bool invalidate_transaction, @@ -171,8 +183,10 @@ void ClientContext::CleanupInternal(ClientContextLock &lock, BaseQueryResult *re void ClientContext::AbortInternal(ClientContextLock &lock) { D_ASSERT(active_query); auto &prepared = active_query->prepared; - // No savepoint isolates a single statement, so partial writes can only be dropped with the whole transaction - const bool may_write = !prepared || !prepared->properties.IsReadOnly(); + // No savepoint isolates a single statement, so partial writes can only be dropped with the whole transaction. A + // statement that expands into several and is abandoned with statements left would leave them unapplied, and those + // are not bound, so they count as writing + const bool may_write = !prepared || !prepared->properties.IsReadOnly() || active_query->HasNextStatement(); CleanupInternal(lock, nullptr, may_write); } @@ -199,10 +213,8 @@ void BindPreparedStatementParameters(ClientContext &context, PreparedStatementDa statement.Bind(context, owned_values); } -unique_ptr -ClientContext::SubmitPreparedStatementInternal(ClientContextLock &lock, - shared_ptr statement_data_p, - const QueryParameters ¶meters, Retention retention) { +unique_ptr ClientContext::SubmitPreparedStatementInternal( + ClientContextLock &lock, shared_ptr statement_data_p, const QueryParameters ¶meters) { D_ASSERT(active_query); auto &statement_data = *statement_data_p; BindPreparedStatementParameters(*this, statement_data, parameters); @@ -226,10 +238,19 @@ ClientContext::SubmitPreparedStatementInternal(ClientContextLock &lock, query_progress.Restart(); } + // The result statement of a query that expands into several is the one that returns rows; the others only run + const bool expands = active_query->ExpandsIntoSeveral(); + const bool result_statement = + !expands || statement_data.properties.return_type == StatementReturnType::QUERY_RESULT; + if (expands && result_statement && active_query->HasNextStatement()) { + throw NotImplementedException("A statement that expands into a row-returning statement followed by others " + "cannot be served as one result"); + } + // Decide how to get the result collector. get_result_collector_t get_collector = PhysicalResultCollector::GetResultCollector; auto &client_config = ClientConfig::GetConfig(*this); - if (client_config.get_result_collector) { + if (client_config.get_result_collector && result_statement) { get_collector = client_config.get_result_collector; } @@ -238,6 +259,10 @@ ClientContext::SubmitPreparedStatementInternal(ClientContextLock &lock, D_ASSERT(collector->type == PhysicalOperatorType::RESULT_COLLECTOR); // A custom hook can hand back the default sink, which is then served like any other query const bool delegating = collector->Cast().BuildsOwnResult(); + if (delegating && expands && !active_query->inside_submission) { + throw NotImplementedException("A result collector that builds its own result object can only serve a " + "statement that expands into several when it is set before the submission"); + } if (delegating && parameters.format) { if (!parameters.format->Is()) { // The collector builds its own result, which the format would never reach @@ -260,24 +285,48 @@ ClientContext::SubmitPreparedStatementInternal(ClientContextLock &lock, auto &sink = collector->Cast(); ResultFormatContext format_context {statement_data.types, statement_data.names, client_properties, sink.ordering}; + // The output of a statement the result does not carry is discarded, so it is kept in chunks + auto format = result_statement ? parameters.format : nullptr; if (sink.ordering == ResultOrdering::BATCH_INDEX_ORDERED) { buffer = make_shared_ptr(*this, ResultLifetime::UNDECIDED, std::move(format_context), - parameters.format); + std::move(format)); } else { buffer = make_shared_ptr(*this, ResultLifetime::UNDECIDED, std::move(format_context), - parameters.format); + std::move(format)); } - if (retention == Retention::RETAINED_AT_SUBMISSION) { - // Settled before execution starts, so no producer ever parks for the decision + // Settled before execution starts, so no producer parks for a decision that is already made, or that no + // consumer makes for a statement whose output is discarded + if (!result_statement) { buffer->Decide(ResultLifetime::RETAINED); + } else if (active_query->retention != ResultLifetime::UNDECIDED) { + buffer->Decide(active_query->retention); } sink.SetResultBuffer(buffer); } executor.SetResultBuffer(buffer); + if (expands) { + // Recorded before Initialize schedules the statement's tasks, which may write before it returns + active_query->RecordDatabases(statement_data.properties); + } executor.Initialize(std::move(collector)); D_ASSERT(executor.GetTypes() == statement_data.types); + if (expands) { + if (!result_statement) { + return nullptr; + } + active_query->result_statement_started = true; + auto &handle = *active_query->handle; + if (!delegating) { + handle.AdoptResultStatement(active_query->QueryProperties(statement_data.properties), statement_data, + std::move(buffer)); + return nullptr; + } + // The handle is released first, so destroying it never takes the context lock held here + handle.context.reset(); + return CompleteDelegatedInternal(lock, handle); + } D_ASSERT(!active_query->HasOpenResult()); auto result = make_uniq(shared_from_this(), statement_data, std::move(types), @@ -338,6 +387,9 @@ QueryResultState ClientContext::ExecuteTaskInternal(ClientContextLock &lock, Bas } auto state = active_query->executor->ExecuteTask(); UpdateProgressInternal(state); + if (state == QueryResultState::FINISHED && active_query->HasNextStatement()) { + return StartNextStatementInternal(lock); + } return state; } catch (std::exception &ex) { return FailQueryInternal(lock, result, ErrorData(ex)); @@ -352,6 +404,9 @@ QueryResultState ClientContext::PollInternal(ClientContextLock &lock, BaseQueryR try { auto state = active_query->executor->Poll(); UpdateProgressInternal(state); + if (state == QueryResultState::FINISHED && active_query->HasNextStatement()) { + return StartNextStatementInternal(lock); + } return state; } catch (std::exception &ex) { return FailQueryInternal(lock, result, ErrorData(ex)); @@ -372,13 +427,14 @@ void ClientContext::UpdateProgressInternal(QueryResultState state) { QueryResultState ClientContext::FailQueryInternal(ClientContextLock &lock, BaseQueryResult &result, ErrorData error) { bool invalidate_transaction = true; if (error.Type() == ExceptionType::INTERRUPT) { - auto &executor = *active_query->executor; - if (executor.HasError()) { + // No executor when the failure comes between two statements of a query that expands into several + auto &executor = active_query->executor; + if (executor && executor->HasError()) { // Interrupted by an exception caused in a worker thread - error = executor.GetError(); - invalidate_transaction = ErrorInvalidatesTransaction(error.Type()); + error = executor->GetError(); + invalidate_transaction = ErrorInvalidatesQueryTransaction(error.Type()); } - } else if (!ErrorInvalidatesTransaction(error.Type())) { + } else if (!ErrorInvalidatesQueryTransaction(error.Type())) { invalidate_transaction = false; } else if (Exception::InvalidatesDatabase(error.Type()) || error.Type() == ExceptionType::INTERNAL) { // fatal exceptions invalidate the entire database @@ -411,10 +467,121 @@ bool ClientContext::IsActiveResult(ClientContextLock &lock, BaseQueryResult &res } bool ClientContext::ExecutionIsFinished() { - if (!active_query || !active_query->executor) { + lock_guard guard(context_lock); + if (!active_query || !active_query->executor || active_query->HasNextStatement()) { return false; } return active_query->executor->ExecutionIsFinished(); } +QueryResultState ClientContext::StartNextStatementInternal(ClientContextLock &lock) { + QueryResultState state; + do { + auto produced = AdvanceStatementInternal(lock); + // Only a submission meets a collector that builds its own result object + D_ASSERT(!produced); + state = active_query->executor->Poll(); + } while (state == QueryResultState::FINISHED && active_query->HasNextStatement()); + return state; +} + +unique_ptr ClientContext::AdvanceStatementInternal(ClientContextLock &lock) { + // Checked before the finished statement is torn down, so a failure is reported against it + if (IsInterrupted()) { + throw InterruptException(); + } + if (QueryDeadlinePassed()) { + throw InterruptException(QUERY_DEADLINE_ERROR); + } + EndStatementInternal(lock); + auto statement = active_query->TakeNextStatement(); + BeginStatementInternal(lock, statement->query); + RefreshLoggerInternal(); + QueryParameters parameters(active_query->format); + auto produced = SubmitStatementInternal(lock, std::move(statement), parameters); + // A statement that could not start fails the query; the result object of a collector already ended it + if (produced && produced->HasError() && active_query && !active_query->IsOpenResult(*produced)) { + produced->GetErrorObject().Throw(); + } + return produced; +} + +unique_ptr ClientContext::RunToResultStatementInternal(ClientContextLock &lock, QueryResult &handle) { + active_query->inside_submission = true; + try { + // A collector that builds its own result object and fails ends the query + while (active_query && !active_query->result_statement_started && active_query->HasNextStatement()) { + auto state = active_query->executor->ExecuteTask(); + UpdateProgressInternal(state); + if (state == QueryResultState::BLOCKED || state == QueryResultState::NO_TASKS_AVAILABLE) { + WaitForTask(lock, handle); + } else if (state == QueryResultState::FINISHED) { + auto produced = AdvanceStatementInternal(lock); + if (produced) { + return produced; + } + } + } + } catch (std::exception &ex) { + if (active_query) { + FailQueryInternal(lock, handle, ErrorData(ex)); + } else { + handle.SetError(ErrorData(ex)); + } + return nullptr; + } + if (active_query) { + active_query->inside_submission = false; + } + return nullptr; +} + +void ClientContext::EndStatementInternal(ClientContextLock &lock) { + active_query->executor->CancelTasks(); + active_query->executor.reset(); + active_query->progress_bar.reset(); + // The plan outlives the executor + active_query->prepared.reset(); + transaction.ResetActiveQuery(); + client_data->profiler->EndQuery(); +} + +unique_ptr ClientContext::TakeFinishedResultInternal(ClientContextLock &lock) { + if (!active_query->ExpandsIntoSeveral() || active_query->result_statement_started) { + return GetExecutor().GetResult(); + } + // No statement returned rows: the result reports nothing, the way a single statement that returns nothing does + StatementProperties properties; + properties.return_type = StatementReturnType::NOTHING; + return EmptyResultInternal(active_query->handle->GetStatementType(), + active_query->QueryProperties(std::move(properties)), {LogicalType::BOOLEAN}, + {Identifier("Success")}, active_query->format); +} + +unique_ptr ClientContext::EmptyResultInternal(StatementType statement_type, StatementProperties properties, + vector types, vector names, + shared_ptr format) { + if (!format) { + format = ChunkFormat::InMemory(); + } + auto client_properties = GetClientProperties(); + ResultFormatContext format_context {types, names, client_properties, ResultOrdering::UNORDERED}; + shared_ptr format_state = format->InitGlobal(format_context); + auto collection = format->CreateCollection(*this, *format_state, format_context); + // A retained collection is read only once finalized + collection->Finalize(); + return make_uniq(statement_type, std::move(properties), std::move(types), std::move(names), + std::move(collection), std::move(format), std::move(format_state), + std::move(client_properties)); +} + +ResultLifetime ClientContext::DecideResultLifetimeInternal(ClientContextLock &lock, ResultLifetime decision) { + D_ASSERT(active_query && !active_query->result_statement_started); + D_ASSERT(decision != ResultLifetime::UNDECIDED); + if (active_query->retention == ResultLifetime::UNDECIDED) { + active_query->retention = decision; + } + return active_query->retention; +} + } // namespace duckdb diff --git a/src/duckdb/src/main/client_verify.cpp b/src/duckdb/src/main/client_verify.cpp index 018b210d0..5242d836f 100644 --- a/src/duckdb/src/main/client_verify.cpp +++ b/src/duckdb/src/main/client_verify.cpp @@ -74,7 +74,7 @@ static void ReplaceStatement(unique_ptr &statement, unique_ptr &statement, - QueryParameters query_parameters) { + QueryParameters query_parameters, bool allow_nested_statements) { auto verification = Settings::Get(*this); if (verification == DebugStatementVerification::COPY_STATEMENT) { if (statement->type == StatementType::LOGICAL_PLAN_STATEMENT) { @@ -178,7 +178,7 @@ void ClientContext::StatementVerification(ClientContextLock &lock, unique_ptrtype != StatementType::SELECT_STATEMENT) { + if (!allow_nested_statements || statement->type != StatementType::SELECT_STATEMENT) { // only supported for SELECT (for now?) return; } @@ -241,7 +241,7 @@ void ClientContext::StatementVerification(ClientContextLock &lock, unique_ptrtype != StatementType::SELECT_STATEMENT) { + if (!allow_nested_statements || (export_sql && statement->type != StatementType::SELECT_STATEMENT)) { return; } if (statement->type == StatementType::EXPLAIN_STATEMENT) { diff --git a/src/duckdb/src/main/config.cpp b/src/duckdb/src/main/config.cpp index 66f3ed20b..83c4bf9ba 100644 --- a/src/duckdb/src/main/config.cpp +++ b/src/duckdb/src/main/config.cpp @@ -922,8 +922,8 @@ const ExtensionCallbackManager &DBConfig::GetCallbackManager() const { return *callback_manager; } -string DBConfig::SanitizeAllowedPath(const string &path_p) const { - auto result = file_system->CanonicalizePath(path_p); +string DBConfig::SanitizeAllowedPath(const string &path_p, optional_ptr opener) const { + auto result = file_system->CanonicalizePath(FileSystem::ExpandPath(path_p, opener)); // allowed_directories/allowed_path always uses forward slashes regardless of the OS auto path_sep = file_system->PathSeparator(path_p); if (path_sep != "/") { @@ -976,6 +976,21 @@ void DBConfig::AddAllowedDirectory(const string &path) { options.allowed_directories.insert(allowed_directory); } +bool DBConfig::IsOptimizerDisabled(OptimizerType type) const { + lock_guard guard(disabled_optimizers_lock); + return options.disabled_optimizers.find(type) != options.disabled_optimizers.end(); +} + +set DBConfig::GetDisabledOptimizers() const { + lock_guard guard(disabled_optimizers_lock); + return options.disabled_optimizers; +} + +void DBConfig::SetDisabledOptimizers(set disabled_optimizers) { + lock_guard guard(disabled_optimizers_lock); + options.disabled_optimizers = std::move(disabled_optimizers); +} + void DBConfig::AddAllowedPath(const string &path) { auto allowed_path = SanitizeAllowedPath(path); lock_guard guard(allowed_paths_lock); @@ -999,12 +1014,12 @@ vector DBConfig::GetAllowedPaths() const { return vector(options.allowed_paths.begin(), options.allowed_paths.end()); } -bool DBConfig::CanAccessFile(const string &input_path, FileType type) { +bool DBConfig::CanAccessFile(const string &input_path, FileType type, optional_ptr opener) { if (Settings::Get(*this)) { // all external access is allowed return true; } - string path = SanitizeAllowedPath(input_path); + string path = SanitizeAllowedPath(input_path, opener); lock_guard guard(allowed_paths_lock); if (options.allowed_paths.count(path) > 0) { diff --git a/src/duckdb/src/main/connection.cpp b/src/duckdb/src/main/connection.cpp index 9ef64e0d3..9b70cdb3e 100644 --- a/src/duckdb/src/main/connection.cpp +++ b/src/duckdb/src/main/connection.cpp @@ -176,16 +176,12 @@ unique_ptr Connection::TableInfo(const Identifier &table_name) } vector> Connection::ExtractStatements(const string &query) { - // Eager convenience over the lazy ClientContext::ExtractStatements iterator: drain the - // engine-facing statements into a vector. auto &client_context = *context; vector> result; try { auto iterator = client_context.IterateStatements(query); while (iterator.Peek()) { - if (auto statement = iterator.GetStatement()) { - result.push_back(std::move(statement)); - } + result.push_back(iterator.GetStatement()); } } catch (std::exception &ex) { ErrorData error(ex); diff --git a/src/duckdb/src/main/extension/extension_helper.cpp b/src/duckdb/src/main/extension/extension_helper.cpp index 7ab8b9303..2835029e8 100644 --- a/src/duckdb/src/main/extension/extension_helper.cpp +++ b/src/duckdb/src/main/extension/extension_helper.cpp @@ -237,6 +237,9 @@ bool ExtensionHelper::TryAutoLoadAvailableExtension(DatabaseInstance &instance, return true; } try { + if (!Settings::Get(instance)) { + return false; + } auto &fs = FileSystem::GetFileSystem(instance); ExtensionHelper::LoadExternalExtension(instance, fs, AutoLoadOptions(extension_name)); return true; diff --git a/src/duckdb/src/main/extension_manager.cpp b/src/duckdb/src/main/extension_manager.cpp index c888a2405..ee186a7fa 100644 --- a/src/duckdb/src/main/extension_manager.cpp +++ b/src/duckdb/src/main/extension_manager.cpp @@ -56,7 +56,7 @@ void ExtensionManager::RemoveExtensionInfo(const string &name) { loaded_extensions_info.erase(Identifier(name)); } -optional_ptr ExtensionManager::GetExtensionInfo(const string &name) { +shared_ptr ExtensionManager::GetExtensionInfo(const string &name) { auto extension_name = ExtensionHelper::GetExtensionName(name); lock_guard guard(lock); @@ -64,7 +64,7 @@ optional_ptr ExtensionManager::GetExtensionInfo(const string &nam if (entry == loaded_extensions_info.end()) { return nullptr; } - return entry->second.get(); + return entry->second; } vector ExtensionManager::GetExtensions() { @@ -101,7 +101,7 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO extension_name = ExtensionHelper::GetExtensionName(options.extension_name); string original_extension_name = extension_name; - optional_ptr info; + shared_ptr info; auto entry = loaded_extensions_info.end(); if (options.alias.empty()) { @@ -113,9 +113,9 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO // check if the extension is not already registered under an alias // we don't have an entry yet - create one - auto extension_info = make_uniq(); + auto extension_info = make_shared_ptr(); extension_info->orig_ext_name = original_extension_name; - info = extension_info.get(); + info = extension_info; if (!options.alias.empty()) { // if aliasing is used, we need to register the alias instead of extension name @@ -142,13 +142,13 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO return nullptr; } // it is not loaded yet - try to load it - info = entry->second.get(); + info = entry->second; } extension_list_lock.unlock(); // we have an extension and we want to try to load it - instantiate the load // we instantiate the ExtensionActiveLoad which also grabs the lock for loading the specific extension - auto result = make_uniq(db, *info, Identifier(original_extension_name), options.alias); + auto result = make_uniq(db, info, Identifier(original_extension_name), options.alias); // we now have a lock for loading the extension // HOWEVER - another thread might have finished loading in the meantime - double check to avoid a double load diff --git a/src/duckdb/src/main/http/http_retry_budget.cpp b/src/duckdb/src/main/http/http_retry_budget.cpp index 3fb00e874..7ba67f2a8 100644 --- a/src/duckdb/src/main/http/http_retry_budget.cpp +++ b/src/duckdb/src/main/http/http_retry_budget.cpp @@ -3,11 +3,14 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/random_engine.hpp" +#include "duckdb/common/string_util.hpp" +#include "duckdb/common/types/timestamp.hpp" +#include "duckdb/function/scalar/strftime_format.hpp" #include "duckdb/main/http/http_util.hpp" +#include #ifndef DUCKDB_NO_THREADS #include -#include #include #endif @@ -17,22 +20,226 @@ HTTPRetryDecision HTTPRetryDecision::Finish() { return HTTPRetryDecision(Type::FINISH); } +HTTPRetryDecision HTTPRetryDecision::Success() { + return HTTPRetryDecision(Type::SUCCESS); +} + +HTTPRetryDecision HTTPRetryDecision::Failure() { + return HTTPRetryDecision(Type::FAILURE); +} + +HTTPRetryDecision HTTPRetryDecision::Correction() { + return HTTPRetryDecision(Type::CORRECTION); +} + HTTPRetryDecision HTTPRetryDecision::Retry() { return HTTPRetryDecision(Type::RETRY); } +HTTPRetryDecision HTTPRetryDecision::Transient() { + return HTTPRetryDecision(Type::TRANSIENT); +} + HTTPRetryDecision HTTPRetryDecision::Throttled(const string &retry_after) { return HTTPRetryDecision(Type::THROTTLED, retry_after); } -HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms) - : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff) { +void HTTPRetryQuota::Touch(int64_t now_ms) { + if (now_ms - last_used_ms >= IDLE_RESET_MS) { + capacity = INITIAL_CAPACITY; + } + last_used_ms = now_ms; +} + +bool HTTPRetryQuota::TryAcquire(int64_t cost, int64_t now_ms) { + lock_guard guard(lock); + Touch(now_ms); + if (cost > capacity) { + return false; + } + capacity -= cost; + return true; +} + +void HTTPRetryQuota::Release(int64_t amount, int64_t now_ms) { + lock_guard guard(lock); + Touch(now_ms); + capacity = MinValue(capacity + amount, INITIAL_CAPACITY); +} + +int64_t HTTPRetryQuota::Available() const { + lock_guard guard(lock); + return capacity; +} + +int64_t HTTPRetryQuota::IdleMs(int64_t now_ms) const { + lock_guard guard(lock); + return now_ms - last_used_ms; +} + +HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms, HTTPRetryOutcomeOwner owner_p) + : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff), + owner(owner_p) { +#ifdef DUCKDB_NO_THREADS + // Without threads there is no back-off sleep, so the schedule settings go unused. + (void)retry_wait_ms; + (void)retry_backoff; +#endif } uint64_t HTTPRetryBudget::ThrottledRetries() const { return throttled_retries; } +uint64_t HTTPRetryBudget::RetriesUsed() const { + return retries_used; +} + +uint64_t HTTPRetryBudget::BeginAttempt() { + return ++attempts; +} + +uint64_t HTTPRetryBudget::ThrottleExtraRetries() const { +#ifndef DUCKDB_NO_THREADS + // http_retries=0 switches retries off entirely, throttled or not. + return retries > 0 ? THROTTLE_EXTRA_RETRIES : 0; +#else + // Without threads we cannot sleep between retries, so do not add zero-delay retries. + return 0; +#endif +} + +uint64_t HTTPRetryBudget::Corrections() const { + // The first send and every admitted retry are accounted for; any other send was a corrected re-send. + const auto accounted = 1 + retries_used; + return attempts > accounted ? attempts - accounted : 0; +} + +uint64_t HTTPRetryBudget::MaxAttempts() const { + return 1 + retries + ThrottleExtraRetries() + Corrections(); +} + +bool HTTPRetryBudget::InRun() const { + return run_depth > 0; +} + +bool HTTPRetryBudget::QuotaExhausted() const { + return quota_exhausted; +} + +bool HTTPRetryBudget::CorrectionsExhausted() const { + return corrections_exhausted; +} + +void HTTPRetryBudget::BindQuota(shared_ptr quota_p) { + quota = std::move(quota_p); +} + +HTTPRetryOutcomeOwner HTTPRetryBudget::Owner() const { + return owner; +} + +void HTTPRetryBudget::RequireCoordinator(const char *what) const { + if (owner != HTTPRetryOutcomeOwner::COORDINATOR) { + throw InternalException( + "HTTP retry budget: %s is the coordinator's decision, but this operation's outcome is " + "owned by the HTTP status; construct the budget with HTTPRetryOutcomeOwner::COORDINATOR", + what); + } +} + +void HTTPRetryBudget::ReportSuccess() { + RequireCoordinator("ReportSuccess"); + Settle(true); +} + +void HTTPRetryBudget::ReportFailure() { + RequireCoordinator("ReportFailure"); + Settle(false); +} + +void HTTPRetryBudget::RecordStatusOutcome(bool success) { + if (owner != HTTPRetryOutcomeOwner::STATUS) { + // The coordinator validates the response and reports; what the status says is never final here. + return; + } + status_outcome = success ? RecordedOutcome::SUCCESS : RecordedOutcome::FAILURE; + if (!InRun()) { + // Nobody else is going to judge this response. + Settle(success); + } +} + +void HTTPRetryBudget::Settle(bool success) { + status_outcome = RecordedOutcome::NONE; + if (outcome_settled) { + // Refunded or charged already: the operation has one outcome. + return; + } + outcome_settled = true; + if (success) { + if (last_retry_quota) { + last_retry_quota->Release(last_retry_cost); + } else if (quota) { + quota->Release(HTTPRetryQuota::NO_RETRY_REFUND); + } + } + ForgetLastRetry(); +} + +void HTTPRetryBudget::ForgetLastRetry() { + last_retry_quota.reset(); + last_retry_cost = 0; +} + +uint64_t HTTPRetryBudget::BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, + uint64_t random_value, bool throttled) { + D_ASSERT(retry > 0); + const auto exponent = static_cast(retry - 1); + // Cap in the double domain so the exponential cannot overflow the cast. + const auto base_ms = + MinValue(static_cast(retry_wait_ms) * pow(static_cast(retry_backoff), exponent), + static_cast(MAX_BACKOFF_MS)); + const auto base = static_cast(base_ms); + // Throttled retries never collapse onto the moment the server asked us to slow down. + const auto lower = throttled ? base / 2 : 0; + const auto jittered = lower + random_value % (base - lower + 1); + return MaxValue(jittered, MinValue(floor_ms, MAX_BACKOFF_MS)); +} + +static const StrpTimeFormat &RetryAfterDateFormat() { + // RFC 7231 IMF-fixdate, e.g. "Sun, 06 Nov 1994 08:49:37 GMT". + static const StrpTimeFormat format = []() { + StrpTimeFormat result; + auto error = StrTimeFormat::ParseFormatSpecifier("%a, %d %b %Y %H:%M:%S GMT", result); + D_ASSERT(error.empty()); + (void)error; + return result; + }(); + return format; +} + +bool HTTPRetryBudget::TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms) { + string value = retry_after; + StringUtil::Trim(value); + if (value.empty()) { + return false; + } + uint64_t seconds = 0; + if (TryCast::Operation(string_t(value), seconds)) { + delay_ms = seconds > MAX_BACKOFF_MS / 1000 ? MAX_BACKOFF_MS : seconds * 1000; + return true; + } + timestamp_t target; + if (!RetryAfterDateFormat().TryParseTimestamp(value.c_str(), value.size(), target)) { + return false; + } + const auto now_ms = Timestamp::GetEpochMs(Timestamp::GetCurrentTimestamp()); + const auto target_ms = Timestamp::GetEpochMs(target); + delay_ms = target_ms > now_ms ? static_cast(target_ms - now_ms) : 0; + return true; +} + void HTTPRetryBudget::Run(const std::function &attempt) { Run(attempt, {}); } @@ -42,60 +249,110 @@ void HTTPRetryBudget::Run(const std::function &attempt, if (!attempt) { throw InternalException("HTTP retry loop requires an attempt callback"); } + // A refusal belongs to the run that hit it, not to later requests sharing the budget. + quota_exhausted = false; + corrections_exhausted = false; + const bool outermost = !InRun(); + if (outermost) { + // A new operation leg: whatever an earlier leg settled is history. + status_outcome = RecordedOutcome::NONE; + outcome_settled = false; + } + struct RunDepthGuard { + explicit RunDepthGuard(uint64_t &depth_p) : depth(depth_p) { + depth++; + } + ~RunDepthGuard() { + depth--; + } + uint64_t &depth; + } depth_guard(run_depth); for (;;) { auto decision = attempt(); - if (decision.type == HTTPRetryDecision::Type::FINISH || !ConsumeAndWait(decision)) { + switch (decision.type) { + case HTTPRetryDecision::Type::SUCCESS: + // The coordinator validated the response: the explicit outcome is final wherever it is decided. + RequireCoordinator("a Success decision"); + Settle(true); return; - } - if (before_retry) { - before_retry(); + case HTTPRetryDecision::Type::FAILURE: + RequireCoordinator("a Failure decision"); + Settle(false); + return; + case HTTPRetryDecision::Type::FINISH: + if (outermost && status_outcome != RecordedOutcome::NONE) { + // A status-owned operation is over and nobody judges it: the outcome core recorded from the status + // is settled now. Without one the caller records the outcome itself after the run. A + // coordinator-owned budget never records a status and waits for the coordinator's report. + Settle(status_outcome == RecordedOutcome::SUCCESS); + } + return; + case HTTPRetryDecision::Type::CORRECTION: + if (!AdmitCorrection()) { + return; + } + continue; + default: + if (!ConsumeAndWait(decision)) { + return; + } + if (before_retry) { + before_retry(); + } } } } +bool HTTPRetryBudget::AdmitCorrection() { + // The corrected request is a new send of the same operation: the last response does not count. + status_outcome = RecordedOutcome::NONE; + if (corrections_admitted >= MAX_CORRECTIONS) { + // The operation ends with the response it has, and its retries did not get it through. + corrections_exhausted = true; + ForgetLastRetry(); + return false; + } + corrections_admitted++; + return true; +} + bool HTTPRetryBudget::ConsumeAndWait(const HTTPRetryDecision &decision) { D_ASSERT(decision.type != HTTPRetryDecision::Type::FINISH); const bool throttled = decision.type == HTTPRetryDecision::Type::THROTTLED; -#ifndef DUCKDB_NO_THREADS - static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; -#else - // Without threads we cannot sleep between retries, so do not add zero-delay retries. - static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 0; -#endif - const auto extra_retries = throttled ? THROTTLE_EXTRA_RETRIES : 0; + const auto extra_retries = throttled ? ThrottleExtraRetries() : 0; + // Asking for a retry means the last attempt did not get the operation through, whatever a nested loop recorded. + status_outcome = RecordedOutcome::NONE; if (retries_used >= retries && retries_used - retries >= extra_retries) { + // Nothing left to refund: the retries already spent did not get this request through. + ForgetLastRetry(); + return false; + } + const auto cost = decision.type == HTTPRetryDecision::Type::TRANSIENT ? HTTPRetryQuota::TRANSIENT_RETRY_COST + : HTTPRetryQuota::RETRY_COST; + if (quota && !quota->TryAcquire(cost)) { + quota_exhausted = true; + ForgetLastRetry(); return false; } + // The admitted retry is the one a later success refunds; its outcome is still open. + last_retry_quota = quota; + last_retry_cost = cost; + outcome_settled = false; retries_used++; if (throttled) { throttled_retries++; } #ifndef DUCKDB_NO_THREADS - if (retries_used > 1 || throttled) { - static constexpr uint64_t THROTTLE_MAX_BACKOFF_MS = 10000; - const auto backoff_exp = static_cast(throttled ? retries_used - 1 : retries_used - 2); - const auto backoff_ms = (double)retry_wait_ms * pow(retry_backoff, backoff_exp); - // Cap in the double domain to avoid overflow in the cast. - uint64_t sleep_amount = (uint64_t)MinValue(backoff_ms, (double)NumericLimits::Maximum()); - if (throttled) { - sleep_amount = MinValue(sleep_amount, THROTTLE_MAX_BACKOFF_MS); - if (!decision.retry_after.empty()) { - // Honor a numeric Retry-After (seconds), capped like the backoff. - uint64_t retry_after_s = 0; - if (TryCast::Operation(string_t(decision.retry_after), retry_after_s)) { - retry_after_s = MinValue(retry_after_s, THROTTLE_MAX_BACKOFF_MS / 1000); - sleep_amount = MaxValue(sleep_amount, retry_after_s * 1000); - } - } - // Subtractive jitter ([base/2, base]) de-synchronizes retry bursts while honoring the cap. - RandomEngine random; - sleep_amount -= random.NextRandomInteger64() % (sleep_amount / 2 + 1); - } - std::this_thread::sleep_for(std::chrono::milliseconds(sleep_amount)); + uint64_t floor_ms = 0; + if (throttled && !decision.retry_after.empty()) { + TryParseRetryAfterMs(decision.retry_after, floor_ms); + } + RandomEngine random; + const auto sleep_ms = + BackoffMs(retry_wait_ms, retry_backoff, retries_used, floor_ms, random.NextRandomInteger64(), throttled); + if (sleep_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } -#else - (void)retry_wait_ms; - (void)retry_backoff; #endif return true; } diff --git a/src/duckdb/src/main/http/http_util.cpp b/src/duckdb/src/main/http/http_util.cpp index cc7896ca2..435977999 100644 --- a/src/duckdb/src/main/http/http_util.cpp +++ b/src/duckdb/src/main/http/http_util.cpp @@ -123,17 +123,26 @@ bool HTTPResponse::ShouldRetry() const { // request errors are eligible for retry return true; } + // Core judges by status only; what a service says in the body is the provider's call (HTTPUtil::ShouldRetry). + return HTTPUtil::IsRetryableStatus(status); +} + +bool HTTPUtil::IsRetryableStatus(HTTPStatusCode status) { switch (status) { case HTTPStatusCode::RequestTimeout_408: case HTTPStatusCode::ImATeapot_418: case HTTPStatusCode::TooManyRequests_429: - case HTTPStatusCode::InternalServerError_500: - case HTTPStatusCode::BadGateway_502: - case HTTPStatusCode::ServiceUnavailable_503: - case HTTPStatusCode::GatewayTimeout_504: return true; - default: + // Server errors that describe the request or the server's abilities, not a passing condition. + case HTTPStatusCode::NotImplemented_501: + case HTTPStatusCode::HttpVersionNotSupported_505: + case HTTPStatusCode::NetworkAuthenticationRequired_511: return false; + default: { + // Any other server error is eligible, as in the AWS SDKs. + const auto code = static_cast(status); + return code >= 500 && code < 600; + } } } @@ -145,6 +154,32 @@ bool HTTPUtil::ShouldRetry(const BaseRequest &request, const HTTPResponse &respo return response.ShouldRetry(); } +void HTTPUtil::PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts) { + // default: requests are sent as-is +} + +shared_ptr HTTPUtil::GetRetryQuota(const string &proto_host_port) { + lock_guard guard(retry_quota_lock); + auto entry = retry_quotas.find(proto_host_port); + if (entry != retry_quotas.end()) { + return entry->second; + } + if (retry_quotas.size() >= MAX_RETRY_QUOTAS) { + // Forget the host that has been quiet for longest; an idle quota would have been reset anyway. + const auto now_ms = TimePoint::GetTickMs(); + auto victim = retry_quotas.begin(); + for (auto it = retry_quotas.begin(); it != retry_quotas.end(); ++it) { + if (it->second->IdleMs(now_ms) > victim->second->IdleMs(now_ms)) { + victim = it; + } + } + retry_quotas.erase(victim); + } + auto quota = make_shared_ptr(); + retry_quotas.emplace(proto_host_port, quota); + return quota; +} + unique_ptr HTTPUtil::Request(BaseRequest &request) { unique_ptr client; return Request(request, client); @@ -368,28 +403,61 @@ void HTTPUtil::DecomposeURL(const string &input, string &path_out, string &proto } } -// Retry eligible requests using an operation-local budget and the existing transport retry hook. +// Handlers turn error statuses into exceptions; rebuild the response they saw so it can be judged like a received one. +static unique_ptr ResponseFromError(const ErrorData &error) { + auto &extra_info = error.ExtraInfo(); + auto status_entry = extra_info.find("status_code"); + int32_t status_code = 0; + if (status_entry == extra_info.end() || + !TryCast::Operation(string_t(status_entry->second), status_code)) { + return nullptr; + } + auto response = make_uniq(HTTPUtil::ToStatusCode(status_code)); + for (auto &entry : extra_info) { + if (entry.first == "response_body") { + response->body = entry.second; + } else if (entry.first == "reason") { + response->reason = entry.second; + } else if (StringUtil::StartsWith(entry.first, "header_")) { + response->headers.Insert(entry.first.substr(7), entry.second); + } + } + return response; +} + +// Retry eligible requests using an operation-local budget, the host's retry quota and the transport retry hook. +// This loop is not the operation's coordinator: it judges statuses (with the provider's ShouldRetry), returns Finish() +// once it stops and records what the status says. A coordinator-owned budget shared by a provider loop +// (request.retry_budget) ignores that record and settles the quota from the provider's validated outcome; the local +// status-owned budget settles it from the status. duckdb::unique_ptr -HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, - const BaseRequest &request, const std::function &retry_cb) { +HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, + const std::function &retry_cb) { auto ¶ms = request.params; HTTPRetryBudget local_retry_budget(params); auto retry_budget = request.retry_budget; if (!retry_budget) { retry_budget = local_retry_budget; } + // Charge this request's host, even when a shared budget was bound to another host before. + retry_budget->BindQuota(params.http_util.GetRetryQuota(request.proto_host_port)); unique_ptr response; std::exception_ptr caught_e; + ErrorData caught_error; string exception_error; bool should_retry = false; retry_budget->Run( [&]() { response.reset(); caught_e = nullptr; + caught_error = ErrorData(); exception_error.clear(); - string caught_status; - string caught_retry_after; + unique_ptr caught_response; + // Attempts are numbered per operation, so a re-send after a correction counts as the attempt it is; the + // bound grows with such re-sends, which do not consume retries. + const auto attempt = retry_budget->BeginAttempt(); + params.http_util.PrepareAttempt(request, attempt, retry_budget->MaxAttempts()); try { response = on_request(); if (response) { @@ -398,66 +466,97 @@ HTTPUtil::RunRequestWithRetry(const std::function(void) } catch (IOException &e) { exception_error = e.what(); caught_e = std::current_exception(); + caught_error = ErrorData(e); } catch (HTTPException &e) { exception_error = e.what(); caught_e = std::current_exception(); - // Handlers turn error statuses into exceptions; recover the throttle information. - ErrorData error_data(e); - auto entry = error_data.ExtraInfo().find("status_code"); - if (entry != error_data.ExtraInfo().end()) { - caught_status = entry->second; - } - auto retry_entry = error_data.ExtraInfo().find("header_Retry-After"); - if (retry_entry != error_data.ExtraInfo().end()) { - caught_retry_after = retry_entry->second; - } + caught_error = ErrorData(e); + caught_response = ResponseFromError(caught_error); } - // Request errors and caught exceptions are eligible without a response status. - should_retry = !response || params.http_util.ShouldRetry(request, *response); + if (response) { + should_retry = params.http_util.ShouldRetry(request, *response); + } else if (caught_response) { + // A thrown status is judged like a received one, provider rules included. + should_retry = params.http_util.ShouldRetry(request, *caught_response); + } else { + // Without a status the transport failed and nothing is known about the server. + should_retry = true; + } if (!should_retry) { - auto response_code = static_cast(response->status); - response->success = (response_code >= 200 && response_code < 300) || - response->status == HTTPStatusCode::NotModified_304; + if (response) { + auto response_code = static_cast(response->status); + response->success = (response_code >= 200 && response_code < 300) || + response->status == HTTPStatusCode::NotModified_304; + } return HTTPRetryDecision::Finish(); } if (!HTTPUtil::IsIdempotent(request.type)) { return HTTPRetryDecision::Finish(); } - const bool throttled = (response && (response->status == HTTPStatusCode::TooManyRequests_429 || - response->status == HTTPStatusCode::ServiceUnavailable_503)) || - caught_status == "429" || caught_status == "503"; - if (throttled) { - if (response && response->headers.HasHeader("Retry-After")) { - caught_retry_after = response->headers.GetHeaderValue("Retry-After"); - } - return HTTPRetryDecision::Throttled(caught_retry_after); + if (!response && !caught_response) { + // The transport failed before any status arrived: nothing is known about the server. + return HTTPRetryDecision::Transient(); + } + auto &judged = response ? *response : *caught_response; + if (judged.HasRequestError()) { + return HTTPRetryDecision::Transient(); + } + if (judged.status == HTTPStatusCode::TooManyRequests_429 || + judged.status == HTTPStatusCode::ServiceUnavailable_503) { + return HTTPRetryDecision::Throttled( + judged.headers.HasHeader("Retry-After") ? judged.headers.GetHeaderValue("Retry-After") : string()); } return HTTPRetryDecision::Retry(); }, retry_cb); - if (!should_retry) { + // With a coordinator-owned budget the outcome is settled by the coordinator once it has validated the response: + // an error document behind HTTP 200 is a failure or a retry there and must not refund the retry it consumed. + if (!should_retry && response) { + retry_budget->RecordStatusOutcome(response->success); return response; } + // The request failed: it threw, could not be retried any further, or is not replay-safe. + retry_budget->RecordStatusOutcome(false); + string quota_note; + if (retry_budget->QuotaExhausted()) { + quota_note = StringUtil::Format(" (retry quota for '%s' exhausted: requests to this host keep failing)", + request.proto_host_port); + } if (request.try_request) { if (!response) { response = make_uniq(HTTPStatusCode::INVALID); - response->request_error = exception_error.empty() ? "Unknown error" : std::move(exception_error); + response->request_error = + (exception_error.empty() ? "Unknown error" : std::move(exception_error)) + quota_note; + } else if (!quota_note.empty()) { + // A received error keeps its status; the note goes to the field GetError() reports. + if (response->HasRequestError()) { + response->request_error += quota_note; + } else { + response->reason += quota_note; + } } response->success = false; return response; } auto method = EnumUtil::ToString(request.type); if (caught_e) { - std::rethrow_exception(caught_e); + if (quota_note.empty()) { + std::rethrow_exception(caught_e); + } + // Same type and extra info as the caught exception, with the quota note appended. + if (caught_error.Type() == ExceptionType::HTTP) { + throw HTTPException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); + } + throw IOException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); } if (response && !response->HasRequestError()) { - throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'", - static_cast(response->status), method, request.url); + throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'%s", + static_cast(response->status), method, request.url, quota_note); } string error = response ? response->GetError() : "Unknown error"; - throw IOException("%s error for HTTP %s to '%s'", error, method, request.url); + throw IOException("%s error for HTTP %s to '%s'%s", error, method, request.url, quota_note); } void HTTPParams::Initialize(optional_ptr opener) { diff --git a/src/duckdb/src/main/parser_context.cpp b/src/duckdb/src/main/parser_context.cpp index 98183a219..ea064a47c 100644 --- a/src/duckdb/src/main/parser_context.cpp +++ b/src/duckdb/src/main/parser_context.cpp @@ -5,6 +5,7 @@ #include "duckdb/main/client_config.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_callback_manager.hpp" +#include "duckdb/parser/grammar_extension.hpp" #include "duckdb/parser/peg/passthrough_dialect.hpp" namespace duckdb { @@ -39,15 +40,7 @@ shared_ptr CompiledGrammar::Get(ClientContext &context) { shared_ptr CompiledGrammar::Create(const ClientContext &context, const vector &active_extensions) { - vector> selected_extensions; - auto &callback_manager = ExtensionCallbackManager::Get(context); - for (auto &name : active_extensions) { - auto grammar_extension = callback_manager.FindGrammarExtension(name); - if (grammar_extension) { - selected_extensions.emplace_back(*grammar_extension); - } - } - return Create(selected_extensions); + return context.db->GetParserCache().GetMatcher(context, active_extensions); } ParserCache::ParserCache() { @@ -72,6 +65,39 @@ shared_ptr ParserCache::GetMatcher() { return matcher; } +shared_ptr ParserCache::GetMatcher(const ClientContext &context, + const vector &active_extensions) { + vector> selected_extensions; + vector extension_names; + auto &callback_manager = ExtensionCallbackManager::Get(context); + for (auto &name : active_extensions) { + auto grammar_extension = callback_manager.FindGrammarExtension(name); + if (grammar_extension) { + selected_extensions.emplace_back(*grammar_extension); + extension_names.push_back(grammar_extension->Name()); + } + } + // compile while holding the lock, so concurrent first uses of the same extensions only compile once + lock_guard lock(extension_mutex); + for (idx_t i = 0; i < extension_matchers.size(); i++) { + if (extension_matchers[i].first == extension_names) { + // move the entry to the back, which holds the most recently used grammar + auto entry = std::move(extension_matchers[i]); + extension_matchers.erase(extension_matchers.begin() + NumericCast(i)); + extension_matchers.push_back(std::move(entry)); + return extension_matchers.back().second; + } + } + // compile before evicting, so a grammar that fails to compile does not evict a valid one + auto new_matcher = CompiledGrammar::Create(selected_extensions); + if (extension_matchers.size() >= MAX_CACHED_EXTENSION_MATCHERS) { + // connections that still use the evicted grammar keep their own reference to it + extension_matchers.erase(extension_matchers.begin()); + } + extension_matchers.emplace_back(std::move(extension_names), std::move(new_matcher)); + return extension_matchers.back().second; +} + shared_ptr ParserCache::GetPassthroughMatcher(const ClientContext &context) { lock_guard lock(passthrough_mutex); if (!passthrough_dialect) { diff --git a/src/duckdb/src/main/prepared_statement.cpp b/src/duckdb/src/main/prepared_statement.cpp index 0211def31..4851d03c4 100644 --- a/src/duckdb/src/main/prepared_statement.cpp +++ b/src/duckdb/src/main/prepared_statement.cpp @@ -1,6 +1,7 @@ #include "duckdb/main/prepared_statement.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/main/client_context.hpp" +#include "duckdb/parser/sql_statement.hpp" #include "duckdb/parser/statement/execute_statement.hpp" namespace duckdb { @@ -11,10 +12,24 @@ PreparedStatement::PreparedStatement(const shared_ptr &context_p, D_ASSERT(!context.expired()); } +PreparedStatement::PreparedStatement(const shared_ptr &context_p, unique_ptr statement) + : context(context_p), query(statement->query), success(true), expanding_statement(std::move(statement)) { + D_ASSERT(!context.expired()); + // The placeholder the planner reports for a result known only at execution. The properties describe nothing + info.names = {Identifier("unknown")}; + info.types = {LogicalTypeId::UNKNOWN}; + info.statement_type = expanding_statement->type; + info.properties.always_require_rebind = true; +} + PreparedStatement::PreparedStatement(ErrorData error) : success(false), error(std::move(error)) { } PreparedStatement::~PreparedStatement() { + if (expanding_statement) { + // nothing was prepared in the client context + return; + } auto client_context = context.lock(); if (!client_context) { // the client context is gone - the prepared statement has been deallocated together with it @@ -93,8 +108,18 @@ case_insensitive_map_t PreparedStatement::GetExpectedParameterTypes return expected_types; } -unique_ptr -PreparedStatement::CreateExecuteStatement(const identifier_map_t &named_values) const { +bool PreparedStatement::MetadataAvailable() const { + return !expanding_statement && info.properties.bound_all_parameters; +} + +unique_ptr PreparedStatement::CreateStatement(identifier_map_t &named_values, + QueryParameters ¶meters) const { + if (expanding_statement) { + if (!named_values.empty()) { + parameters.statement_args = named_values; + } + return expanding_statement->Copy(); + } auto execute = make_uniq(); execute->name = Identifier(name); // report the query that was prepared - not the generated EXECUTE - in errors and profiling output @@ -116,7 +141,9 @@ unique_ptr PreparedStatement::Execute(identifier_map_t(ErrorData( InvalidInputException("Attempting to execute a prepared statement after its connection was closed!"))); } - return client_context->RunInternalStatement(CreateExecuteStatement(named_values), query_parameters); + auto parameters = query_parameters; + auto statement = CreateStatement(named_values, parameters); + return client_context->RunInternalStatement(std::move(statement), parameters); } unique_ptr PreparedStatement::Execute(vector &values, const QueryParameters &query_parameters) { @@ -148,7 +175,9 @@ unique_ptr PreparedStatement::Submit(identifier_map_t(ErrorData(exception)); } - return client_context->SubmitInternalStatement(CreateExecuteStatement(named_values), query_parameters); + auto parameters = query_parameters; + auto statement = CreateStatement(named_values, parameters); + return client_context->SubmitInternalStatement(std::move(statement), parameters); } } // namespace duckdb diff --git a/src/duckdb/src/main/profiler/gathered_metrics.cpp b/src/duckdb/src/main/profiler/gathered_metrics.cpp index dc8f5f98f..e58f4e541 100644 --- a/src/duckdb/src/main/profiler/gathered_metrics.cpp +++ b/src/duckdb/src/main/profiler/gathered_metrics.cpp @@ -105,12 +105,15 @@ void GatheredMetrics::SetMetric(const string &key, const string &value) { metrics[key] = Value(value); } -void GatheredMetrics::WriteMetricsToLog(ClientContext &context) const { +void GatheredMetrics::WriteMetricsToLog(ClientContext &context, + const unordered_map &timings) const { auto &logger = Logger::Get(context); if (logger.ShouldLog(MetricsLogType::NAME, MetricsLogType::LEVEL)) { for (const auto &entry : metrics) { + auto timing = timings.find(entry.first); logger.WriteLog(MetricsLogType::NAME, MetricsLogType::LEVEL, - MetricsLogType::ConstructLogMessage(entry.first, entry.second)); + MetricsLogType::ConstructLogMessage(entry.first, entry.second, + timing == timings.end() ? nullptr : &timing->second)); } } } diff --git a/src/duckdb/src/main/profiler/profiling_utils.cpp b/src/duckdb/src/main/profiler/profiling_utils.cpp index 29a40645c..7887993f6 100644 --- a/src/duckdb/src/main/profiler/profiling_utils.cpp +++ b/src/duckdb/src/main/profiler/profiling_utils.cpp @@ -5,8 +5,8 @@ namespace duckdb { void QueryMetrics::FinalizeMetrics(GatheredMetrics &info) { info.SetMetric(query_sql); - for (const auto &[key, ns] : string_timings) { - info.SetMetric(key, static_cast(ns) / 1e9); + for (const auto &[key, timing] : string_timings) { + info.SetMetric(key, static_cast(timing.duration) / 1e9); } for (const auto &[key, count] : string_counters) { info.SetMetric(key, count); diff --git a/src/duckdb/src/main/query_profiler.cpp b/src/duckdb/src/main/query_profiler.cpp index 4325e7b62..7d9010747 100644 --- a/src/duckdb/src/main/query_profiler.cpp +++ b/src/duckdb/src/main/query_profiler.cpp @@ -159,7 +159,7 @@ void QueryProfiler::StartQuery(const string &query, bool is_explain_analyze_p, b } Start(query); } -void QueryProfiler::AddParserTime(const Profiler &parser_timer) { +void QueryProfiler::AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time) { if (!running || !IsEnabled()) { return; } @@ -167,7 +167,7 @@ void QueryProfiler::AddParserTime(const Profiler &parser_timer) { if (!parser_time_ns) { return; } - query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns); + query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns, start_time, end_time); } bool QueryProfiler::OperatorRequiresProfiling(const PhysicalOperatorType op_type) { @@ -843,7 +843,7 @@ void QueryProfiler::ToLogInternal() const { if (!root) { return; } - metrics->WriteMetricsToLog(context); + metrics->WriteMetricsToLog(context, query_metrics.GetMetricTimings()); } void QueryProfiler::ToLog() const { diff --git a/src/duckdb/src/main/query_result.cpp b/src/duckdb/src/main/query_result.cpp index 46f5a5ff9..e98b39e9a 100644 --- a/src/duckdb/src/main/query_result.cpp +++ b/src/duckdb/src/main/query_result.cpp @@ -28,6 +28,32 @@ BaseQueryResult::BaseQueryResult(ErrorData error) : success(false), error(std::m D_ASSERT(this->error.HasError()); } +BaseQueryResult::BaseQueryResult(StatementType statement_type) + : statement_type(statement_type), metadata_available(false), success(true) { +} + +void BaseQueryResult::SetMetadata(StatementProperties properties_p, vector types_p, + vector names_p) { + D_ASSERT(!metadata_available); + D_ASSERT(types_p.size() == names_p.size()); + properties = std::move(properties_p); + types = std::move(types_p); + names = std::move(names_p); + metadata_available = true; +} + +void BaseQueryResult::RequireMetadata() const { + // A result that failed reports the empty metadata of every failed result + if (!MetadataAvailable()) { + throw InvalidInputException("The metadata of this result is not available yet: the statement expands into " + "several, and the one that produces the result has not been bound"); + } +} + +bool BaseQueryResult::MetadataAvailable() const { + return metadata_available || !success; +} + BaseQueryResult::~BaseQueryResult() { } @@ -70,6 +96,7 @@ const ErrorData &BaseQueryResult::GetErrorObject() const { } idx_t BaseQueryResult::ColumnCount() const { + RequireMetadata(); return types.size(); } @@ -78,14 +105,17 @@ StatementType BaseQueryResult::GetStatementType() const { } const StatementProperties &BaseQueryResult::GetStatementProperties() const { + RequireMetadata(); return properties; } const vector &BaseQueryResult::GetTypes() const { + RequireMetadata(); return types; } const vector &BaseQueryResult::GetNames() const { + RequireMetadata(); return names; } @@ -106,6 +136,35 @@ QueryResult::QueryResult(shared_ptr context_p, PreparedStatementD } } +QueryResult::QueryResult(shared_ptr context_p, StatementType statement_type, + shared_ptr format_p, ClientProperties client_properties_p) + : BaseQueryResult(statement_type), client_properties(std::move(client_properties_p)), context(std::move(context_p)), + format(std::move(format_p)) { + if (!format) { + format = ChunkFormat::InMemory(); + } + // The state of a result without columns, which a failure leaves in place; the statement producing the result + // replaces it + ResultFormatContext format_context {vector(), vector(), client_properties, + ResultOrdering::UNORDERED}; + format_state = format->InitGlobal(format_context); +} + +void QueryResult::AdoptResultStatement(StatementProperties properties, PreparedStatementData &statement, + shared_ptr buffer_p) { + D_ASSERT(!buffer && buffer_p); + SetMetadata(std::move(properties), statement.types, statement.names); + buffer = std::move(buffer_p); + format = buffer->SharedFormat(); + format_state = buffer->SharedFormatState(); +} + +void QueryResult::AdoptRowlessResult(QueryResult &produced) { + SetMetadata(produced.GetStatementProperties(), produced.GetTypes(), produced.GetNames()); + format_state = produced.format_state; + AdoptCollected(produced); +} + QueryResult::QueryResult(StatementType statement_type, StatementProperties properties, vector names_p, unique_ptr collection_p, ClientProperties client_properties_p) : BaseQueryResult(statement_type, std::move(properties), collection_p->Types(), std::move(names_p)), @@ -239,8 +298,9 @@ QueryResultState QueryResult::ExecuteTask() { auto lock = LockContext(); CheckExecutableInternal(*lock); auto state = context->ExecuteTaskInternal(*lock, *this); - // Only a statement that sank no row finishes undecided, since producers park until the decision - if (state == QueryResultState::FINISHED && buffer && buffer->Lifetime() != ResultLifetime::DRAINING) { + // Only a statement that sank no row finishes undecided, since producers park until the decision. A statement + // that expands into several without returning rows never has a buffer + if (state == QueryResultState::FINISHED && (!buffer || buffer->Lifetime() != ResultLifetime::DRAINING)) { return EndFinishedInternal(*lock); } return state; @@ -297,6 +357,7 @@ const ResultFormatGlobalState &QueryResult::CheckedFormatState(const char *expec if (!StringUtil::Equals(Format().Name(), expected)) { ThrowFormatMismatch(expected); } + RequireMetadata(); D_ASSERT(format_state); return *format_state; } @@ -328,8 +389,16 @@ void QueryResult::Materialize() { context.reset(); return; } - D_ASSERT(buffer); - buffer->Decide(ResultLifetime::RETAINED); + SettleRetainedInternal(*lock); +} + +void QueryResult::SettleRetainedInternal(ClientContextLock &lock) { + if (buffer) { + buffer->Decide(ResultLifetime::RETAINED); + return; + } + // The statement that produces the result has not started: its buffer takes the decision when it is created + context->DecideResultLifetimeInternal(lock, ResultLifetime::RETAINED); } void QueryResult::Complete() { @@ -351,8 +420,7 @@ void QueryResult::CompleteInternal(ClientContextLock &lock) { context.reset(); return; } - D_ASSERT(buffer); - buffer->Decide(ResultLifetime::RETAINED); + SettleRetainedInternal(lock); try { QueryResultState state; while (!IsTerminal(state = context->ExecuteTaskInternal(lock, *this))) { @@ -363,26 +431,41 @@ void QueryResult::CompleteInternal(ClientContextLock &lock) { if (state == QueryResultState::FINISHED) { EndFinishedInternal(lock); } - } catch (...) { - // the caller holds the context lock - clean up with it here, as closing this result would lock it again - try { - if (context->IsActiveResult(lock, *this)) { - context->CleanupInternal(lock, this, true); + } catch (std::exception &ex) { + AbandonAfterThrowInternal(lock, ErrorData(ex)); + throw; + } catch (...) { // LCOV_EXCL_START + AbandonAfterThrowInternal(lock, ErrorData("Unhandled exception while completing the query")); + throw; + } // LCOV_EXCL_STOP + context.reset(); +} + +void QueryResult::AbandonAfterThrowInternal(ClientContextLock &lock, ErrorData error) { + // the caller holds the context lock - clean up with it here, as closing this result would lock it again + try { + if (context->IsActiveResult(lock, *this)) { + if (!HasError()) { + // Ended as failed, so nothing the query ran is committed + SetError(std::move(error)); } - } catch (...) { // NOLINT + context->CleanupInternal(lock, this, true); } - context.reset(); - throw; + } catch (...) { // NOLINT } context.reset(); } QueryResultState QueryResult::EndFinishedInternal(ClientContextLock &lock) { - auto produced = context->GetExecutor().GetResult(); + auto produced = context->TakeFinishedResultInternal(lock); // Cleanup can fail on an autocommit commit; it records the error on this result context->CleanupInternal(lock, this, false); if (!HasError()) { - AdoptCollected(*produced); + if (MetadataAvailable()) { + AdoptCollected(*produced); + } else { + AdoptRowlessResult(*produced); + } } context.reset(); return HasError() ? QueryResultState::EXECUTION_ERROR : QueryResultState::FINISHED; @@ -412,8 +495,15 @@ void QueryResult::EndQuery(ClientContextLock &lock, bool invalidate_transaction) } void QueryResult::HandleFetchFailure(ClientContextLock &lock, ErrorData error) { + if (!context || !context->IsActiveResult(lock, *this)) { + // The query already ended, so the failure is only recorded, unless an earlier one is + if (!HasError()) { + SetError(std::move(error)); + } + return; + } bool invalidate_query = true; - if (!context->ErrorInvalidatesTransaction(error.Type())) { + if (!context->ErrorInvalidatesQueryTransaction(error.Type())) { // standard exceptions do not invalidate the current transaction invalidate_query = false; } else if (Exception::InvalidatesDatabase(error.Type())) { @@ -444,6 +534,8 @@ unique_ptr QueryResult::FetchRaw() { // Rendering //===--------------------------------------------------------------------===// string QueryResult::ToString() { + // The metadata of a statement that expands into several is known once it runs + Complete(); if (HasError()) { return GetError() + "\n"; } @@ -468,6 +560,7 @@ string QueryResult::ToString() { } string QueryResult::ToBox(BoxRendererContext &context_p, const BoxRendererConfig &config) { + Complete(); if (HasError()) { return GetError() + "\n"; } @@ -480,6 +573,8 @@ string QueryResult::ToBox(BoxRendererContext &context_p, const BoxRendererConfig } bool QueryResult::Equals(QueryResult &other, bool compare_names) { // LCOV_EXCL_START + Complete(); + other.Complete(); // first compare the success state of the results if (HasError() != other.HasError()) { return false; diff --git a/src/duckdb/src/main/query_result_stream.cpp b/src/duckdb/src/main/query_result_stream.cpp index ccc52bb6e..5770b2fca 100644 --- a/src/duckdb/src/main/query_result_stream.cpp +++ b/src/duckdb/src/main/query_result_stream.cpp @@ -1,15 +1,20 @@ #include "duckdb/main/query_result_stream.hpp" #include "duckdb/common/string_util.hpp" +#include "duckdb/execution/executor.hpp" #include "duckdb/main/buffered_data/buffered_data.hpp" #include "duckdb/main/client_context.hpp" #include "duckdb/main/result_unit.hpp" namespace duckdb { -bool StreamsByDefault(StatementType written_type, const StatementProperties &properties, bool reads_to_the_end) { - if (written_type != StatementType::SELECT_STATEMENT || - properties.return_type != StatementReturnType::QUERY_RESULT) { +bool StreamsByDefault(StatementType written_type, const QueryResult &result, bool reads_to_the_end) { + // Checked first: a statement that expands into several is no SELECT, and its properties may not be known yet + if (written_type != StatementType::SELECT_STATEMENT) { + return false; + } + auto &properties = result.GetStatementProperties(); + if (properties.return_type != StatementReturnType::QUERY_RESULT) { return false; } return reads_to_the_end || properties.IsReadOnly(); @@ -24,12 +29,22 @@ ResultStreamBase::ResultStreamBase(unique_ptr result, const char *e throw InvalidInputException("Attempting to open a stream on an unsuccessful query result\nError: %s", handle->GetError()); } - if (!handle->HasBufferedData()) { + if (handle->HasBufferedData()) { + if (handle->GetBufferedData().Decide(ResultLifetime::DRAINING) != ResultLifetime::DRAINING) { + throw InvalidInputException("Attempting to open a stream on a query result that is being retained"); + } + } else if (handle->context && !handle->MetadataAvailable()) { + // A statement that expands into several: the statement producing the result takes the decision when it + // starts + auto keep_alive = handle->context; + auto lock = keep_alive->LockContext(); + handle->CheckExecutableInternal(*lock); + if (keep_alive->DecideResultLifetimeInternal(*lock, ResultLifetime::DRAINING) != ResultLifetime::DRAINING) { + throw InvalidInputException("Attempting to open a stream on a query result that is being retained"); + } + } else { throw InvalidInputException("Attempting to open a stream on a query result that has no streaming buffer"); } - if (handle->GetBufferedData().Decide(ResultLifetime::DRAINING) != ResultLifetime::DRAINING) { - throw InvalidInputException("Attempting to open a stream on a query result that is being retained"); - } if (!StringUtil::Equals(handle->Format().Name(), expected_format)) { throw InvalidInputException("Attempting to open a \"%s\" stream on a query result in the \"%s\" format", expected_format, handle->Format().Name()); @@ -48,6 +63,10 @@ bool ResultStreamBase::IsOpen() { return handle->IsOpen(); } +bool ResultStreamBase::MetadataAvailable() const { + return handle->MetadataAvailable(); +} + QueryResultState ResultStreamBase::GuardedInternal(const char *name, const std::function &call) { @@ -55,7 +74,9 @@ ResultStreamBase::GuardedInternal(const char *name, // The stream already ended. Keep reporting the terminal state return handle->HasError() ? QueryResultState::EXECUTION_ERROR : QueryResultState::FINISHED; } - auto lock = handle->LockContext(); + // Ending the query drops the handle's reference to the context, which the lock below outlives + auto keep_alive = handle->context; + auto lock = keep_alive->LockContext(); try { return call(*lock); } catch (std::exception &ex) { @@ -68,8 +89,30 @@ ResultStreamBase::GuardedInternal(const char *name, return QueryResultState::EXECUTION_ERROR; } +QueryResultState ResultStreamBase::DriveWithoutBufferInternal(ClientContextLock &lock, bool execute_task) { + D_ASSERT(!handle->buffer); + if (!handle->IsOpenInternal(lock)) { + return handle->Cancelled(); + } + auto &context = *handle->context; + // Checked as the buffer's own Poll checks it, so a cancel is seen before a finished execution can be ended. A + // worker error also raises the flag; both loads are seq_cst + if (context.interrupt_state.load() == ClientInterruptState::INTERRUPTED && !Executor::Get(context).HasError()) { + throw InterruptException(); + } + return execute_task ? context.ExecuteTaskInternal(lock, *handle) : context.PollInternal(lock, *handle); +} + QueryResultState ResultStreamBase::Poll() { - auto state = GuardedInternal("Poll", [&](ClientContextLock &lock) { return handle->buffer->Poll(lock, *handle); }); + auto state = GuardedInternal("Poll", [&](ClientContextLock &lock) { + if (!handle->buffer) { + auto state = DriveWithoutBufferInternal(lock, false); + if (!handle->buffer) { + return state; + } + } + return handle->buffer->Poll(lock, *handle); + }); if (state == QueryResultState::EXECUTION_ERROR) { Close(); } @@ -77,8 +120,14 @@ QueryResultState ResultStreamBase::Poll() { } QueryResultState ResultStreamBase::ExecuteTask() { - auto state = GuardedInternal("ExecuteTask", - [&](ClientContextLock &lock) { return handle->buffer->Participate(lock, *handle); }); + auto state = GuardedInternal("ExecuteTask", [&](ClientContextLock &lock) { + if (!handle->buffer) { + auto state = DriveWithoutBufferInternal(lock, true); + // When the statement producing the result started, the buffer reports what is poppable + return handle->buffer ? handle->buffer->Poll(lock, *handle) : state; + } + return handle->buffer->Participate(lock, *handle); + }); if (state == QueryResultState::EXECUTION_ERROR) { // A finished execution can still hold trailing units, so only an error ends the stream here Close(); @@ -90,13 +139,25 @@ void ResultStreamBase::WaitForTask() { if (!handle->context) { return; } - handle->buffer->UnblockSinks(); + if (handle->buffer) { + handle->buffer->UnblockSinks(); + } handle->WaitForTask(); } QueryResultState ResultStreamBase::TryFetchUnit(unique_ptr &out_unit) { out_unit.reset(); auto state = GuardedInternal("TryFetch", [&](ClientContextLock &lock) { + if (!handle->buffer) { + auto state = DriveWithoutBufferInternal(lock, false); + if (!handle->buffer) { + if (state == QueryResultState::FINISHED) { + // A statement that expands into several and returns no rows ends without a unit + return handle->EndFinishedInternal(lock); + } + return state; + } + } auto &buffer = *handle->buffer; auto state = buffer.Poll(lock, *handle); if (state == QueryResultState::EXECUTION_ERROR) { @@ -124,8 +185,22 @@ QueryResultState ResultStreamBase::TryFetchUnit(unique_ptr &out_unit } unique_ptr ResultStreamBase::FetchUnitInternal(ClientContextLock &lock) { - auto &buffer = *handle->buffer; try { + // The statements before the one producing the result of a statement that expands run on this thread + while (!handle->buffer) { + auto state = handle->context->ExecuteTaskInternal(lock, *handle); + if (state == QueryResultState::EXECUTION_ERROR) { + return nullptr; + } + if (state == QueryResultState::FINISHED && !handle->buffer) { + handle->EndFinishedInternal(lock); + return nullptr; + } + if (state == QueryResultState::BLOCKED || state == QueryResultState::NO_TASKS_AVAILABLE) { + handle->context->WaitForTask(lock, *handle); + } + } + auto &buffer = *handle->buffer; auto state = buffer.ReplenishBuffer(lock, *handle); if (state == QueryResultState::EXECUTION_ERROR) { return nullptr; @@ -154,12 +229,14 @@ unique_ptr ResultStreamBase::FetchUnit() { } unique_ptr unit; { + // Ending the query drops the handle's reference to the context, which the lock below outlives + auto keep_alive = handle->context; auto lock = handle->LockContext(); handle->CheckExecutableInternal(*lock); unit = FetchUnitInternal(*lock); } if (!unit) { - if (!HasError()) { + if (!HasError() && handle->buffer) { handle->buffer->AssertNoBlockedSinks(); } Close(); @@ -169,6 +246,9 @@ unique_ptr ResultStreamBase::FetchUnit() { } const ResultFormatGlobalState &ResultStreamBase::FormatStateInternal() const { + if (!handle->buffer) { + return handle->CheckedFormatState(handle->Format().Name()); + } return handle->GetBufferedData().FormatState(); } diff --git a/src/duckdb/src/main/secret/default_secrets.cpp b/src/duckdb/src/main/secret/default_secrets.cpp index f0dce5f5d..4fa756253 100644 --- a/src/duckdb/src/main/secret/default_secrets.cpp +++ b/src/duckdb/src/main/secret/default_secrets.cpp @@ -3,6 +3,10 @@ namespace duckdb { +identifier_set_t CreateHTTPSecretFunctions::GetRedactedKeys() { + return {"http_proxy_password", "bearer_token", "extra_http_headers"}; +} + vector CreateHTTPSecretFunctions::GetDefaultSecretTypes() { vector result; @@ -89,7 +93,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromEnv(Client secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = {"http_proxy_password", "bearer_token"}; + secret->redact_keys = GetRedactedKeys(); return std::move(secret); } @@ -107,7 +111,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromConfig(Cli secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = {"http_proxy_password", "bearer_token"}; + secret->redact_keys = GetRedactedKeys(); return std::move(secret); } diff --git a/src/duckdb/src/main/secret/secret.cpp b/src/duckdb/src/main/secret/secret.cpp index cf6bf6538..32e5cc921 100644 --- a/src/duckdb/src/main/secret/secret.cpp +++ b/src/duckdb/src/main/secret/secret.cpp @@ -101,6 +101,41 @@ void KeyValueSecret::Serialize(Serializer &serializer) const { serializer.WriteProperty(202, "redact_keys", list); } +void KeyValueSecret::ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result) { + deserializer.ReadObject(201, "secret_map", [&](Deserializer &map) { + auto stored_type = map.ReadProperty(100, "type"); + // Before DuckDB 1.1.3, secrets declared VARCHAR values even when storing other types. + if (stored_type.id() != LogicalTypeId::MAP || !stored_type.HasParameters() || + MapType::KeyType(stored_type) != LogicalType::VARCHAR) { + throw SerializationException("Secret map must be a MAP with VARCHAR keys"); + } + if (map.ReadProperty(101, "is_null")) { + throw SerializationException("Secret map cannot be NULL"); + } + + map.Set(ListType::GetChildType(stored_type)); + map.ReadObject(102, "value", [&](Deserializer &obj) { + obj.ReadList(100, "children", [&](Deserializer::List &list, idx_t i) { + // Before DuckDB 1.2.0, entries carried their actual value types, even when the map declared VARCHAR. + // Deserialize them separately so their payloads still undergo normal Value validation. + auto entry = list.ReadElement(); + if (entry.IsNull() || entry.type().id() != LogicalTypeId::STRUCT) { + throw SerializationException("Secret map entry must be a non-NULL struct"); + } + const auto &children = StructValue::GetChildren(entry); + if (children.size() != 2) { + throw SerializationException("Secret map entry must have two fields"); + } + if (children[0].IsNull() || children[0].type() != LogicalType::VARCHAR) { + throw SerializationException("Secret map key must be a non-NULL VARCHAR"); + } + result[Identifier(StringValue::Get(children[0]))] = children[1]; + }); + }); + map.Unset(); + }); +} + Value KeyValueSecret::TryGetValue(const Identifier &key, bool error_on_missing) const { auto lookup = secret_map.find(key); if (lookup == secret_map.end()) { diff --git a/src/duckdb/src/main/secret/secret_manager.cpp b/src/duckdb/src/main/secret/secret_manager.cpp index 5eb49e3b8..16ca921b5 100644 --- a/src/duckdb/src/main/secret/secret_manager.cpp +++ b/src/duckdb/src/main/secret/secret_manager.cpp @@ -113,8 +113,17 @@ unique_ptr SecretManager::DeserializeSecret(Deserializer &deserializ switch (serialization_type) { // This allows us to skip looking up the secret type for deserialization altogether - case SecretSerializationType::KEY_VALUE_SECRET: - return KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); + case SecretSerializationType::KEY_VALUE_SECRET: { + auto result = + KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); + if (type == "http") { + // Preserve stored keys and apply current HTTP redaction rules to older secrets. + const auto keys = CreateHTTPSecretFunctions::GetRedactedKeys(); + auto &secret = result->Cast(); + secret.redact_keys.insert(keys.begin(), keys.end()); + } + return result; + } // Continues below: we need to do a type lookup to find the secret deserialize method case SecretSerializationType::CUSTOM: break; @@ -542,8 +551,8 @@ vector SecretManager::AllSecrets(CatalogTransaction transaction) { } // Add results from all backends to the result set - for (const auto &backend : secret_storages) { - auto backend_result = backend.second->AllSecrets(&transaction); + for (const auto &backend : GetSecretStorages()) { + auto backend_result = backend.get().AllSecrets(&transaction); for (const auto &it : backend_result) { result.push_back(it); } diff --git a/src/duckdb/src/main/settings/custom_settings.cpp b/src/duckdb/src/main/settings/custom_settings.cpp index cf9449843..3b63cefb3 100644 --- a/src/duckdb/src/main/settings/custom_settings.cpp +++ b/src/duckdb/src/main/settings/custom_settings.cpp @@ -619,17 +619,17 @@ void DisabledOptimizersSetting::SetGlobal(DatabaseInstance *db, DBConfig &config } disabled_optimizers.insert(OptimizerTypeFromString(param)); } - config.options.disabled_optimizers = std::move(disabled_optimizers); + config.SetDisabledOptimizers(std::move(disabled_optimizers)); } void DisabledOptimizersSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) { - config.options.disabled_optimizers = DBConfigOptions().disabled_optimizers; + config.SetDisabledOptimizers(DBConfigOptions().disabled_optimizers); } Value DisabledOptimizersSetting::GetSetting(const ClientContext &context) { auto &config = DBConfig::GetConfig(context); string result; - for (auto &optimizer : config.options.disabled_optimizers) { + for (auto &optimizer : config.GetDisabledOptimizers()) { if (!result.empty()) { result += ","; } @@ -1417,6 +1417,10 @@ void ProfilingRendererSettingsSetting::SetLocal(ClientContext &context, const Va unordered_map new_settings; for (auto &entry : MapValue::GetChildren(input)) { auto &key_value = StructValue::GetChildren(entry); + if (key_value[1].IsNull()) { + throw InvalidInputException("profiling_renderer_settings: the value of \"%s\" cannot be NULL", + key_value[0].ToString()); + } new_settings[StringUtil::Lower(key_value[0].ToString())] = key_value[1]; } // eagerly configure the renderer of the current profiler format to validate the setting values @@ -1975,7 +1979,8 @@ void ActiveGrammarExtensionsSetting::SetLocal(ClientContext &context, const Valu return; } - auto compiled_grammar = CompiledGrammar::Create(context, selected_extensions); + auto compiled_grammar = + DatabaseInstance::GetDatabase(context).GetParserCache().GetMatcher(context, selected_extensions); client_config.active_grammar_extensions = std::move(selected_extensions); client_config.cached_grammar = std::move(compiled_grammar); } diff --git a/src/duckdb/src/main/statement_iterator.cpp b/src/duckdb/src/main/statement_iterator.cpp index 0e20adbdd..5a49b4d2c 100644 --- a/src/duckdb/src/main/statement_iterator.cpp +++ b/src/duckdb/src/main/statement_iterator.cpp @@ -4,7 +4,6 @@ #include "duckdb/main/query_profiler.hpp" #include "duckdb/parser/sql_statement.hpp" #include "duckdb/parser/statement/explain_statement.hpp" -#include "duckdb/planner/statement_preprocessor.hpp" namespace duckdb { @@ -16,8 +15,7 @@ static bool StatementIteratorIsExplainAnalyze(SQLStatement *statement) { } StatementIterator::StatementIterator(ParseIterator &&parse_iterator) - : source(std::move(parse_iterator)), context(source.GetClientContext()), - preprocessor(make_uniq(context)) { + : source(std::move(parse_iterator)), context(source.GetClientContext()) { } StatementIterator::~StatementIterator() = default; @@ -25,21 +23,17 @@ StatementIterator::~StatementIterator() = default; StatementIterator::StatementIterator(StatementIterator &&) noexcept = default; bool StatementIterator::Peek() { - // More buffered engine statements from the current peel's expansion? - if (buffer_cursor < buffer.size()) { - return true; - } if (pending_statement) { return true; } - // Otherwise, is there another parse-facing statement to pull? Parses ahead, does NOT preprocess - // — safe to use as a lookahead. + parser_start_time = Timestamp::GetCurrentTimestamp(); parser_timer.Start(); if (!source.Peek()) { parser_timer.Reset(); return false; } parser_timer.End(); + parser_end_time = Timestamp::GetCurrentTimestamp(); pending_statement = source.GetStatement(); if (!pending_statement) { parser_timer.Reset(); @@ -49,71 +43,32 @@ bool StatementIterator::Peek() { } bool StatementIterator::HasMore() { - // Buffered engine statements from the current peel still remain? - if (buffer_cursor < buffer.size()) { - return true; - } if (pending_statement) { return true; } - // Otherwise defer to the parse-facing source's grammar-free existence check. return source.HasMore(); } -unique_ptr StatementIterator::GetStatementInternal(optional_ptr lock, - bool profile_statement) { - // Drain the current peel's expansion first. - if (buffer_cursor < buffer.size()) { - auto statement = std::move(buffer[buffer_cursor++]); - if (profile_statement) { - auto &profiler = QueryProfiler::Get(context); - profiler.StartQuery(statement->query, StatementIteratorIsExplainAnalyze(statement.get())); - profiler.AddParserTime(parser_timer); - } - parser_timer.Reset(); - return statement; - } - // Pull the next parse-facing statement. +unique_ptr StatementIterator::GetStatementInternal(bool profile_statement) { if (!pending_statement && !Peek()) { - return nullptr; // exhausted - } - auto stmt = std::move(pending_statement); - buffer.clear(); - buffer_cursor = 0; - buffer.push_back(std::move(stmt)); - // Preprocess the peel into one-or-more engine-facing statements. This runs in Get (not Peek) so it - // sees the transaction state left by the previously executed statement. - context.PreprocessStatements(buffer, lock, preprocessor.get()); - if (buffer.empty()) { - parser_timer.Reset(); - // Preprocessing swallowed the peel — caller skips with `continue`; the next Get pulls on. return nullptr; } - buffer_cursor = 1; - auto statement = std::move(buffer[0]); + auto statement = std::move(pending_statement); if (profile_statement) { auto &profiler = QueryProfiler::Get(context); profiler.StartQuery(statement->query, StatementIteratorIsExplainAnalyze(statement.get())); - profiler.AddParserTime(parser_timer); + profiler.AddParserTime(parser_timer, parser_start_time, parser_end_time); } parser_timer.Reset(); return statement; } unique_ptr StatementIterator::GetStatement() { - return GetStatementInternal(nullptr, false); -} - -unique_ptr StatementIterator::GetStatementWithLock(ClientContextLock &lock) { - return GetStatementInternal(&lock, false); + return GetStatementInternal(false); } unique_ptr StatementIterator::GetStatementForExecution() { - return GetStatementInternal(nullptr, true); -} - -unique_ptr StatementIterator::GetStatementForExecutionWithLock(ClientContextLock &lock) { - return GetStatementInternal(&lock, true); + return GetStatementInternal(true); } } // namespace duckdb diff --git a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp index 19a1871ce..b8b82fe8f 100644 --- a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp +++ b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp @@ -19,6 +19,54 @@ namespace duckdb { //===--------------------------------------------------------------------===// // Subplan Signature/Info //===--------------------------------------------------------------------===// +static vector GetExpandedColumnBindings(LogicalOperator &op) { + switch (op.type) { + case LogicalOperatorType::LOGICAL_FILTER: + case LogicalOperatorType::LOGICAL_ORDER_BY: + case LogicalOperatorType::LOGICAL_LIMIT: + case LogicalOperatorType::LOGICAL_TOP_N: + case LogicalOperatorType::LOGICAL_DISTINCT: + return GetExpandedColumnBindings(*op.children[0]); + case LogicalOperatorType::LOGICAL_WINDOW: + case LogicalOperatorType::LOGICAL_UNNEST: { + auto bindings = GetExpandedColumnBindings(*op.children[0]); + const auto table_index = op.GetTableIndex()[0]; + for (auto col_idx : ProjectionIndex::GetIndexes(op.expressions.size())) { + bindings.emplace_back(table_index, col_idx); + } + return bindings; + } + case LogicalOperatorType::LOGICAL_ANY_JOIN: + case LogicalOperatorType::LOGICAL_ASOF_JOIN: + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { + auto &join = op.Cast(); + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { + return left_bindings; + } + if (join.join_type == JoinType::MARK) { + left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); + return left_bindings; + } + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { + return right_bindings; + } + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: + case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + default: + return op.GetColumnBindings(); + } +} + enum class ConversionType { TO_CANONICAL, RESTORE_ORIGINAL, @@ -100,7 +148,7 @@ class PlanSignatureTableIndexMap { // Store temporary mapping for (const auto &child_op : op.children) { - for (const auto &child_cb : child_op->GetColumnBindings()) { + for (const auto &child_cb : GetExpandedColumnBindings(*child_op)) { const auto &original = child_cb.table_index; auto it = to_canonical_table_index.find(original); if (it != to_canonical_table_index.end()) { @@ -1108,42 +1156,6 @@ class CommonSubplanFinder { return a.get(); } - vector GetExpandedColumnBindings(LogicalOperator &op) { - switch (op.type) { - case LogicalOperatorType::LOGICAL_FILTER: - case LogicalOperatorType::LOGICAL_ORDER_BY: - return GetExpandedColumnBindings(*op.children[0]); - case LogicalOperatorType::LOGICAL_ANY_JOIN: - case LogicalOperatorType::LOGICAL_ASOF_JOIN: - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { - auto &join = op.Cast(); - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { - return left_bindings; - } - if (join.join_type == JoinType::MARK) { - left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); - return left_bindings; - } - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { - return right_bindings; - } - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: - case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - default: - return op.GetColumnBindings(); - } - } - arena_vector GetCanonicalBindings(const vector &original_bindings) { const auto &table_index_map = state.table_index_map.GetMap(); arena_vector canonical_bindings(state.allocator); diff --git a/src/duckdb/src/optimizer/filter_combiner.cpp b/src/duckdb/src/optimizer/filter_combiner.cpp index 696bad84d..3477a0082 100644 --- a/src/duckdb/src/optimizer/filter_combiner.cpp +++ b/src/duckdb/src/optimizer/filter_combiner.cpp @@ -799,7 +799,19 @@ static bool IsLessThan(ExpressionType type) { //! Margins: 2h for DST ambiguity (TIMESTAMP<->TIMESTAMPTZ), 24h for time-of-day loss, 1s for rounding loss. static int64_t GetTemporalCastMargin(LogicalTypeId source, LogicalTypeId target) { if (source == target) { - return 0; + switch (source) { + case LogicalTypeId::DATE: + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + case LogicalTypeId::TIMESTAMP_NS: + case LogicalTypeId::TIMESTAMP_TZ_NS: + case LogicalTypeId::TIMESTAMP_MS: + case LogicalTypeId::TIMESTAMP_SEC: + return 0; + default: + // non-temporal types with the same id can differ (e.g. UNION or DECIMAL types) + return -1; + } } switch (source) { case LogicalTypeId::TIMESTAMP: @@ -1135,7 +1147,6 @@ FilterResult FilterCombiner::AddBoundComparisonFilter(Expression &expr) { return ret; } else { // comparison between two non-scalars - // only handle comparisons for now if (expr.GetExpressionType() != ExpressionType::COMPARE_EQUAL) { return FilterResult::UNSUPPORTED; } @@ -1290,47 +1301,18 @@ FilterResult FilterCombiner::AddFilter(Expression &expr) { return FilterResult::UNSUPPORTED; } -/* - * Create and add new transitive filters from a two non-scalar filter such as j > i, j >= i, j < i, and j <= i - * It's missing to create another method to add transitive filters from scalar filters, e.g, i > 10 - */ +// Infer constant bounds from comparisons between non-scalar expressions. FilterResult FilterCombiner::AddTransitiveFilters(BoundFunctionExpression &comparison, bool is_root) { if (!IsGreaterThan(comparison.GetExpressionType()) && !IsLessThan(comparison.GetExpressionType())) { return FilterResult::UNSUPPORTED; } + // Derived bounds must not duplicate volatile or throwing expressions. + if (comparison.IsVolatile() || comparison.CanThrow()) { + return FilterResult::UNSUPPORTED; + } // get the LHS and RHS nodes auto &left_node = GetNode(*BoundComparisonExpression::LeftMutable(comparison)); - reference right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); - // In case with filters like CAST(i) = j and i = 5 we replace the COLUMN_REF i with the constant 5 - do { - if (right_node.get().GetExpressionType() != ExpressionType::OPERATOR_CAST) { - break; - } - auto &bound_cast_expr = right_node.get().Cast(); - if (BoundCastExpression::Child(bound_cast_expr).GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - break; - } - auto &col_ref = BoundCastExpression::Child(bound_cast_expr).Cast(); - for (auto &stored_exp : stored_expressions) { - const_reference expr = stored_exp.first; - if (expr.get().GetExpressionType() == ExpressionType::OPERATOR_CAST) { - expr = BoundCastExpression::Child(right_node.get().Cast()); - } - if (expr.get().GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - continue; - } - auto &st_col_ref = expr.get().Cast(); - if (st_col_ref.Binding() != col_ref.Binding()) { - continue; - } - if (bound_cast_expr.GetReturnType() != stored_exp.second->GetReturnType()) { - continue; - } - BoundCastExpression::ChildMutable(bound_cast_expr) = stored_exp.second->Copy(); - right_node = GetNode(*BoundCastExpression::ChildMutable(bound_cast_expr)); - break; - } - } while (false); + auto &right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); if (left_node.Equals(right_node)) { return FilterResult::UNSUPPORTED; diff --git a/src/duckdb/src/optimizer/filter_pushdown.cpp b/src/duckdb/src/optimizer/filter_pushdown.cpp index b9484e797..d262b32a9 100644 --- a/src/duckdb/src/optimizer/filter_pushdown.cpp +++ b/src/duckdb/src/optimizer/filter_pushdown.cpp @@ -172,62 +172,34 @@ unique_ptr FilterPushdown::PushdownJoin(unique_ptrtype == LogicalOperatorType::LOGICAL_DELIM_JOIN); auto &join = op->Cast(); - const auto restore_projection_maps = join.HasProjectionMap(); - auto left_projection_map = join.left_projection_map; - auto right_projection_map = join.right_projection_map; - unordered_set left_bindings, right_bindings; LogicalJoin::GetTableReferences(*op->children[0], left_bindings); LogicalJoin::GetTableReferences(*op->children[1], right_bindings); - unique_ptr result; + // every branch keeps the join's projection maps on the operator it returns: the join itself when it survives, + // the re-created join in PushdownInnerJoin, or a filter carrying the map when a side replaces the join switch (join.join_type) { case JoinType::OUTER: - result = PushdownOuterJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownOuterJoin(std::move(op), left_bindings, right_bindings); case JoinType::INNER: // AsOf joins can't push anything into the RHS, so treat it as a left join if (op->type == LogicalOperatorType::LOGICAL_ASOF_JOIN) { - result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); } - result = PushdownInnerJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownInnerJoin(std::move(op), left_bindings, right_bindings); case JoinType::LEFT: - result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); case JoinType::MARK: - result = PushdownMarkJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownMarkJoin(std::move(op), left_bindings, right_bindings); case JoinType::SINGLE: - result = PushdownSingleJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownSingleJoin(std::move(op), left_bindings, right_bindings); case JoinType::SEMI: case JoinType::ANTI: - result = PushdownSemiAntiJoin(std::move(op)); - break; + return PushdownSemiAntiJoin(std::move(op)); default: // unsupported join type: stop pushing down return FinishPushdown(std::move(op)); } - - if (restore_projection_maps) { - switch (result->type) { - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: - case LogicalOperatorType::LOGICAL_ASOF_JOIN: - case LogicalOperatorType::LOGICAL_ANY_JOIN: - case LogicalOperatorType::LOGICAL_DELIM_JOIN: { - // Pushing down filter through join didn't change operator type (e.g., LogicalEmptyResult), restore maps - auto &result_join = result->Cast(); - result_join.left_projection_map = std::move(left_projection_map); - result_join.right_projection_map = std::move(right_projection_map); - break; - } - default: - break; - } - } - return result; } FilterResult FilterPushdown::PushFilters() { for (auto &f : filters) { diff --git a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp index 2549eac84..51f2247d7 100644 --- a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp +++ b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp @@ -50,6 +50,9 @@ struct GroupingSetLevel { } // namespace +//! The maximum number of grouping sets that are rewritten into separate aggregates +static constexpr idx_t MAX_REWRITE_GROUPING_SETS = 1024; + static bool CanRewriteAggregate(const BoundAggregateExpression &aggregate) { if (aggregate.IsDistinct() || aggregate.GetOrderBys()) { // DISTINCT / ORDER BY aggregates cannot be computed by combining states of a finer aggregation @@ -249,6 +252,10 @@ bool GroupingSetsOptimizer::TryRewriteGroupingSets(unique_ptr & can_cascade = false; } } + if (aggr.grouping_sets.size() > MAX_REWRITE_GROUPING_SETS) { + // the rewritten plan nests a materialized CTE per grouping set - leave large grouping sets to the aggregate + return false; + } vector levels; if (!can_cascade || !FindCascade(aggr.grouping_sets, levels)) { // Explicit branches support arbitrary aggregates and expose single grouping sets to subsequent rewrites. diff --git a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp index 98a8f2b33..1ed6428e9 100644 --- a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp +++ b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp @@ -8,7 +8,6 @@ #include "duckdb/optimizer/join_order/plan_enumerator.hpp" #include "duckdb/planner/expression/list.hpp" #include "duckdb/planner/operator/list.hpp" -#include "duckdb/main/settings.hpp" namespace duckdb { @@ -27,14 +26,12 @@ static optional CombineReorderableStats(const vector source; - for (auto &stats : relation_stats) { - source = stats.GetColumnStats(binding); - if (source) { - break; - } - } + auto source = lookup.Find(binding); if (!source) { return {}; } @@ -59,8 +56,7 @@ JoinOrderOptimizer JoinOrderOptimizer::CreateChildOptimizer() { unique_ptr JoinOrderOptimizer::Optimize(unique_ptr plan, optional_ptr stats) { - auto max_expression_depth = Settings::Get(query_graph_manager.context); - if (depth > max_expression_depth) { + if (depth > MAX_RECURSION_DEPTH) { // Very deep plans will eventually consume quite some stack space // Returning the current plan is always a valid choice return plan; diff --git a/src/duckdb/src/optimizer/join_order/relation_manager.cpp b/src/duckdb/src/optimizer/join_order/relation_manager.cpp index f963cb088..8b6139cae 100644 --- a/src/duckdb/src/optimizer/join_order/relation_manager.cpp +++ b/src/duckdb/src/optimizer/join_order/relation_manager.cpp @@ -362,14 +362,12 @@ static optional CombineNonReorderableStats(LogicalOperator &op, c auto ordinal_set_output = op.type == LogicalOperatorType::LOGICAL_UNION || op.type == LogicalOperatorType::LOGICAL_INTERSECT || op.type == LogicalOperatorType::LOGICAL_EXCEPT; + RelationColumnStatsLookup lookup; + for (auto &stats : child_stats) { + lookup.AddRelation(stats); + } for (idx_t column_idx = 0; column_idx < bindings.size(); column_idx++) { - optional_ptr source; - for (auto &stats : child_stats) { - source = stats.GetColumnStats(bindings[column_idx]); - if (source) { - break; - } - } + auto source = lookup.Find(bindings[column_idx]); if (!source && ordinal_set_output && !child_stats.empty() && column_idx < child_stats[0].columns.size()) { source = child_stats[0].columns[column_idx]; } diff --git a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp index 00825612b..ff8524f89 100644 --- a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp +++ b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp @@ -1,6 +1,8 @@ #include "duckdb/optimizer/multi_stage_aggregate_rewriter.hpp" #include "duckdb/common/string_util.hpp" +#include "duckdb/function/aggregate/distributive_function_utils.hpp" +#include "duckdb/function/function_binder.hpp" #include "duckdb/optimizer/aggregate_rewrite_helper.hpp" #include "duckdb/optimizer/aggregate_rewrite.hpp" #include "duckdb/optimizer/optimizer.hpp" @@ -9,6 +11,7 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" +#include "duckdb/planner/expression_binder.hpp" #include "duckdb/planner/expression_iterator.hpp" #include "duckdb/planner/operator/logical_aggregate.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" @@ -27,12 +30,15 @@ MultiStageAggregateRewriter::MultiStageAggregateRewriter( namespace { struct DistinctAggregateSet { - explicit DistinctAggregateSet(idx_t source_index) : source_index(source_index) { + DistinctAggregateSet(idx_t source_index, vector> keys_p) + : source_index(source_index), keys(std::move(keys_p)) { aggregate_indices.push_back(source_index); } idx_t source_index; vector aggregate_indices; + //! Actual deduplication keys, including collations unless the binder already applied them. + vector> keys; vector> order_expressions; }; @@ -62,6 +68,24 @@ static optional_idx FindExpression(const vector> &express return optional_idx(); } +//! Expression equality ignores the return type, but a collation lives in the return type and decides which +//! argument values are duplicates of each other. Two distinct aggregates can only share a deduplication branch +//! if their arguments agree on that as well. +static bool ArgumentsEqualIncludingCollation(const BoundAggregateExpression &lhs, const BoundAggregateExpression &rhs) { + auto &left = lhs.GetChildren(); + auto &right = rhs.GetChildren(); + if (left.size() != right.size()) { + return false; + } + for (idx_t i = 0; i < left.size(); i++) { + if (!Expression::Equals(*left[i], *right[i]) || + !left[i]->GetReturnType().EqualsIncludingCollation(right[i]->GetReturnType())) { + return false; + } + } + return true; +} + static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateExpression &aggregate) { if (!aggregate.GetOrderBys()) { return; @@ -78,33 +102,34 @@ static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateE static unique_ptr CreateFinalAggregate(const BoundAggregateExpression &source, const DistinctAggregateSet &set, - TableIndex input_table, idx_t input_column_offset, - idx_t order_column_offset, + TableIndex input_table, idx_t order_column_offset, + const vector &value_columns, optional_idx filter_column_offset = optional_idx()) { auto result = unique_ptr_cast(source.Copy()); result->GetAggregateTypeMutable() = AggregateType::NON_DISTINCT; result->GetChildrenMutable().clear(); for (idx_t child_idx = 0; child_idx < source.GetChildren().size(); child_idx++) { auto &child = source.GetChildren()[child_idx]; - result->GetChildrenMutable().push_back(make_uniq( - child->GetReturnType(), ColumnBinding(input_table, ProjectionIndex(input_column_offset + child_idx)))); + auto &value_column = value_columns[child_idx]; + result->GetChildrenMutable().push_back( + make_uniq(child->GetReturnType(), value_column)); } if (source.GetOrderBys()) { result->GetOrderBysMutable() = make_uniq(); for (auto &order : source.GetOrderBys()->orders) { auto order_idx = FindExpression(source.GetChildren(), *order.expression); - idx_t column_offset; + ColumnBinding order_binding; if (order_idx.IsValid()) { - column_offset = input_column_offset + order_idx.GetIndex(); + // the ORDER BY is one of the arguments - sort on the value of that argument + order_binding = value_columns[order_idx.GetIndex()]; } else { order_idx = FindExpression(set.order_expressions, *order.expression); D_ASSERT(order_idx.IsValid()); - column_offset = order_column_offset + order_idx.GetIndex(); + order_binding = ColumnBinding(input_table, ProjectionIndex(order_column_offset + order_idx.GetIndex())); } result->GetOrderBysMutable()->orders.emplace_back( order.type, order.null_order, - make_uniq(order.expression->GetReturnType(), - ColumnBinding(input_table, ProjectionIndex(column_offset)))); + make_uniq(order.expression->GetReturnType(), order_binding)); } } else { result->GetOrderBysMutable().reset(); @@ -153,8 +178,30 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate for (auto &group : aggr.groups) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*group, input_replacements)); } - for (auto &child : source_aggregate.GetChildren()) { - distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); + auto distinct_group_index = optimizer.binder.GenerateTableIndex(); + auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); + + // Group on comparison keys, but pass original values to the final aggregate. When these differ, + // FIRST carries a representative, just as for collated GROUP BY. + vector> distinct_aggregates; + vector value_columns; + value_columns.reserve(source_aggregate.GetChildren().size()); + FunctionBinder function_binder(optimizer.GetContext()); + for (idx_t child_idx = 0; child_idx < source_aggregate.GetChildren().size(); child_idx++) { + auto &child = source_aggregate.GetChildren()[child_idx]; + auto &key = set.keys[child_idx]; + auto value_binding = ColumnBinding(distinct_group_index, ProjectionIndex(distinct_groups.size())); + if (!Expression::Equals(*child, *key)) { + vector> first_children; + first_children.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); + auto first_aggregate = + function_binder.BindAggregateFunction(FirstFunctionGetter::GetFunction(child->GetReturnType()), + std::move(first_children), nullptr, AggregateType::NON_DISTINCT); + value_binding = ColumnBinding(distinct_aggregate_index, ProjectionIndex(distinct_aggregates.size())); + distinct_aggregates.push_back(std::move(first_aggregate)); + } + value_columns.push_back(value_binding); + distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*key, input_replacements)); } for (auto &order_expr : set.order_expressions) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*order_expr, input_replacements)); @@ -167,10 +214,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate AggregateRewriteHelper::CopyAndRebind(*source_aggregate.GetFilter(), input_replacements)); } - auto distinct_group_index = optimizer.binder.GenerateTableIndex(); - auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); auto distinct = - make_uniq(distinct_group_index, distinct_aggregate_index, vector>()); + make_uniq(distinct_group_index, distinct_aggregate_index, std::move(distinct_aggregates)); distinct->groups = std::move(distinct_groups); distinct->children.push_back(std::move(input)); @@ -186,8 +231,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate const auto order_column_offset = group_count + source_aggregate.GetChildren().size(); for (auto aggregate_idx : set.aggregate_indices) { auto &aggregate = aggr.expressions[aggregate_idx]->Cast(); - final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, group_count, - order_column_offset, filter_column_offset)); + final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, order_column_offset, + value_columns, filter_column_offset)); } auto final_group_index = optimizer.binder.GenerateTableIndex(); @@ -688,10 +733,20 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { regular_aggregates.push_back(aggregate_idx); continue; } + // Resolve keys before sharing branches: COUNT may already consume keys from the binder, while a + // value-returning aggregate with the same argument still needs its collation applied. + vector> keys; + for (auto &child : aggregate.GetChildren()) { + auto key = child->Copy(); + if (aggregate.Function().GetCollationHandling() != FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { + ExpressionBinder::PushCollation(optimizer.GetContext(), key, key->GetReturnType()); + } + keys.push_back(std::move(key)); + } bool found_match = false; for (auto &set : distinct_sets) { auto &other = aggr.expressions[set.source_index]->Cast(); - if (Expression::ListEquals(aggregate.GetChildren(), other.GetChildren()) && + if (ArgumentsEqualIncludingCollation(aggregate, other) && Expression::ListEquals(keys, set.keys) && Expression::Equals(aggregate.GetFilter(), other.GetFilter())) { set.aggregate_indices.push_back(aggregate_idx); AddOrderExpressions(set, aggregate); @@ -700,7 +755,7 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { } } if (!found_match) { - distinct_sets.emplace_back(aggregate_idx); + distinct_sets.emplace_back(aggregate_idx, std::move(keys)); AddOrderExpressions(distinct_sets.back(), aggregate); } } diff --git a/src/duckdb/src/optimizer/optimizer.cpp b/src/duckdb/src/optimizer/optimizer.cpp index a068c5fba..e9ef3cb82 100644 --- a/src/duckdb/src/optimizer/optimizer.cpp +++ b/src/duckdb/src/optimizer/optimizer.cpp @@ -130,13 +130,11 @@ bool Optimizer::OptimizerDisabled(ClientContext &context_p, OptimizerType type) return true; } auto &config = DBConfig::GetConfig(context_p); - return config.options.disabled_optimizers.find(type) != config.options.disabled_optimizers.end(); + return config.IsOptimizerDisabled(type); } void Optimizer::RunOptimizer(OptimizerType type, const std::function &callback) { - if (context.IsInterrupted()) { - throw InterruptException(); - } + context.InterruptCheck(); if (OptimizerDisabled(type)) { // optimizer is marked as disabled: skip diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp index b8a626121..84893ecf6 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp @@ -1,13 +1,60 @@ #include "duckdb/optimizer/filter_pushdown.hpp" #include "duckdb/optimizer/in_clause_rewriter.hpp" +#include "duckdb/planner/expression/bound_between_expression.hpp" +#include "duckdb/planner/expression/bound_columnref_expression.hpp" +#include "duckdb/planner/expression/bound_comparison_expression.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" #include "duckdb/planner/operator/logical_cross_product.hpp" #include "duckdb/planner/operator/logical_filter.hpp" +#include "duckdb/planner/operator/logical_empty_result.hpp" namespace duckdb { using Filter = FilterPushdown::Filter; +static bool HasTransitiveJoinBounds(const vector> &filters) { + unordered_set bound_tables; + for (auto &filter : filters) { + if (filter->bindings.size() != 1) { + continue; + } + auto &expr = *filter->filter; + bool has_bound = false; + if (BoundComparisonExpression::IsComparison(expr)) { + auto &comparison = expr.Cast(); + has_bound = BoundComparisonExpression::Left(comparison).IsFoldable() || + BoundComparisonExpression::Right(comparison).IsFoldable(); + } else if (expr.GetExpressionType() == ExpressionType::COMPARE_BETWEEN) { + auto &between = expr.Cast(); + has_bound = BoundBetweenExpression::LowerBound(between).IsFoldable() || + BoundBetweenExpression::UpperBound(between).IsFoldable(); + } + if (has_bound) { + bound_tables.insert(*filter->bindings.begin()); + } + } + if (bound_tables.empty()) { + return false; + } + for (auto &filter : filters) { + if (filter->bindings.size() < 2 || !BoundComparisonExpression::IsComparison(*filter->filter)) { + continue; + } + auto &comparison = filter->filter->Cast(); + auto type = comparison.GetExpressionType(); + if (type != ExpressionType::COMPARE_GREATERTHAN && type != ExpressionType::COMPARE_GREATERTHANOREQUALTO && + type != ExpressionType::COMPARE_LESSTHAN && type != ExpressionType::COMPARE_LESSTHANOREQUALTO) { + continue; + } + auto &right = BoundComparisonExpression::Right(comparison); + if (right.GetExpressionType() == ExpressionType::BOUND_COLUMN_REF && + bound_tables.count(right.Cast().Binding().table_index)) { + return true; + } + } + return false; +} + unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr op) { D_ASSERT(op->children.size() > 1); FilterPushdown left_pushdown(optimizer, convert_mark_joins, projection_mode); @@ -23,6 +70,13 @@ unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr left_bindings, right_bindings; if (!filters.empty()) { + // Recombine useful join bounds before distributing filters to the inputs. + if (HasTransitiveJoinBounds(filters)) { + if (PushFilters() == FilterResult::UNSATISFIABLE) { + return make_uniq(std::move(op)); + } + GenerateFilters(); + } // check to see into which side we should push the filters // first get the LHS and RHS bindings LogicalJoin::GetTableReferences(*op->children[0], left_bindings); diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp index f0acab67e..045497783 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp @@ -49,7 +49,21 @@ unique_ptr FilterPushdown::PushdownInnerJoin(unique_ptrtype) { + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: + case LogicalOperatorType::LOGICAL_ANY_JOIN: { + auto &result_join = result->Cast(); + result_join.left_projection_map = std::move(join.left_projection_map); + result_join.right_projection_map = std::move(join.right_projection_map); + break; + } + default: + break; + } + return result; } } // namespace duckdb diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp index bbe7685d2..4c375253f 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp @@ -157,18 +157,30 @@ unique_ptr FilterPushdown::PushdownProjection(unique_ptr> remain_expressions; vector> split_expressions; + auto stays_on_top = [&](const Filter &f) { + return IsVolatile(proj, *f.filter) || (f.filter->CanThrow() && !f.has_barrier); + }; + auto splits_off = [&](const Filter &f) { + return projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && + ReferencesComputedExpression(proj, *f.filter); + }; + // barred expressions may only cross the projection if every other filter does: a filter kept on top of the + // projection, or split off below it, would otherwise be evaluated after them + bool hold_back_barriers = false; + for (auto &filter : filters) { + if (!filter->has_barrier && (stays_on_top(*filter) || splits_off(*filter))) { + hold_back_barriers = true; + } + } for (auto &filter : filters) { auto &f = *filter; D_ASSERT(f.bindings.size() <= 1); - bool is_volatile = IsVolatile(proj, *f.filter); - bool preserve_computed_expression = projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && - ReferencesComputedExpression(proj, *f.filter); - if (is_volatile || (f.filter->CanThrow() && !f.has_barrier)) { + if (stays_on_top(f) || (f.has_barrier && hold_back_barriers)) { // Volatile and throwing expressions cannot move across the projection. Expressions carrying a barrier // are the exception: a projection does not remove any rows, so pushing them through it does not - // change which rows they are evaluated on. + // change which rows they are evaluated on - unless another filter stays behind, see above. remain_expressions.push_back(std::move(f.filter)); - } else if (preserve_computed_expression) { + } else if (splits_off(f)) { // Compute filter-dependent projection expressions once, below the filter. split_expressions.push_back(std::move(f.filter)); } else { diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp index 9d4ba02b7..21009431e 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp @@ -39,8 +39,8 @@ unique_ptr FilterPushdown::PushdownSemiAntiJoin(unique_ptrchildren[0]); + // nothing to match against: every left row passes, keep the join's output layout + return LogicalJoin::PromoteJoinSide(std::move(op->children[0]), std::move(join.left_projection_map)); case JoinType::SEMI: return make_uniq(std::move(op)); default: diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp index 7426e56e5..fbc8738a7 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp @@ -42,6 +42,24 @@ bool RelationStats::MatchesBindings(const vector &bindings) const return true; } +RelationColumnStatsLookup::RelationColumnStatsLookup(const RelationStats &stats) { + AddRelation(stats); +} + +void RelationColumnStatsLookup::AddRelation(const RelationStats &stats) { + for (auto &column : stats.columns) { + columns.emplace(column.binding, column); + } +} + +optional_ptr RelationColumnStatsLookup::Find(ColumnBinding binding) const { + auto entry = columns.find(binding); + if (entry == columns.end()) { + return nullptr; + } + return entry->second.get(); +} + void RelationStats::Verify(const vector &bindings) const { D_ASSERT(!stats_initialized || MatchesBindings(bindings)); } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp index f32bab0e8..b4a983207 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp @@ -71,6 +71,7 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical result.stats_initialized = true; auto bindings = projection.GetColumnBindings(); D_ASSERT(bindings.size() == projection.expressions.size()); + RelationColumnStatsLookup child_columns(child_stats); for (idx_t expression_idx = 0; expression_idx < projection.expressions.size(); expression_idx++) { auto &expression = *projection.expressions[expression_idx]; auto expression_binding = GetChildColumnBinding(expression); @@ -78,7 +79,7 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical if (expression_binding.expression_is_constant) { distinct_count = DistinctCount(MinValue(result.cardinality, 1), DistinctCountSource::EXACT); } else if (expression_binding.FoundColumnRef()) { - auto child_column = child_stats.GetColumnStats(expression_binding.child_binding); + auto child_column = child_columns.Find(expression_binding.child_binding); if (!child_column) { return {}; } @@ -108,6 +109,7 @@ idx_t RelationStatisticsHelper::EstimateDistinctCardinality(const vector RelationStatisticsHelper::ExtractAggregationStats(LogicalAggregate &aggregate, const RelationStats &child_stats) { + RelationColumnStatsLookup child_columns(child_stats); vector cardinality_counts; for (auto &grouping_set : aggregate.grouping_sets) { vector set_counts; @@ -116,7 +118,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica if (group.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) { continue; } - auto column = child_stats.GetColumnStats(group.Cast().Binding()); + auto column = child_columns.Find(group.Cast().Binding()); if (!column) { return {}; } @@ -139,7 +141,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica auto &group = *aggregate.groups[group_idx]; DistinctCount distinct_count(result.cardinality, DistinctCountSource::CARDINALITY); if (group.GetExpressionClass() == ExpressionClass::BOUND_COLUMN_REF) { - auto child_column = child_stats.GetColumnStats(group.Cast().Binding()); + auto child_column = child_columns.Find(group.Cast().Binding()); if (!child_column) { return {}; } @@ -168,8 +170,9 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind result.cardinality = child_stats.cardinality; result.table_name = Identifier(window.GetName()); result.stats_initialized = true; + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : window.GetColumnBindings()) { - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == window.window_index) { @@ -183,10 +186,11 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind return result; } -static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats) { +static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats, + const RelationColumnStatsLookup &child_columns) { switch (target.GetExpressionClass()) { case ExpressionClass::BOUND_COLUMN_REF: { - auto column = child_stats.GetColumnStats(target.Cast().Binding()); + auto column = child_columns.Find(target.Cast().Binding()); return column ? optional(column->distinct_count) : optional(); } case ExpressionClass::BOUND_REF: { @@ -213,8 +217,9 @@ optional RelationStatisticsHelper::ExtractDistinctStats(LogicalDi distinct_counts.push_back(column.distinct_count); } } else { + RelationColumnStatsLookup child_columns(child_stats); for (auto &target : distinct.distinct_targets) { - auto count = GetDistinctTargetCount(*target, child_stats); + auto count = GetDistinctTargetCount(*target, child_stats, child_columns); if (!count) { return result; } @@ -250,14 +255,16 @@ optional RelationStatisticsHelper::ProjectOutputStats(const Relat result.filter_strength = stats.filter_strength; result.stats_initialized = true; result.table_name = stats.table_name; - for (auto &binding : op.GetColumnBindings()) { - auto column = stats.GetColumnStats(binding); + RelationColumnStatsLookup lookup(stats); + auto bindings = op.GetColumnBindings(); + for (auto &binding : bindings) { + auto column = lookup.Find(binding); if (!column) { return {}; } result.columns.push_back(*column); } - result.Verify(op.GetColumnBindings()); + result.Verify(bindings); return result; } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp index f1ba72352..4ac81b70b 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp @@ -11,14 +11,12 @@ ProjectChildStats(LogicalOperator &op, const vector source; - for (auto &child : children) { - source = child.get().GetColumnStats(binding); - if (source) { - break; - } - } + auto source = child_columns.Find(binding); if (!source) { return {}; } @@ -47,11 +45,12 @@ static optional ExtractGetWithChildStats(LogicalGet &get, ClientC const RelationStats &child_stats) { auto result = RelationStatisticsHelper::ExtractGetStats(get, context); result.cardinality = child_stats.cardinality; + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : get.GetColumnBindings()) { if (binding.table_index == get.table_index) { continue; } - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (!child_column) { return {}; } @@ -67,8 +66,9 @@ static optional ExtractUnnestStats(LogicalOperator &op, const Rel result.cardinality = child_stats.cardinality; result.stats_initialized = true; result.table_name = Identifier(op.GetName()); + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : op.GetColumnBindings()) { - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == unnest.unnest_index) { @@ -97,8 +97,9 @@ static optional ExtractComparisonJoinStats(LogicalComparisonJoin mark_result.cardinality = cardinality; mark_result.stats_initialized = true; mark_result.table_name = Identifier(join.GetName()); + RelationColumnStatsLookup left_columns(child_stats[0].get()); for (auto &binding : join.GetColumnBindings()) { - auto column = child_stats[0].get().GetColumnStats(binding); + auto column = left_columns.Find(binding); if (column) { mark_result.columns.emplace_back(binding, column->distinct_count, column->name); } else if (binding.table_index == join.mark_index) { diff --git a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp index c0ab697ba..ef7363cf5 100644 --- a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp +++ b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp @@ -246,9 +246,9 @@ void RemoveDuplicateGroups::VisitAggregate(LogicalAggregate &aggr) { return; } - // This optimizer should run before statistics propagation, so this should be empty - // If it runs after, then group_stats should be updated too - D_ASSERT(aggr.group_stats.empty()); + // Earlier statistics (e.g. from the filter simplification) no longer match the groups - the statistics + // propagation that runs after this optimizer computes them again + aggr.group_stats.clear(); const idx_t num_original_groups = aggr.groups.size(); const idx_t num_aggregate_outputs = aggr.expressions.size(); diff --git a/src/duckdb/src/optimizer/remove_unused_columns.cpp b/src/duckdb/src/optimizer/remove_unused_columns.cpp index 8c9e597c5..35e31126c 100644 --- a/src/duckdb/src/optimizer/remove_unused_columns.cpp +++ b/src/duckdb/src/optimizer/remove_unused_columns.cpp @@ -230,10 +230,11 @@ idx_t BaseColumnPruner::ReplaceBinding(ColumnBinding current_binding, ColumnBind template void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex table_idx, bool replace) { D_ASSERT(mode == RemoveUnusedColumnsMode::APPLY); - idx_t offset = 0; idx_t new_col_idx = 0; + // entries that are referred to are moved to the front of the list, the rest is erased at the end + idx_t kept_count = 0; for (idx_t col_idx = 0; col_idx < list.size(); col_idx++) { - auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx + offset)); + auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx)); if (replace && !root.active_secure_views.empty()) { root.secure_view_replacements[current_binding].clear(); } @@ -243,16 +244,17 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab root.projection_map_replacements[current_binding].clear(); } // this entry is not referred to, erase it from the set of expressions - list.erase_at(col_idx); - offset++; - col_idx--; continue; } + if (kept_count != col_idx) { + list[kept_count] = std::move(list[col_idx]); + } + kept_count++; if (!replace) { continue; } bool should_replace = false; - if (col_idx + offset != new_col_idx) { + if (col_idx != new_col_idx) { should_replace = true; } if (!entry->second.child_columns.empty() && @@ -279,6 +281,7 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab new_col_idx++; } } + list.erase(list.begin() + NumericCast(kept_count), list.end()); } enum class ProjectionBindingResolution : uint8_t { SELECTED, REPLACEMENT }; @@ -1838,9 +1841,20 @@ static bool TryGetCastChild(unique_ptr &expr, optional_ptr *expression) { - //! Check if this is a struct extract wrapped in a cast + if ((*expression)->GetExpressionType() == ExpressionType::OPERATOR_TRY) { + const auto was_inside_try = inside_try; + inside_try = true; + LogicalOperatorVisitor::VisitExpression(expression); + inside_try = was_inside_try; + return; + } + + //! Check if this is a struct extract wrapped in a cast. TRY_CAST is already + //! skipped by TryGetCastChild. A throwing CAST under TRY must also stay put: + //! unused-columns would otherwise emit CAST(struct_extract) in a child + //! projection and leave TRY wrapping only the already-cast column (#25236). optional_ptr> cast_child; - if (TryGetCastChild(*expression, cast_child)) { + if (!inside_try && TryGetCastChild(*expression, cast_child)) { if (HandleExtractExpression(cast_child.get(), expression)) { // already handled return; diff --git a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp index ef00ba652..53de7616b 100644 --- a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp @@ -121,6 +121,12 @@ static bool ConstantCastIsInvertible(ClientContext &context, BoundFunctionExpres auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); return roundtrip && *roundtrip == constant_value; } + if (target_type.id() == LogicalTypeId::DECIMAL && cast_expression.GetReturnType().id() == LogicalTypeId::DECIMAL) { + // a narrowing decimal cast of the constant is fine if the constant round-trips exactly + string error_message; + auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); + return roundtrip && *roundtrip == constant_value; + } if (target_type.id() != LogicalTypeId::DATE || cast_expression.GetReturnType().id() != LogicalTypeId::TIMESTAMP) { return false; } diff --git a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp index 87f0b2fc3..787a721ba 100644 --- a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp @@ -13,6 +13,7 @@ #include "duckdb/optimizer/matcher/expression_matcher.hpp" #include "duckdb/optimizer/expression_rewriter.hpp" #include "duckdb/common/enums/date_part_specifier.hpp" +#include "duckdb/common/types/date.hpp" #include "duckdb/function/function.hpp" #include "duckdb/function/function_binder.hpp" @@ -37,6 +38,27 @@ DateTruncSimplificationRule::DateTruncSimplificationRule(ExpressionRewriter &rew root = std::move(op); } +static bool TruncatesYearsTowardsZero(const BoundConstantExpression &date_part) { + DatePartSpecifier part; + if (!TryGetDatePartSpecifier(StringValue::Get(date_part.GetValue()), part)) { + return true; + } + return part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM; +} + +static bool IsAfterFirstYear(const Value &value) { + if (value.IsNull()) { + return true; + } + auto date_value = value.DefaultTryCastAs(LogicalType::DATE); + if (!date_value || date_value->IsNull()) { + return false; + } + // leave a margin for time zones + return Date::ExtractYear(date_value->GetValue()) > 1; +} + unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, vector> &bindings, bool &changes_made, bool is_root) { auto &expr = bindings[0].get().Cast(); @@ -73,6 +95,12 @@ unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, v return nullptr; } + // Truncating to decades, centuries or millennia does not round years before the common era towards the past + // (e.g. 150 BC truncates to 100 BC), so the rewrite below is only valid for constants after that + if (TruncatesYearsTowardsZero(date_part) && !IsAfterFirstYear(rhs.GetValue())) { + return nullptr; + } + // Check whether trunc(date_part, constant_rhs) = constant_rhs. const bool is_truncated = DateIsTruncated(date_part, rhs); diff --git a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp index e8c97f9bc..11825d66e 100644 --- a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp +++ b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp @@ -51,8 +51,10 @@ static bool GetFiniteDomain(const LogicalType &type, int64_t &lo, int64_t &hi, b return true; case LogicalTypeId::TIMESTAMP: case LogicalTypeId::TIMESTAMP_TZ: - // the ±infinity sentinels keep the finite domain bounded away from INT64_MIN/MAX, so the - // never-true sentinels (hi+1 / lo-1) in the bisection can't overflow + // every non-sentinel int64 is a valid, comparable timestamp (make_timestamp accepts all of them), so the + // finite domain is everything except the ±infinity sentinels; that also keeps the never-true sentinels + // (hi+1 / lo-1) of the bisection away from INT64_MIN/MAX. Not all of these values render, though: see + // IsRenderableBound. lo = timestamp_t::ninfinity().value + 1; hi = timestamp_t::infinity().value - 1; has_infinity = true; @@ -145,10 +147,37 @@ static int64_t LastTruePrefix(PRED pred, int64_t lo, int64_t hi, bool &ok) { return l; } -static unique_ptr MakeColComparison(ExpressionType type, const BoundColumnRefExpression &col, - const LogicalType &col_type, int64_t bound) { - return BoundComparisonExpression::Create(type, col.Copy(), - make_uniq(Value::Numeric(col_type, bound))); +//! Whether a bound with internal representation `bound` can be rendered (Expression::ToString, EXPLAIN). +//! TIMESTAMP values between INT64_MIN and the first midnight of Timestamp::MIN_YEAR are valid and comparable +//! (make_timestamp accepts them) but have no date/time form: Timestamp::Convert overflows on them. +static bool IsRenderableBound(const LogicalType &col_type, int64_t bound) { + switch (col_type.id()) { + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + return bound >= Value::MinimumValue(LogicalType::TIMESTAMP).GetValue().value; + default: + return true; + } +} + +//! Emit `col >= bound` (lower) or `col <= bound` (upper). On types with ±infinity a bound on the edge of the +//! finite domain is written as a strict comparison against the adjacent sentinel instead (`col > -infinity`, +//! `col < infinity`): nothing lies between the two, and the sentinel always renders while the edge of the +//! TIMESTAMP domain does not. Returns nullptr if the bound cannot be rendered - the caller then keeps f(col). +static unique_ptr MakeColBound(bool lower, const BoundColumnRefExpression &col, const LogicalType &col_type, + int64_t bound, int64_t dom_edge, bool has_infinity) { + if (has_infinity && bound == dom_edge) { + auto sentinel = lower ? Value::NegativeInfinity(col_type) : Value::Infinity(col_type); + return BoundComparisonExpression::Create(lower ? ExpressionType::COMPARE_GREATERTHAN + : ExpressionType::COMPARE_LESSTHAN, + col.Copy(), make_uniq(std::move(sentinel))); + } + if (!IsRenderableBound(col_type, bound)) { + return nullptr; + } + return BoundComparisonExpression::Create( + lower ? ExpressionType::COMPARE_GREATERTHANOREQUALTO : ExpressionType::COMPARE_LESSTHANOREQUALTO, col.Copy(), + make_uniq(Value::Numeric(col_type, bound))); } unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector> &bindings, @@ -323,10 +352,16 @@ unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector lower; unique_ptr upper; if (need_lower) { - lower = MakeColComparison(ExpressionType::COMPARE_GREATERTHANOREQUALTO, col, col_type, a); + lower = MakeColBound(true, col, col_type, a, dom_lo, has_infinity); + if (!lower) { + return nullptr; + } } if (need_upper) { - upper = MakeColComparison(ExpressionType::COMPARE_LESSTHANOREQUALTO, col, col_type, b); + upper = MakeColBound(false, col, col_type, b, dom_hi, has_infinity); + if (!upper) { + return nullptr; + } } if (lower && upper) { return make_uniq(ExpressionType::CONJUNCTION_AND, std::move(lower), diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp index 8d3eaa6b3..d937e40e9 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp @@ -13,21 +13,6 @@ namespace duckdb { -static unique_ptr PromoteJoinSide(unique_ptr child, - vector projection_map) { - if (projection_map.empty()) { - return child; - } - auto filter = make_uniq(); - filter->projection_map = std::move(projection_map); - filter->children.push_back(std::move(child)); - if (filter->children[0]->has_estimated_cardinality) { - filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); - } - filter->ResolveOperatorTypes(); - return std::move(filter); -} - bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: @@ -40,10 +25,10 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< // If the filter is always false or Null, the join output is exactly one of its sides: // promote that side together with its projection map so the output layout stays unchanged. case JoinType::RIGHT_ANTI: - node_ptr = PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); return true; case JoinType::ANTI: - node_ptr = PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); return true; case JoinType::LEFT: // anti/left outer join: replace right side with empty node @@ -61,13 +46,32 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< } } +//! Appends the columns of a join side that the join emits to the projection map of its replacement +static void AddProjectedColumns(vector &result, const vector &projection_map, + idx_t column_count, idx_t offset) { + if (projection_map.empty()) { + for (idx_t column_idx = 0; column_idx < column_count; column_idx++) { + result.emplace_back(offset + column_idx); + } + return; + } + for (auto &column_idx : projection_map) { + result.emplace_back(offset + column_idx.GetIndex()); + } +} + bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: case JoinType::SEMI: { + auto &emitted_projection_map = + join.join_type == JoinType::RIGHT_SEMI ? join.right_projection_map : join.left_projection_map; if (join.join_type == JoinType::RIGHT_SEMI) { std::swap(join.children[0], join.children[1]); } + // the cross product also emits the columns of the other side: project them out again + vector projection_map; + AddProjectedColumns(projection_map, emitted_projection_map, join.children[0]->GetColumnBindings().size(), 0); // when the right child has no data, return an empty set // cannot just return the left child because if the right child has no cardinality // then the whole result should be empty. @@ -78,14 +82,21 @@ bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr limit->AddChild(std::move(join.children[1])); auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(limit)); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = std::move(cross_product); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); return true; } case JoinType::INNER: { - // inner, replace with cross product + // inner, replace with cross product - keeping the columns that the join emits + vector projection_map; + if (!join.left_projection_map.empty() || !join.right_projection_map.empty()) { + auto left_count = join.children[0]->GetColumnBindings().size(); + AddProjectedColumns(projection_map, join.left_projection_map, left_count, 0); + AddProjectedColumns(projection_map, join.right_projection_map, join.children[1]->GetColumnBindings().size(), + left_count); + } auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(join.children[1])); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = std::move(cross_product); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); return true; } default: @@ -228,8 +239,12 @@ void StatisticsPropagator::MultiplyCardinalities(unique_ptr &sta return; } stats->estimated_cardinality = MaxValue(stats->estimated_cardinality, new_stats.estimated_cardinality); - auto new_max = Hugeint::Multiply(NumericCast(stats->max_cardinality), - NumericCast(new_stats.max_cardinality)); + hugeint_t new_max; + if (!Hugeint::TryMultiply(Hugeint::Convert(stats->max_cardinality), Hugeint::Convert(new_stats.max_cardinality), + new_max)) { + stats = nullptr; + return; + } if (new_max < NumericLimits::Maximum()) { int64_t result; if (!Hugeint::TryCast(new_max, result)) { diff --git a/src/duckdb/src/optimizer/topn_window_elimination.cpp b/src/duckdb/src/optimizer/topn_window_elimination.cpp index 7808bc49e..586787929 100644 --- a/src/duckdb/src/optimizer/topn_window_elimination.cpp +++ b/src/duckdb/src/optimizer/topn_window_elimination.cpp @@ -900,24 +900,10 @@ TopNWindowElimination::ExtractOptimizerParameters(const LogicalWindow &window, c params.partition_count = window_expr.Partitions().size(); params.order_type = window_expr.OrderBy()[0].type; - VisitExpression(&window_expr.OrderByMutable()[0].expression); - if (params.payload_type == TopNPayloadType::SINGLE_COLUMN && !aggregate_payload.empty()) { - VisitExpression(&aggregate_payload[0]); - } - for (const auto &column_ref : column_references) { - const auto &column_stats = stats->find(column_ref.first); - if (column_stats == stats->end() || column_stats->second->CanHaveNull()) { - params.can_be_null = true; - break; - } - } - // A compound ORDER BY expression (NULLIF, arithmetic, cast, ...) can produce NULLs even when its input - // columns are non-null. Only a direct column reference inherits the column's known nullability, so treat a - // non-column-reference as potentially null to keep the nulls-last aggregate variant. - if (window_expr.OrderBy()[0].expression->GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - params.can_be_null = true; - } - column_references.clear(); + // The statistics of a column binding are those at the top of the plan: a filter above the window (e.g. on the + // result of a decorrelated LATERAL join) can have narrowed them to non-NULL although the input of the window + // contains NULLs. They cannot tell whether the window input can be NULL, so keep the NULL-preserving variants. + params.can_be_null = true; return params; } diff --git a/src/duckdb/src/parallel/pipeline_executor.cpp b/src/duckdb/src/parallel/pipeline_executor.cpp index 3f4cfbc3d..e61574823 100644 --- a/src/duckdb/src/parallel/pipeline_executor.cpp +++ b/src/duckdb/src/parallel/pipeline_executor.cpp @@ -62,7 +62,7 @@ PipelineExecutor::PipelineExecutor(ClientContext &context_p, Pipeline &pipeline_ auto ¤t_operator = pipeline.operators[i].get(); auto chunk = make_uniq(); - chunk->Initialize(BufferAllocator::Get(context.client), prev_operator.GetTypes()); + prev_operator.InitializeOutputChunk(BufferAllocator::Get(context.client), *chunk); intermediate_chunks.push_back(std::move(chunk)); auto op_state = current_operator.GetOperatorState(context); @@ -896,7 +896,7 @@ void PipelineExecutor::SampleProgress() { void PipelineExecutor::InitializeChunk(DataChunk &chunk) { auto &last_op = pipeline.operators.empty() ? *pipeline.source : pipeline.operators.back().get(); - chunk.Initialize(BufferAllocator::Get(context.client), last_op.GetTypes()); + last_op.InitializeOutputChunk(BufferAllocator::Get(context.client), chunk); } void PipelineExecutor::StartOperator(PhysicalOperator &op) { diff --git a/src/duckdb/src/parallel/scan_read_ahead.cpp b/src/duckdb/src/parallel/scan_read_ahead.cpp index 61bd67e25..a789974ba 100644 --- a/src/duckdb/src/parallel/scan_read_ahead.cpp +++ b/src/duckdb/src/parallel/scan_read_ahead.cpp @@ -46,12 +46,6 @@ bool ReadAheadJobCompletion::TryPark(TableFunctionInput &data_p) { return true; } -//! Throw when the query was interrupted, otherwise yield the thread -static void ScanReadAheadYield(ClientContext &context) { - context.InterruptCheck(); - TaskScheduler::YieldThread(); -} - void ReadAheadJobCompletion::WaitForIO() { shared_ptr task; while (pending_io_tasks.load() > 0) { @@ -70,8 +64,9 @@ void ReadAheadJobCompletion::WaitForIO() { class ReadAheadIOTask : public BaseExecutorTask { public: ReadAheadIOTask(TaskExecutor &executor, unique_ptr task_p, - shared_ptr completion_p) - : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)) { + shared_ptr completion_p, ScanReadAhead &read_ahead_p) + : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)), + read_ahead(read_ahead_p) { completion->AddIOTask(); } ~ReadAheadIOTask() override { @@ -80,13 +75,20 @@ class ReadAheadIOTask : public BaseExecutorTask { } void ExecuteTask() override { - // Does the actual IO - task->Execute(); + try { + // Does the actual IO + task->Execute(); + } catch (std::exception &ex) { + // recording the error through the read-ahead also wakes the threads waiting for progress + read_ahead.PushError(ErrorData(ex)); + } } private: unique_ptr task; shared_ptr completion; + //! Outlives the task, the read-ahead drains its executor before it is destroyed + ScanReadAhead &read_ahead; }; //! Async task that opens one file ahead of decoding and releases the pending-open count when destroyed @@ -199,13 +201,19 @@ unique_ptr ScanReadAhead::ClaimJob() { auto job = std::move(ready_queue.front()); ready_queue.pop_front(); ReleaseSlot(); + NotifyProgress(false); return job; } void ScanReadAhead::WaitForJob(ScanReadAheadJob &job) { job.SettleIO(); // the job's I/O has completed, release its budget charge - pending_io_bytes -= job.io_bytes; + const auto budget = backlog_budget.load(); + const auto previous = pending_io_bytes.fetch_sub(job.io_bytes); + if (previous >= budget && previous - job.io_bytes < budget) { + // the scan is no longer over budget, a waiting thread can produce + SignalProgress(false); + } job.io_bytes = 0; ThrowIfError(); } @@ -214,7 +222,9 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc const ProduceJobCallback &claim_and_schedule, unique_ptr &job) { D_ASSERT(!job); + optional yield_deadline; while (true) { + const auto epoch = ProgressEpoch(); // keep the queue full before claiming while (TryProduceJob(claim_and_schedule)) { } @@ -232,8 +242,18 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc } } if (!job) { - // another thread is between claiming an assignment and pushing its job, wait for it - ScanReadAheadYield(context); + // another thread is between claiming an assignment and pushing its job, a short claim is caught by + // yielding and a long one is waited for + const auto now = std::chrono::steady_clock::now(); + if (!yield_deadline) { + yield_deadline = now + std::chrono::microseconds(PRODUCER_YIELD_MICROS); + } + if (now < *yield_deadline) { + context.InterruptCheck(); + TaskScheduler::YieldThread(); + } else { + WaitForProgress(context, epoch); + } continue; } // park until the job's scheduled I/O completes, the last I/O task to finish wakes the scan task @@ -250,14 +270,22 @@ void ScanReadAhead::SetDone() { done = true; } +idx_t ScanReadAhead::ReservationDepth() const { + // over budget one job stays queued, claimed jobs hold the budget until their task resumes + return pending_io_bytes.load() >= backlog_budget.load() ? 1 : read_ahead_depth; +} + bool ScanReadAhead::TryReserveSlot() { // take the minimum job charge up front so concurrent producers cannot all pass the budget check together const bool over_budget = pending_io_bytes.fetch_add(MINIMUM_JOB_IO_CHARGE) >= backlog_budget.load(); - // over budget one job stays queued, claimed jobs hold the budget until their task resumes const idx_t depth = over_budget ? 1 : read_ahead_depth; if (active_jobs.fetch_add(1) >= depth) { --active_jobs; pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; + if (active_jobs.load() < ReservationDepth()) { + // a concurrent attempt failed on this attempt's charge or slot, let it retry + SignalProgress(false); + } return false; } ++active_producers; @@ -272,7 +300,7 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorio_bytes += task->GetIOSize(); - read_tasks.push_back(make_uniq(*executor, std::move(task), completion)); + read_tasks.push_back(make_uniq(*executor, std::move(task), completion, *this)); } job->io_bytes = MaxValue(job->io_bytes, MINIMUM_JOB_IO_CHARGE); // the minimum charge was taken by TryReserveSlot, charge the remainder @@ -284,22 +312,27 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorScheduleTasks(std::move(read_tasks)); lock_guard guard(lock); if (memory_governor) { - memory_governor->UpdateReservation(pending_io_bytes.load()); + // Reserve room for the next job before admission checks the budget. + memory_governor->UpdateReservation(pending_io_bytes.load() + MINIMUM_JOB_IO_CHARGE); backlog_budget = memory_governor->BackpressureBudget(); } // producers push concurrently, so admit jobs to the queue in batch-index order if (job->batch_index < next_batch_index || !pending_jobs.emplace(job->batch_index, std::move(job)).second) { throw InternalException("Read-ahead jobs must have unique batch indexes, assigned densely from 0"); } + idx_t admitted = 0; while (!pending_jobs.empty() && pending_jobs.begin()->first == next_batch_index) { ready_queue.push_back(std::move(pending_jobs.begin()->second)); pending_jobs.erase(pending_jobs.begin()); next_batch_index++; + admitted++; } + NotifyProgress(admitted > 1); } void ScanReadAhead::PushError(ErrorData error) { executor->PushError(std::move(error)); + SignalProgress(true); } void ScanReadAhead::ScheduleFileOpen(std::function open_fn, std::function cancel_fn) { @@ -331,4 +364,47 @@ void ScanReadAhead::ReleaseSlot() { active_jobs--; } +void ScanReadAhead::WakeWaiters(bool wake_all) { + if (wake_all) { + progress.notify_all(); + } else { + progress.notify_one(); + } +} + +void ScanReadAhead::NotifyProgress(bool wake_all) { + ++progress_epoch; + if (waiting_threads.load() > 0) { + WakeWaiters(wake_all); + } +} + +void ScanReadAhead::SignalProgress(bool wake_all) { + ++progress_epoch; + if (waiting_threads.load() == 0) { + return; + } + { + // a waiter that saw the old epoch is blocked in the wait once the lock is free + lock_guard guard(lock); + } + WakeWaiters(wake_all); +} + +void ScanReadAhead::WaitForProgress(ClientContext &context, idx_t epoch) { + context.BlockingInterruptCheck(); + { + unique_lock guard(lock); + ++waiting_threads; + // the timeout bounds the interrupt latency + progress.wait_for(guard, std::chrono::milliseconds(10), [&]() { return progress_epoch.load() != epoch; }); + --waiting_threads; + } + context.BlockingInterruptCheck(); +} + +idx_t ScanReadAhead::ProgressEpoch() const { + return progress_epoch.load(); +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/expression/value_expression.cpp b/src/duckdb/src/parser/expression/value_expression.cpp index 169b8531d..a845e06ce 100644 --- a/src/duckdb/src/parser/expression/value_expression.cpp +++ b/src/duckdb/src/parser/expression/value_expression.cpp @@ -237,6 +237,15 @@ static unique_ptr NestedValueExpression(const LogicalType &typ auto result = ValueFunction("map", std::move(arguments)); return type.HasAlias() ? CastTo(type, std::move(result)) : std::move(result); } + if (type.id() == LogicalTypeId::UNION && UnionType::GetMemberCount(type) == 1) { + auto &member_type = UnionType::GetMemberType(type, 0); + auto child = NestedValueExpression(member_type, value ? &UnionValue::GetValue(*value) : nullptr); + vector arguments; + arguments.emplace_back(UnionType::GetMemberName(type, 0), std::move(child)); + unique_ptr result = + make_uniq(QualifiedName("system", "main", "union_value"), std::move(arguments)); + return type.HasAlias() ? CastTo(type, std::move(result)) : std::move(result); + } vector arguments; child_list_t child_types; optional_ptr> children; diff --git a/src/duckdb/src/parser/peg/autocomplete_core.cpp b/src/duckdb/src/parser/peg/autocomplete_core.cpp index 076aad940..a637e7ba1 100644 --- a/src/duckdb/src/parser/peg/autocomplete_core.cpp +++ b/src/duckdb/src/parser/peg/autocomplete_core.cpp @@ -229,6 +229,7 @@ vector GenerateAutoCompleteSuggestions(AutoCompleteCatal vector new_suggestions; switch (suggestion.type) { case SuggestionState::SUGGEST_VARIABLE: + case SuggestionState::SUGGEST_BARE_LABEL: // variables have no suggestions available break; case SuggestionState::SUGGEST_KEYWORD: diff --git a/src/duckdb/src/parser/peg/compiled_grammar.cpp b/src/duckdb/src/parser/peg/compiled_grammar.cpp index 623048f7e..3cf3f2878 100644 --- a/src/duckdb/src/parser/peg/compiled_grammar.cpp +++ b/src/duckdb/src/parser/peg/compiled_grammar.cpp @@ -55,6 +55,8 @@ terminal_rule_overrides_t ParsedGrammar::BuildTerminalRuleOverrides(const PEGKey make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); AddTerminalRuleOverride(overrides, "ReservedIdentifier", make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); + AddTerminalRuleOverride(overrides, "BareColLabel", + make_uniq(SuggestionState::SUGGEST_BARE_LABEL, keyword_helper)); AddTerminalRuleOverride(overrides, "CatalogName", make_uniq(SuggestionState::SUGGEST_CATALOG_NAME, keyword_helper)); AddTerminalRuleOverride(overrides, "SchemaName", diff --git a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp index 732eb3d66..3517431b4 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp @@ -8,6 +8,7 @@ static constexpr keyword_categories_t KEYWORD_RESERVED = keyword_categories_t::C static constexpr keyword_categories_t KEYWORD_TYPE_FUNC = keyword_categories_t::CreateCategory(2); static constexpr keyword_categories_t KEYWORD_COL_NAME = keyword_categories_t::CreateCategory(3); static constexpr keyword_categories_t KEYWORD_TYPE_NAME = keyword_categories_t::CreateCategory(4); +static constexpr keyword_categories_t KEYWORD_BARE_LABEL = keyword_categories_t::CreateCategory(5); LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t literal_id) const { keyword_categories_t flags; @@ -26,11 +27,16 @@ LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t liter if (typename_keyword_map.count(text)) { flags |= KEYWORD_TYPE_NAME; } + if (bare_label_keyword_map.count(text)) { + flags |= KEYWORD_BARE_LABEL; + } return LiteralInfo(literal_id, flags); } keyword_categories_t DefaultKeywordMaps::GetIdentifierMask(SuggestionState type) { switch (type) { + case SuggestionState::SUGGEST_BARE_LABEL: + return KEYWORD_BARE_LABEL; case SuggestionState::SUGGEST_TYPE_NAME: return KEYWORD_UNRESERVED | KEYWORD_TYPE_NAME; case SuggestionState::SUGGEST_SCALAR_FUNCTION_NAME: diff --git a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp index 528d26657..f1c75cd79 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp @@ -48,16 +48,32 @@ static void PopulateKeywordMap(const ParsedGrammar &grammar, const string &root_ static DefaultKeywordMaps BuildKeywordMaps(const ParsedGrammar &grammar) { DefaultKeywordMaps keyword_maps; - unordered_map> mapping { - {"ReservedKeyword", keyword_maps.reserved_keyword_map}, - {"UnreservedKeyword", keyword_maps.unreserved_keyword_map}, - {"ColumnNameKeyword", keyword_maps.colname_keyword_map}, - {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map}, - {"TypeNameKeyword", keyword_maps.typename_keyword_map}, + struct KeywordRuleMapping { + const char *rule_name; + reference keyword_map; + bool optional; + }; + // grammars without a bare-label rule only accept non-keywords as bare labels + const KeywordRuleMapping mapping[] = { + {"ReservedKeyword", keyword_maps.reserved_keyword_map, false}, + {"UnreservedKeyword", keyword_maps.unreserved_keyword_map, false}, + {"ColumnNameKeyword", keyword_maps.colname_keyword_map, false}, + {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map, false}, + {"TypeNameKeyword", keyword_maps.typename_keyword_map, false}, + {"BareLabelKeyword", keyword_maps.bare_label_keyword_map, true}, }; for (auto &entry : mapping) { + if (entry.optional && !grammar.GetRule(entry.rule_name)) { + continue; + } case_insensitive_set_t active_rules; - PopulateKeywordMap(grammar, entry.first, entry.first, entry.second.get(), active_rules); + PopulateKeywordMap(grammar, entry.rule_name, entry.rule_name, entry.keyword_map.get(), active_rules); + } + // the bare-label classification is orthogonal: every entry must also be an unreserved or column-name keyword + for (auto &keyword : keyword_maps.bare_label_keyword_map) { + if (!keyword_maps.unreserved_keyword_map.count(keyword) && !keyword_maps.colname_keyword_map.count(keyword)) { + throw InvalidInputException("Bare label keyword '%s' is not an unreserved or column-name keyword", keyword); + } } return keyword_maps; } diff --git a/src/duckdb/src/parser/peg/keyword_map.cpp b/src/duckdb/src/parser/peg/keyword_map.cpp index f6d8b83f7..160b0cd36 100644 --- a/src/duckdb/src/parser/peg/keyword_map.cpp +++ b/src/duckdb/src/parser/peg/keyword_map.cpp @@ -10,6 +10,7 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { auto &colname_keyword_map = keyword_maps.colname_keyword_map; auto &typefunc_keyword_map = keyword_maps.typefunc_keyword_map; auto &typename_keyword_map = keyword_maps.typename_keyword_map; + auto &bare_label_keyword_map = keyword_maps.bare_label_keyword_map; // Populating reserved_keyword_map reserved_keyword_map.insert("all"); @@ -560,6 +561,366 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { typename_keyword_map.insert("try_cast"); typename_keyword_map.insert("unpack"); typename_keyword_map.insert("verbose"); + + // Populating bare_label_keyword_map + bare_label_keyword_map.insert("abort"); + bare_label_keyword_map.insert("absolute"); + bare_label_keyword_map.insert("access"); + bare_label_keyword_map.insert("action"); + bare_label_keyword_map.insert("add"); + bare_label_keyword_map.insert("admin"); + bare_label_keyword_map.insert("after"); + bare_label_keyword_map.insert("aggregate"); + bare_label_keyword_map.insert("also"); + bare_label_keyword_map.insert("alter"); + bare_label_keyword_map.insert("always"); + bare_label_keyword_map.insert("approx"); + bare_label_keyword_map.insert("assertion"); + bare_label_keyword_map.insert("assignment"); + bare_label_keyword_map.insert("attach"); + bare_label_keyword_map.insert("attribute"); + bare_label_keyword_map.insert("backward"); + bare_label_keyword_map.insert("before"); + bare_label_keyword_map.insert("begin"); + bare_label_keyword_map.insert("between"); + bare_label_keyword_map.insert("bigint"); + bare_label_keyword_map.insert("bit"); + bare_label_keyword_map.insert("boolean"); + bare_label_keyword_map.insert("cache"); + bare_label_keyword_map.insert("call"); + bare_label_keyword_map.insert("called"); + bare_label_keyword_map.insert("cascade"); + bare_label_keyword_map.insert("cascaded"); + bare_label_keyword_map.insert("catalog"); + bare_label_keyword_map.insert("chain"); + bare_label_keyword_map.insert("char"); + bare_label_keyword_map.insert("character"); + bare_label_keyword_map.insert("characteristics"); + bare_label_keyword_map.insert("checkpoint"); + bare_label_keyword_map.insert("class"); + bare_label_keyword_map.insert("close"); + bare_label_keyword_map.insert("cluster"); + bare_label_keyword_map.insert("coalesce"); + bare_label_keyword_map.insert("columns"); + bare_label_keyword_map.insert("comment"); + bare_label_keyword_map.insert("comments"); + bare_label_keyword_map.insert("commit"); + bare_label_keyword_map.insert("committed"); + bare_label_keyword_map.insert("compression"); + bare_label_keyword_map.insert("configuration"); + bare_label_keyword_map.insert("conflict"); + bare_label_keyword_map.insert("connect"); + bare_label_keyword_map.insert("connection"); + bare_label_keyword_map.insert("constraints"); + bare_label_keyword_map.insert("content"); + bare_label_keyword_map.insert("continue"); + bare_label_keyword_map.insert("conversion"); + bare_label_keyword_map.insert("copy"); + bare_label_keyword_map.insert("cost"); + bare_label_keyword_map.insert("csv"); + bare_label_keyword_map.insert("cube"); + bare_label_keyword_map.insert("current"); + bare_label_keyword_map.insert("cursor"); + bare_label_keyword_map.insert("cycle"); + bare_label_keyword_map.insert("data"); + bare_label_keyword_map.insert("database"); + bare_label_keyword_map.insert("deallocate"); + bare_label_keyword_map.insert("dec"); + bare_label_keyword_map.insert("decimal"); + bare_label_keyword_map.insert("declare"); + bare_label_keyword_map.insert("defaults"); + bare_label_keyword_map.insert("deferred"); + bare_label_keyword_map.insert("definer"); + bare_label_keyword_map.insert("delete"); + bare_label_keyword_map.insert("delimiter"); + bare_label_keyword_map.insert("delimiters"); + bare_label_keyword_map.insert("depends"); + bare_label_keyword_map.insert("destroy"); + bare_label_keyword_map.insert("detach"); + bare_label_keyword_map.insert("dictionary"); + bare_label_keyword_map.insert("disable"); + bare_label_keyword_map.insert("discard"); + bare_label_keyword_map.insert("disconnect"); + bare_label_keyword_map.insert("distance"); + bare_label_keyword_map.insert("document"); + bare_label_keyword_map.insert("domain"); + bare_label_keyword_map.insert("double"); + bare_label_keyword_map.insert("drop"); + bare_label_keyword_map.insert("each"); + bare_label_keyword_map.insert("enable"); + bare_label_keyword_map.insert("encoding"); + bare_label_keyword_map.insert("encrypted"); + bare_label_keyword_map.insert("enum"); + bare_label_keyword_map.insert("error"); + bare_label_keyword_map.insert("event"); + bare_label_keyword_map.insert("exact"); + bare_label_keyword_map.insert("excluding"); + bare_label_keyword_map.insert("exclusive"); + bare_label_keyword_map.insert("execute"); + bare_label_keyword_map.insert("exists"); + bare_label_keyword_map.insert("explain"); + bare_label_keyword_map.insert("export"); + bare_label_keyword_map.insert("extension"); + bare_label_keyword_map.insert("extensions"); + bare_label_keyword_map.insert("external"); + bare_label_keyword_map.insert("extract"); + bare_label_keyword_map.insert("family"); + bare_label_keyword_map.insert("first"); + bare_label_keyword_map.insert("float"); + bare_label_keyword_map.insert("following"); + bare_label_keyword_map.insert("force"); + bare_label_keyword_map.insert("forward"); + bare_label_keyword_map.insert("function"); + bare_label_keyword_map.insert("functions"); + bare_label_keyword_map.insert("generated"); + bare_label_keyword_map.insert("global"); + bare_label_keyword_map.insert("grant"); + bare_label_keyword_map.insert("granted"); + bare_label_keyword_map.insert("grouping"); + bare_label_keyword_map.insert("grouping_id"); + bare_label_keyword_map.insert("groups"); + bare_label_keyword_map.insert("handler"); + bare_label_keyword_map.insert("header"); + bare_label_keyword_map.insert("hold"); + bare_label_keyword_map.insert("identity"); + bare_label_keyword_map.insert("if"); + bare_label_keyword_map.insert("immediate"); + bare_label_keyword_map.insert("immutable"); + bare_label_keyword_map.insert("implicit"); + bare_label_keyword_map.insert("import"); + bare_label_keyword_map.insert("include"); + bare_label_keyword_map.insert("including"); + bare_label_keyword_map.insert("increment"); + bare_label_keyword_map.insert("index"); + bare_label_keyword_map.insert("indexes"); + bare_label_keyword_map.insert("inherit"); + bare_label_keyword_map.insert("inherits"); + bare_label_keyword_map.insert("inline"); + bare_label_keyword_map.insert("inout"); + bare_label_keyword_map.insert("input"); + bare_label_keyword_map.insert("insensitive"); + bare_label_keyword_map.insert("insert"); + bare_label_keyword_map.insert("install"); + bare_label_keyword_map.insert("instead"); + bare_label_keyword_map.insert("int"); + bare_label_keyword_map.insert("integer"); + bare_label_keyword_map.insert("interval"); + bare_label_keyword_map.insert("invoker"); + bare_label_keyword_map.insert("isolation"); + bare_label_keyword_map.insert("json"); + bare_label_keyword_map.insert("key"); + bare_label_keyword_map.insert("label"); + bare_label_keyword_map.insert("language"); + bare_label_keyword_map.insert("large"); + bare_label_keyword_map.insert("last"); + bare_label_keyword_map.insert("leakproof"); + bare_label_keyword_map.insert("level"); + bare_label_keyword_map.insert("listen"); + bare_label_keyword_map.insert("load"); + bare_label_keyword_map.insert("local"); + bare_label_keyword_map.insert("location"); + bare_label_keyword_map.insert("lock"); + bare_label_keyword_map.insert("locked"); + bare_label_keyword_map.insert("logged"); + bare_label_keyword_map.insert("macro"); + bare_label_keyword_map.insert("map"); + bare_label_keyword_map.insert("mapping"); + bare_label_keyword_map.insert("match"); + bare_label_keyword_map.insert("matched"); + bare_label_keyword_map.insert("materialized"); + bare_label_keyword_map.insert("maxvalue"); + bare_label_keyword_map.insert("merge"); + bare_label_keyword_map.insert("method"); + bare_label_keyword_map.insert("minvalue"); + bare_label_keyword_map.insert("mode"); + bare_label_keyword_map.insert("move"); + bare_label_keyword_map.insert("name"); + bare_label_keyword_map.insert("names"); + bare_label_keyword_map.insert("national"); + bare_label_keyword_map.insert("nchar"); + bare_label_keyword_map.insert("nearest"); + bare_label_keyword_map.insert("new"); + bare_label_keyword_map.insert("next"); + bare_label_keyword_map.insert("no"); + bare_label_keyword_map.insert("none"); + bare_label_keyword_map.insert("nothing"); + bare_label_keyword_map.insert("notify"); + bare_label_keyword_map.insert("nowait"); + bare_label_keyword_map.insert("nullif"); + bare_label_keyword_map.insert("nulls"); + bare_label_keyword_map.insert("numeric"); + bare_label_keyword_map.insert("object"); + bare_label_keyword_map.insert("of"); + bare_label_keyword_map.insert("off"); + bare_label_keyword_map.insert("oids"); + bare_label_keyword_map.insert("old"); + bare_label_keyword_map.insert("operator"); + bare_label_keyword_map.insert("option"); + bare_label_keyword_map.insert("options"); + bare_label_keyword_map.insert("ordinality"); + bare_label_keyword_map.insert("others"); + bare_label_keyword_map.insert("out"); + bare_label_keyword_map.insert("overlay"); + bare_label_keyword_map.insert("overriding"); + bare_label_keyword_map.insert("owned"); + bare_label_keyword_map.insert("owner"); + bare_label_keyword_map.insert("parallel"); + bare_label_keyword_map.insert("parser"); + bare_label_keyword_map.insert("partial"); + bare_label_keyword_map.insert("partition"); + bare_label_keyword_map.insert("partitioned"); + bare_label_keyword_map.insert("passing"); + bare_label_keyword_map.insert("password"); + bare_label_keyword_map.insert("percent"); + bare_label_keyword_map.insert("persistent"); + bare_label_keyword_map.insert("plans"); + bare_label_keyword_map.insert("policy"); + bare_label_keyword_map.insert("position"); + bare_label_keyword_map.insert("pragma"); + bare_label_keyword_map.insert("preceding"); + bare_label_keyword_map.insert("prepare"); + bare_label_keyword_map.insert("prepared"); + bare_label_keyword_map.insert("preserve"); + bare_label_keyword_map.insert("prior"); + bare_label_keyword_map.insert("privileges"); + bare_label_keyword_map.insert("procedural"); + bare_label_keyword_map.insert("procedure"); + bare_label_keyword_map.insert("program"); + bare_label_keyword_map.insert("publication"); + bare_label_keyword_map.insert("quote"); + bare_label_keyword_map.insert("range"); + bare_label_keyword_map.insert("read"); + bare_label_keyword_map.insert("real"); + bare_label_keyword_map.insert("reassign"); + bare_label_keyword_map.insert("recheck"); + bare_label_keyword_map.insert("recursive"); + bare_label_keyword_map.insert("ref"); + bare_label_keyword_map.insert("referencing"); + bare_label_keyword_map.insert("refresh"); + bare_label_keyword_map.insert("register"); + bare_label_keyword_map.insert("reindex"); + bare_label_keyword_map.insert("relative"); + bare_label_keyword_map.insert("release"); + bare_label_keyword_map.insert("repeatable"); + bare_label_keyword_map.insert("replica"); + bare_label_keyword_map.insert("reset"); + bare_label_keyword_map.insert("resource"); + bare_label_keyword_map.insert("resources"); + bare_label_keyword_map.insert("restart"); + bare_label_keyword_map.insert("restrict"); + bare_label_keyword_map.insert("returns"); + bare_label_keyword_map.insert("revoke"); + bare_label_keyword_map.insert("role"); + bare_label_keyword_map.insert("rollback"); + bare_label_keyword_map.insert("rollup"); + bare_label_keyword_map.insert("row"); + bare_label_keyword_map.insert("rows"); + bare_label_keyword_map.insert("rule"); + bare_label_keyword_map.insert("sample"); + bare_label_keyword_map.insert("savepoint"); + bare_label_keyword_map.insert("schema"); + bare_label_keyword_map.insert("schemas"); + bare_label_keyword_map.insert("scope"); + bare_label_keyword_map.insert("scroll"); + bare_label_keyword_map.insert("search"); + bare_label_keyword_map.insert("secret"); + bare_label_keyword_map.insert("secure"); + bare_label_keyword_map.insert("security"); + bare_label_keyword_map.insert("sequence"); + bare_label_keyword_map.insert("sequences"); + bare_label_keyword_map.insert("serializable"); + bare_label_keyword_map.insert("server"); + bare_label_keyword_map.insert("session"); + bare_label_keyword_map.insert("set"); + bare_label_keyword_map.insert("setof"); + bare_label_keyword_map.insert("sets"); + bare_label_keyword_map.insert("share"); + bare_label_keyword_map.insert("similarity"); + bare_label_keyword_map.insert("simple"); + bare_label_keyword_map.insert("skip"); + bare_label_keyword_map.insert("smallint"); + bare_label_keyword_map.insert("snapshot"); + bare_label_keyword_map.insert("sorted"); + bare_label_keyword_map.insert("source"); + bare_label_keyword_map.insert("sql"); + bare_label_keyword_map.insert("stable"); + bare_label_keyword_map.insert("standalone"); + bare_label_keyword_map.insert("start"); + bare_label_keyword_map.insert("statement"); + bare_label_keyword_map.insert("statistics"); + bare_label_keyword_map.insert("stdin"); + bare_label_keyword_map.insert("stdout"); + bare_label_keyword_map.insert("storage"); + bare_label_keyword_map.insert("stored"); + bare_label_keyword_map.insert("strict"); + bare_label_keyword_map.insert("strip"); + bare_label_keyword_map.insert("struct"); + bare_label_keyword_map.insert("subscription"); + bare_label_keyword_map.insert("substring"); + bare_label_keyword_map.insert("sysid"); + bare_label_keyword_map.insert("system"); + bare_label_keyword_map.insert("tables"); + bare_label_keyword_map.insert("tablespace"); + bare_label_keyword_map.insert("target"); + bare_label_keyword_map.insert("temp"); + bare_label_keyword_map.insert("template"); + bare_label_keyword_map.insert("temporary"); + bare_label_keyword_map.insert("text"); + bare_label_keyword_map.insert("ties"); + bare_label_keyword_map.insert("time"); + bare_label_keyword_map.insert("timestamp"); + bare_label_keyword_map.insert("transaction"); + bare_label_keyword_map.insert("transform"); + bare_label_keyword_map.insert("treat"); + bare_label_keyword_map.insert("trigger"); + bare_label_keyword_map.insert("trim"); + bare_label_keyword_map.insert("truncate"); + bare_label_keyword_map.insert("trusted"); + bare_label_keyword_map.insert("try_cast"); + bare_label_keyword_map.insert("tuple"); + bare_label_keyword_map.insert("type"); + bare_label_keyword_map.insert("types"); + bare_label_keyword_map.insert("unbounded"); + bare_label_keyword_map.insert("uncommitted"); + bare_label_keyword_map.insert("unencrypted"); + bare_label_keyword_map.insert("unknown"); + bare_label_keyword_map.insert("unlisten"); + bare_label_keyword_map.insert("unlogged"); + bare_label_keyword_map.insert("until"); + bare_label_keyword_map.insert("update"); + bare_label_keyword_map.insert("use"); + bare_label_keyword_map.insert("user"); + bare_label_keyword_map.insert("vacuum"); + bare_label_keyword_map.insert("valid"); + bare_label_keyword_map.insert("validate"); + bare_label_keyword_map.insert("validator"); + bare_label_keyword_map.insert("value"); + bare_label_keyword_map.insert("values"); + bare_label_keyword_map.insert("varchar"); + bare_label_keyword_map.insert("variable"); + bare_label_keyword_map.insert("version"); + bare_label_keyword_map.insert("view"); + bare_label_keyword_map.insert("views"); + bare_label_keyword_map.insert("virtual"); + bare_label_keyword_map.insert("volatile"); + bare_label_keyword_map.insert("whitespace"); + bare_label_keyword_map.insert("work"); + bare_label_keyword_map.insert("wrapper"); + bare_label_keyword_map.insert("write"); + bare_label_keyword_map.insert("xml"); + bare_label_keyword_map.insert("xmlattributes"); + bare_label_keyword_map.insert("xmlconcat"); + bare_label_keyword_map.insert("xmlelement"); + bare_label_keyword_map.insert("xmlexists"); + bare_label_keyword_map.insert("xmlforest"); + bare_label_keyword_map.insert("xmlnamespaces"); + bare_label_keyword_map.insert("xmlparse"); + bare_label_keyword_map.insert("xmlpi"); + bare_label_keyword_map.insert("xmlroot"); + bare_label_keyword_map.insert("xmlserialize"); + bare_label_keyword_map.insert("xmltable"); + bare_label_keyword_map.insert("yes"); + bare_label_keyword_map.insert("zone"); return keyword_maps; } } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/matcher_factory.cpp b/src/duckdb/src/parser/peg/matcher_factory.cpp index 77e1ca472..84f9b1d2d 100644 --- a/src/duckdb/src/parser/peg/matcher_factory.cpp +++ b/src/duckdb/src/parser/peg/matcher_factory.cpp @@ -220,6 +220,7 @@ Matcher &MatcherFactory::CreateRootMatcher(const string &root_rule) { // START GENERATED PACKRAT MEMOIZED RULES //===--------------------------------------------------------------------===// AddPackratMemoizedRule("Expression"); + AddPackratMemoizedRule("TableRef"); AddPackratMemoizedRule("Identifier"); AddPackratMemoizedRule("ColId"); AddPackratMemoizedRule("CatalogQualification"); diff --git a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp index 408bf3963..9f8df5442 100644 --- a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp +++ b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp @@ -442,8 +442,9 @@ bool Tokenizer::TokenizeInputInternal(TokenizerBehavior &behavior) const { if (CharacterIsInitialNumber(c)) { break; // Continue tokenizing } - // Allow underscore only when immediately followed by a digit (no consecutive underscores) - if (c == '_' && i + 1 < sql.size() && CharacterIsInitialNumber(sql[i + 1])) { + // A digit separator must sit between two digits + if (c == '_' && StringUtil::CharacterIsDigit(sql[i - 1]) && i + 1 < sql.size() && + StringUtil::CharacterIsDigit(sql[i + 1])) { break; } diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp index cc687f4b9..abd92a91b 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp @@ -162,9 +162,8 @@ PEGTransformerFactory::PEGTransformerFactory(ParsedGrammar &grammar_p) : grammar //===--------------------------------------------------------------------===// grammar.SetExpressionDepth("SingleExpression", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("Type", ExpressionDepthKind::NESTING); + grammar.SetExpressionDepth("NestedType", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("LambdaArrowExpression", ExpressionDepthKind::CHAIN); - grammar.SetExpressionDepth("LogicalOrExpression", ExpressionDepthKind::CHAIN); - grammar.SetExpressionDepth("LogicalAndExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("NotExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsExpressionContinuation", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsDistinctFromExpression", ExpressionDepthKind::CHAIN); diff --git a/src/duckdb/src/parser/peg/transformer/transform_common.cpp b/src/duckdb/src/parser/peg/transformer/transform_common.cpp index 0dc6dd071..b466696ab 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_common.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_common.cpp @@ -53,6 +53,12 @@ string PEGTransformerFactory::TransformIdentifierOrKeyword(PEGTransformer &trans LogicalType PEGTransformerFactory::TransformType(PEGTransformer &transformer, unique_ptr type_variations, const optional> &array_bounds) { + return LogicalType::UNBOUND(TransformNestedType(transformer, std::move(type_variations), array_bounds)); +} + +unique_ptr PEGTransformerFactory::TransformNestedType(PEGTransformer &transformer, + unique_ptr type_variations, + const optional> &array_bounds) { auto array_depth_guard = transformer.StackCheck(array_bounds ? array_bounds->size() : 0); auto type = std::move(type_variations); if (array_bounds) { @@ -68,7 +74,7 @@ LogicalType PEGTransformerFactory::TransformType(PEGTransformer &transformer, } } } - return LogicalType::UNBOUND(std::move(type)); + return type; } int64_t PEGTransformerFactory::TransformArrayKeyword(PEGTransformer &transformer) { @@ -132,6 +138,9 @@ PEGTransformerFactory::TransformTimeType(PEGTransformer &transformer, const Logi return make_uniq(Identifier(EnumUtil::ToString(LogicalType::TIMESTAMP)), vector> {}); } + if (with_timezone) { + throw ParserException("TIMESTAMP WITH TIME ZONE does not take a precision"); + } if (modifiers.size() > 1) { throw ParserException("TIMESTAMP only supports a single modifier"); } @@ -316,36 +325,29 @@ QualifiedName PEGTransformerFactory::TransformCatalogReservedSchemaTypeName( return QualifiedName(std::move(qualification), reserved_type_name); } -unique_ptr PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, - const optional> &type) { +unique_ptr +PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, + optional>> nested_type) { vector> map_children; - if (type) { - for (auto &child_type : *type) { - map_children.push_back(UnboundType::GetTypeExpression(child_type)->Copy()); - } + if (nested_type) { + map_children = std::move(*nested_type); } return make_uniq(Identifier("MAP"), std::move(map_children)); } -unique_ptr PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, - const vector &type) { - vector> tuple_children; - for (auto &child : type) { - tuple_children.push_back(UnboundType::GetTypeExpression(child)->Copy()); - } - return make_uniq(Identifier("TUPLE"), std::move(tuple_children)); +unique_ptr +PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, + vector> nested_type) { + return make_uniq(Identifier("TUPLE"), std::move(nested_type)); } -unique_ptr -PEGTransformerFactory::TransformRowType(PEGTransformer &transformer, - const optional> &col_id_type_list) { +unique_ptr PEGTransformerFactory::TransformRowType( + PEGTransformer &transformer, optional>>> col_id_type_list) { vector> struct_children; if (col_id_type_list) { for (auto &child : *col_id_type_list) { - auto &type_expr = UnboundType::GetTypeExpression(child.second); - auto new_type_expr = type_expr->Copy(); - new_type_expr->SetAlias(child.first); - struct_children.push_back(std::move(new_type_expr)); + child.second->SetAlias(child.first); + struct_children.push_back(std::move(child.second)); } } return make_uniq(Identifier("STRUCT"), std::move(struct_children)); @@ -372,29 +374,25 @@ unique_ptr PEGTransformerFactory::TransformVariantType(PEGTran unique_ptr PEGTransformerFactory::TransformUnionType(PEGTransformer &transformer, - const child_list_t &col_id_type_list) { - identifier_set_t union_names; + vector>> col_id_type_list) { vector> union_children; for (auto &colid : col_id_type_list) { - union_names.insert(colid.first); - auto &type_expr = UnboundType::GetTypeExpression(colid.second); - auto new_type_expr = type_expr->Copy(); - new_type_expr->SetAlias(colid.first); - union_children.push_back(std::move(new_type_expr)); + colid.second->SetAlias(colid.first); + union_children.push_back(std::move(colid.second)); } return make_uniq(Identifier("UNION"), std::move(union_children)); } -child_list_t +vector>> PEGTransformerFactory::TransformColIdTypeList(PEGTransformer &transformer, - const vector> &col_id_type) { + vector>> col_id_type) { return col_id_type; } -pair PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, - const Identifier &col_id, - const LogicalType &type) { - return make_pair(Identifier(col_id), type); +pair> +PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, + unique_ptr nested_type) { + return make_pair(Identifier(col_id), std::move(nested_type)); } unique_ptr PEGTransformerFactory::TransformBitType( @@ -531,8 +529,8 @@ unique_ptr PEGTransformerFactory::TransformNumberLiteral(PEGTr } unique_ptr PEGTransformerFactory::TransformSetofType(PEGTransformer &transformer, - const LogicalType &type) { - return UnboundType::GetTypeExpression(type)->Copy(); + unique_ptr nested_type) { + return nested_type; } // StringLiteral <- '\'' [^\']* '\'' diff --git a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp index 3b1f0e552..d71badeae 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp @@ -40,7 +40,9 @@ void SetCopyOptions(unique_ptr &info, vector &optio info->parsed_options[option.name] = std::move(row_func); } } else if (option.name == "HEADER" || option.name == "ESCAPE") { - if (option.children.empty()) { + if (option.expression) { + info->parsed_options[option.name] = std::move(option.expression); + } else if (option.children.empty()) { info->parsed_options[option.name] = nullptr; } else { info->parsed_options[option.name] = ConstantExpression::FromValue(option.children[0]); @@ -132,6 +134,7 @@ unique_ptr PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, const optional> &insert_column_list, const bool &from_or_to, unique_ptr copy_file_name, + const optional &legacy_option, const optional> ©_options) { auto result = make_uniq(); auto info = make_uniq(); @@ -149,8 +152,17 @@ PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_pt } info->format = ExtractFormat(info->file_path); + vector generic_options; + + if (legacy_option) { + generic_options.push_back(*legacy_option); + } + if (copy_options) { - auto generic_options = *copy_options; + generic_options.insert(generic_options.end(), copy_options->begin(), copy_options->end()); + } + + if (!generic_options.empty()) { SetCopyOptions(info, generic_options); } @@ -193,6 +205,11 @@ Identifier PEGTransformerFactory::TransformIdentifierColId(PEGTransformer &trans return Identifier(result); } +GenericCopyOption PEGTransformerFactory::TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, + const string &delimiter) { + return GenericCopyOption("delimiter", delimiter); +} + vector PEGTransformerFactory::TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp index 5a76f6ac1..d8169e9d9 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp @@ -130,6 +130,13 @@ pair> PEGTransformerFactory::TransformSeqNoCy return make_pair("cycle", make_uniq(SequenceInfo::SEQ_CYCLE, Value(false))); } +//! Sequence options take integer constants; anything else used to be rounded or cast silently +static void CheckIntegerSequenceOption(const char *option, const Literal &literal) { + if (!literal.IsNull() && literal.kind != LiteralKind::INTEGER) { + throw ParserException("Expected an integer argument for %s, got %s", option, literal.ToString()); + } +} + pair> PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, const bool &has_result, unique_ptr expression) { @@ -152,6 +159,7 @@ PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, con throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption("INCREMENT", const_expr.GetLiteral()); return make_pair("increment", make_uniq(SequenceInfo::SEQ_INC, const_expr.GetLiteral().ToValue())); } @@ -179,6 +187,7 @@ PEGTransformerFactory::TransformSeqSetMinMax(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption(seq_min_or_max == "minvalue" ? "MINVALUE" : "MAXVALUE", const_expr.GetLiteral()); auto seq_info = seq_min_or_max == "minvalue" ? SequenceInfo::SEQ_MIN : SequenceInfo::SEQ_MAX; return make_pair(seq_min_or_max, make_uniq(seq_info, const_expr.GetLiteral().ToValue())); } @@ -196,6 +205,7 @@ PEGTransformerFactory::TransformSeqStartWith(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption("START", const_expr.GetLiteral()); return make_pair("start", make_uniq(SequenceInfo::SEQ_START, const_expr.GetLiteral().ToValue())); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp index 9d4023599..3115c9282 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp @@ -33,6 +33,9 @@ PEGTransformerFactory::TransformCreateStatement(PEGTransformer &transformer, con } conflict_policy = OnCreateConflict::REPLACE_ON_CONFLICT; } + if (temporary && *temporary == SecretPersistType::PERSISTENT && result->info->type != CatalogType::SECRET_ENTRY) { + throw ParserException("PERSISTENT is only supported for CREATE SECRET"); + } if (result->info->type == CatalogType::SECRET_ENTRY) { auto &secret_info = result->info->Cast(); secret_info.persist_type = temporary ? *temporary : SecretPersistType::DEFAULT; diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp index 991d7ade2..9c870fb7f 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp @@ -1,5 +1,6 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/string_vector.hpp" +#include "duckdb/parser/expression/type_expression.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/parsed_data/create_type_info.hpp" @@ -35,7 +36,8 @@ PEGTransformerFactory::TransformEnumSelectType(PEGTransformer &transformer, unique_ptr PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal) { + const optional> &string_literal, + const optional> &array_bounds) { auto result = make_uniq(); idx_t enum_count = string_literal ? string_literal->size() : 0; Vector enum_vector(LogicalType::VARCHAR, enum_count); @@ -46,6 +48,9 @@ PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transforme } } result->type = LogicalType::ENUM(enum_vector, enum_count); + if (array_bounds && !array_bounds->empty()) { + result->type = TransformType(transformer, TypeExpression::FromLogicalType(result->type), array_bounds); + } return result; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp index 2c634282e..f342bf280 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp @@ -102,6 +102,9 @@ PEGTransformerFactory::TransformCreateViewStmt(PEGTransformer &transformer, cons if (create_secure) { info->security_type = ViewSecurityType::SECURE_VIEW; } + if (create_recursive && !insert_column_list) { + throw ParserException("CREATE RECURSIVE VIEW requires a column list"); + } if (insert_column_list) { info->aliases = StringsToIdentifiers(*insert_column_list); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp index 55ce4fdb0..372c91309 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp @@ -48,10 +48,24 @@ vector> PEGTransformerFactory::TransformDeleteUsingClause(P unique_ptr PEGTransformerFactory::TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name) { + unique_ptr base_table_name, + optional truncate_options) { + if (truncate_options && *truncate_options) { + throw NotImplementedException("TRUNCATE ... CASCADE is not implemented"); + } auto result = make_uniq(); result->node->table = std::move(base_table_name); return std::move(result); } +bool PEGTransformerFactory::TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, + optional truncate_drop_behavior) { + // the alias is ignored, the result is whether CASCADE was given + return truncate_drop_behavior && *truncate_drop_behavior; +} + +bool PEGTransformerFactory::TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior) { + return drop_behavior; +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp index 5fe0ec5c7..5ff313174 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp @@ -213,6 +213,9 @@ DescribeTarget PEGTransformerFactory::TransformDescribeBaseTableName(PEGTransfor DescribeTarget PEGTransformerFactory::TransformDescribeStringLiteral(PEGTransformer &transformer, const string &string_literal) { + if (string_literal.empty()) { + throw ParserException("Expected a table name, but got an empty string"); + } DescribeTarget result; result.is_table_name = true; result.table_name = Identifier(string_literal); diff --git a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp index 0c8854dd4..ecc4f4394 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp @@ -8,6 +8,9 @@ unique_ptr PEGTransformerFactory::TransformDropStatement(PEGTransf unique_ptr drop_entries, const optional &drop_behavior) { if (drop_behavior) { + if (drop_entries->info->type == CatalogType::SECRET_ENTRY) { + throw ParserException("CASCADE and RESTRICT are not supported for DROP SECRET"); + } drop_entries->info->cascade = *drop_behavior; } return std::move(drop_entries); @@ -45,13 +48,13 @@ bool PEGTransformerFactory::TransformFunctionTypeFunction(PEGTransformer &transf unique_ptr PEGTransformerFactory::TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name) { + const vector &table_function_name) { auto result = make_uniq(); auto info = make_uniq(); if (table_function_name.size() > 1) { throw NotImplementedException("Can only drop one object at a time"); } - info->SetQualifiedName(QualifiedName(table_function_name[0])); + info->SetQualifiedName(table_function_name[0]); info->type = comment_macro_table; info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; result->info = std::move(info); @@ -211,6 +214,9 @@ unique_ptr PEGTransformerFactory::TransformDropSecret(PEGTransfor info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; info->SetName(secret_name); if (drop_secret_storage) { + if (extra_drop_info->persist_mode == SecretPersistType::TEMPORARY) { + throw ParserException("Can not combine TEMPORARY with specifying a storage for drop secret"); + } extra_drop_info->secret_storage = drop_secret_storage->GetIdentifierName(); } info->extra_drop_info = std::move(extra_drop_info); diff --git a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp index 42e080ed7..65aff7f04 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp @@ -239,6 +239,19 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( if (filter_clause) { filter_expr = std::move(*filter_clause); } + if (within_group_clause) { + if (distinct) { + throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); + } + if (!order_modifier->orders.empty()) { + throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); + } + order_modifier->orders = std::move(*within_group_clause); + if (order_modifier->orders.size() != 1) { + throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); + } + } + if (function_children.size() == 1 && ExpressionIsUnqualifiedStar(function_children[0].GetExpression()) && !distinct && order_modifier->orders.empty()) { // COUNT(*) gets converted into COUNT() @@ -289,20 +302,6 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( throw ParserException("RESPECT/IGNORE NULLS is not supported for non-window functions"); } if (within_group_clause) { - auto order_by_clause = std::move(*within_group_clause); - if (distinct) { - throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); - } - if (!order_modifier->orders.empty()) { - throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); - } - if (!order_modifier) { - throw InternalException("ORDER modifier for WITHIN GROUP is not initialized"); - } - order_modifier->orders = std::move(order_by_clause); - if (order_modifier->orders.size() != 1) { - throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); - } if (lowercase_name == "percentile_cont") { if (function_children.size() != 1) { throw ParserException("Wrong number of arguments for PERCENTILE_CONT"); @@ -603,11 +602,14 @@ static unique_ptr FoldConjunctionExpression(PEGTransformer &tr if (!tails) { return expr; } - auto depth_guard = transformer.StackCheck(tails->size()); + auto depth_guard = transformer.StackCheck(1); + vector> children; + children.reserve(tails->size() + 1); + children.push_back(std::move(expr)); for (auto &tail : *tails) { - expr = make_uniq(conjunction_type, std::move(expr), std::move(tail)); + children.push_back(std::move(tail)); } - return expr; + return make_uniq(conjunction_type, std::move(children)); } unique_ptr PEGTransformerFactory::TransformLogicalOrExpression( @@ -662,15 +664,10 @@ PEGTransformerFactory::TransformColDefAndExpressionTail(PEGTransformer &transfor return is_distinct_from_expression; } -unique_ptr -PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, optional> not_expression, - unique_ptr is_expression) { - auto expr = std::move(is_expression); - if (!not_expression) { - return expr; - } - auto not_depth_guard = transformer.StackCheck(not_expression->size()); - for (idx_t i = 0; i < not_expression->size(); i++) { +static unique_ptr ApplyNotKeywords(PEGTransformer &transformer, const vector ¬_keywords, + unique_ptr expr) { + auto not_depth_guard = transformer.StackCheck(not_keywords.size()); + for (idx_t i = 0; i < not_keywords.size(); i++) { vector> inner_list_children; inner_list_children.push_back(std::move(expr)); expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); @@ -678,6 +675,22 @@ PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer return expr; } +unique_ptr +PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, + const optional> ¬_expression, + unique_ptr is_expression) { + if (!not_expression) { + return is_expression; + } + return ApplyNotKeywords(transformer, *not_expression, std::move(is_expression)); +} + +unique_ptr +PEGTransformerFactory::TransformNotComparisonOperand(PEGTransformer &transformer, const vector ¬_expression, + unique_ptr is_expression) { + return ApplyNotKeywords(transformer, not_expression, std::move(is_expression)); +} + vector PEGTransformerFactory::TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword) { return not_keyword; @@ -699,6 +712,13 @@ static unique_ptr ApplyIsTest(unique_ptr exp ExpressionClassToString(is_expr->GetExpressionClass())); } +static unique_ptr ApplyCastOperator(unique_ptr expr, + unique_ptr cast_operator) { + auto cast_expr = unique_ptr_cast(std::move(cast_operator)); + cast_expr->ChildMutable() = std::move(expr); + return std::move(cast_expr); +} + static unique_ptr ApplyIsDistinctFromTail(unique_ptr expr, IsDistinctFromTail tail) { return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); @@ -706,13 +726,7 @@ static unique_ptr ApplyIsDistinctFromTail(unique_ptr ApplyComparisonTail(unique_ptr expr, ComparisonExpressionTail tail) { - auto right_expr = std::move(tail.expression); - for (idx_t i = 0; i < tail.not_keywords.size(); i++) { - vector> inner_list_children; - inner_list_children.push_back(std::move(right_expr)); - right_expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); - } - return make_uniq(tail.comparison_type, std::move(expr), std::move(right_expr)); + return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); } unique_ptr @@ -745,6 +759,9 @@ PEGTransformerFactory::TransformIsExpression(PEGTransformer &transformer, expr = TransformInfixOtherOperatorExpression(transformer, std::move(expr), std::move(other_tail)); break; } + case ExpressionTailType::CAST: + expr = ApplyCastOperator(std::move(expr), std::move(tail.cast)); + break; } previous_type = tail.type; } @@ -796,6 +813,14 @@ IsExpressionTail PEGTransformerFactory::TransformIsOtherOperatorTail(PEGTransfor return result; } +IsExpressionTail PEGTransformerFactory::TransformIsCastTail(PEGTransformer &transformer, + unique_ptr cast_operator) { + IsExpressionTail result; + result.type = ExpressionTailType::CAST; + result.cast = std::move(cast_operator); + return result; +} + unique_ptr PEGTransformerFactory::TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value) { @@ -869,15 +894,13 @@ PEGTransformerFactory::TransformIsDistinctFromTail(PEGTransformer &transformer, return {is_distinct_from_op, std::move(comparison_expression)}; } -ComparisonExpressionTail PEGTransformerFactory::TransformComparisonExpressionTail( - PEGTransformer &transformer, const ExpressionType &comparison_operator, optional> not_expression, - unique_ptr between_in_like_expression) { +ComparisonExpressionTail +PEGTransformerFactory::TransformComparisonExpressionTail(PEGTransformer &transformer, + const ExpressionType &comparison_operator, + unique_ptr comparison_operand) { ComparisonExpressionTail result; result.comparison_type = comparison_operator; - if (not_expression) { - result.not_keywords = std::move(*not_expression); - } - result.expression = std::move(between_in_like_expression); + result.expression = std::move(comparison_operand); return result; } @@ -1122,7 +1145,12 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (!in_predicate && !between_like_op) { return expr; } - auto predicate_count = in_predicate ? in_predicate->size() : 0; + idx_t predicate_count = 0; + if (in_predicate) { + for (auto &predicate : *in_predicate) { + predicate_count += 1 + predicate.casts.size(); + } + } if (between_like_op) { predicate_count++; } @@ -1130,6 +1158,9 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (in_predicate) { for (auto &predicate : *in_predicate) { expr = ApplyBetweenInLikeOperator(std::move(expr), predicate); + for (auto &cast : predicate.casts) { + expr = ApplyCastOperator(std::move(expr), std::move(cast)); + } } } if (between_like_op) { @@ -1138,14 +1169,26 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres return expr; } -BetweenInLikeOperator PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause) { - return {has_result, std::move(in_clause)}; +BetweenInLikeOperator +PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, + unique_ptr in_clause, + optional>> cast_operator) { + BetweenInLikeOperator result {has_result, std::move(in_clause)}; + if (cast_operator) { + result.casts = std::move(*cast_operator); + } + return result; } BetweenInLikeOperator PEGTransformerFactory::TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression) { + if (has_result && between_like_op_expression->GetExpressionClass() == ExpressionClass::FUNCTION) { + auto &function_name = between_like_op_expression->Cast().FunctionName(); + if (StringUtil::StartsWith(function_name.GetIdentifierName(), "!")) { + throw ParserException("NOT cannot precede an operator that is already negated (!~~, !~~*, !~ or !~*)"); + } + } return {has_result, std::move(between_like_op_expression)}; } @@ -1947,11 +1990,12 @@ PEGTransformerFactory::TransformSliceExpression(PEGTransformer &transformer, return make_uniq(ExpressionType::ARRAY_SLICE, std::move(slice_bound)); } -vector> PEGTransformerFactory::TransformSliceBound( - PEGTransformer &transformer, optional> expression, - optional> end_slice_bound, optional> step_slice_bound) { +vector> +PEGTransformerFactory::TransformSliceBound(PEGTransformer &transformer, + optional> expression, + optional>> slice_upper_bound) { vector> slice_bounds; - if (!end_slice_bound && !step_slice_bound) { + if (!slice_upper_bound) { if (expression && *expression) { slice_bounds.push_back(std::move(*expression)); } @@ -1962,11 +2006,27 @@ vector> PEGTransformerFactory::TransformSliceBound( } else { slice_bounds.push_back(OperatorExpression::EmptySliceBound()); } - if (end_slice_bound && *end_slice_bound) { - slice_bounds.push_back(std::move(*end_slice_bound)); - } else { - slice_bounds.push_back(OperatorExpression::EmptySliceBound()); + for (auto &bound : *slice_upper_bound) { + slice_bounds.push_back(std::move(bound)); } + return slice_bounds; +} + +vector> +PEGTransformerFactory::TransformMinusStepSliceBound(PEGTransformer &transformer, + unique_ptr step_slice_bound) { + vector> slice_bounds; + slice_bounds.push_back(OperatorExpression::EmptySliceBound()); + slice_bounds.push_back(std::move(step_slice_bound)); + return slice_bounds; +} + +vector> +PEGTransformerFactory::TransformEndStepSliceBound(PEGTransformer &transformer, + unique_ptr end_slice_bound, + optional> step_slice_bound) { + vector> slice_bounds; + slice_bounds.push_back(std::move(end_slice_bound)); if (step_slice_bound && *step_slice_bound) { slice_bounds.push_back(std::move(*step_slice_bound)); } @@ -1975,19 +2035,15 @@ vector> PEGTransformerFactory::TransformSliceBound( unique_ptr PEGTransformerFactory::TransformEndSliceBound(PEGTransformer &transformer, - optional> end_slice_value) { + optional> expression) { // If either the lower or upper bound is not specified, we use an empty constant LIST, // which we handle in the execution. - if (end_slice_value && *end_slice_value) { - return std::move(*end_slice_value); + if (expression && *expression) { + return std::move(*expression); } return OperatorExpression::EmptySliceBound(); } -unique_ptr PEGTransformerFactory::TransformEndSliceMinus(PEGTransformer &transformer) { - return OperatorExpression::EmptySliceBound(); -} - unique_ptr PEGTransformerFactory::TransformStepSliceBound(PEGTransformer &transformer, optional> expression) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp index 44549a67b..0b3eef8e5 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp @@ -35,6 +35,9 @@ static const TransformFrameOps FUNC_NAME_KEYWORD_OPS = {"FuncNameKeyword", static const TransformFrameOps TYPE_NAME_KEYWORD_OPS = {"TypeNameKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; +static const TransformFrameOps BARE_LABEL_KEYWORD_OPS = { + "BareLabelKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, + &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; static const TransformFrameOps SETTING_NAME_OPS = {"SettingName", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; @@ -406,6 +409,8 @@ static const TransformFrameOps SCHEMA_RESERVED_TYPE_NAME_OPS = { static const TransformFrameOps TYPE_MODIFIERS_OPS = {"TypeModifiers", &PEGTransformerFactory::InitializeTypeModifiersTrampoline, &PEGTransformerFactory::FinalizeTypeModifiersTrampoline}; +static const TransformFrameOps NESTED_TYPE_OPS = {"NestedType", &PEGTransformerFactory::InitializeNestedTypeTrampoline, + &PEGTransformerFactory::FinalizeNestedTypeTrampoline}; static const TransformFrameOps ROW_TYPE_OPS = {"RowType", &PEGTransformerFactory::InitializeRowTypeTrampoline, &PEGTransformerFactory::FinalizeRowTypeTrampoline}; static const TransformFrameOps SETOF_TYPE_OPS = {"SetofType", &PEGTransformerFactory::InitializeSetofTypeTrampoline, @@ -508,6 +513,9 @@ static const TransformFrameOps IDENTIFIER_COL_ID_OPS = {"IdentifierColId", static const TransformFrameOps COPY_FILE_NAME_SUFFIX_OPS = { "CopyFileNameSuffix", &PEGTransformerFactory::InitializeCopyFileNameSuffixTrampoline, &PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline}; +static const TransformFrameOps COPY_LEGACY_OPTION_OPS = {"CopyLegacyOption", + &PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline, + &PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline}; static const TransformFrameOps COPY_OPTIONS_OPS = {"CopyOptions", &PEGTransformerFactory::InitializeCopyOptionsTrampoline, &PEGTransformerFactory::FinalizeCopyOptionsTrampoline}; @@ -1118,6 +1126,15 @@ static const TransformFrameOps DELETE_STATEMENT_OPS = {"DeleteStatement", static const TransformFrameOps TRUNCATE_STATEMENT_OPS = {"TruncateStatement", &PEGTransformerFactory::InitializeTruncateStatementTrampoline, &PEGTransformerFactory::FinalizeTruncateStatementTrampoline}; +static const TransformFrameOps TRUNCATE_OPTIONS_OPS = {"TruncateOptions", + &PEGTransformerFactory::InitializeTruncateOptionsTrampoline, + &PEGTransformerFactory::FinalizeTruncateOptionsTrampoline}; +static const TransformFrameOps TRUNCATE_ALIAS_OPTIONS_OPS = { + "TruncateAliasOptions", &PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline, + &PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline}; +static const TransformFrameOps TRUNCATE_DROP_BEHAVIOR_OPS = { + "TruncateDropBehavior", &PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline, + &PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline}; static const TransformFrameOps TARGET_OPT_ALIAS_OPS = {"TargetOptAlias", &PEGTransformerFactory::InitializeTargetOptAliasTrampoline, &PEGTransformerFactory::FinalizeTargetOptAliasTrampoline}; @@ -1705,6 +1722,8 @@ static const TransformFrameOps IS_COMPARISON_TAIL_OPS = {"IsComparisonTail", static const TransformFrameOps IS_OTHER_OPERATOR_TAIL_OPS = { "IsOtherOperatorTail", &PEGTransformerFactory::InitializeIsOtherOperatorTailTrampoline, &PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline}; +static const TransformFrameOps IS_CAST_TAIL_OPS = {"IsCastTail", &PEGTransformerFactory::InitializeIsCastTailTrampoline, + &PEGTransformerFactory::FinalizeIsCastTailTrampoline}; static const TransformFrameOps IS_TEST_OPS = {"IsTest", &PEGTransformerFactory::InitializeIsTestTrampoline, &PEGTransformerFactory::FinalizeIsTestTrampoline}; static const TransformFrameOps IS_LITERAL_OPS = {"IsLiteral", &PEGTransformerFactory::InitializeIsLiteralTrampoline, @@ -1743,6 +1762,12 @@ static const TransformFrameOps COMPARISON_EXPRESSION_OPS = { static const TransformFrameOps COMPARISON_EXPRESSION_TAIL_OPS = { "ComparisonExpressionTail", &PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline, &PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline}; +static const TransformFrameOps COMPARISON_OPERAND_OPS = {"ComparisonOperand", + &PEGTransformerFactory::InitializeComparisonOperandTrampoline, + &PEGTransformerFactory::FinalizeComparisonOperandTrampoline}; +static const TransformFrameOps NOT_COMPARISON_OPERAND_OPS = { + "NotComparisonOperand", &PEGTransformerFactory::InitializeNotComparisonOperandTrampoline, + &PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline}; static const TransformFrameOps COMPARISON_OPERATOR_OPS = { "ComparisonOperator", &PEGTransformerFactory::InitializeComparisonOperatorTrampoline, &PEGTransformerFactory::FinalizeComparisonOperatorTrampoline}; @@ -1972,15 +1997,18 @@ static const TransformFrameOps SLICE_EXPRESSION_OPS = {"SliceExpression", &PEGTransformerFactory::FinalizeSliceExpressionTrampoline}; static const TransformFrameOps SLICE_BOUND_OPS = {"SliceBound", &PEGTransformerFactory::InitializeSliceBoundTrampoline, &PEGTransformerFactory::FinalizeSliceBoundTrampoline}; +static const TransformFrameOps SLICE_UPPER_BOUND_OPS = {"SliceUpperBound", + &PEGTransformerFactory::InitializeSliceUpperBoundTrampoline, + &PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline}; +static const TransformFrameOps MINUS_STEP_SLICE_BOUND_OPS = { + "MinusStepSliceBound", &PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline, + &PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline}; +static const TransformFrameOps END_STEP_SLICE_BOUND_OPS = { + "EndStepSliceBound", &PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline, + &PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline}; static const TransformFrameOps END_SLICE_BOUND_OPS = {"EndSliceBound", &PEGTransformerFactory::InitializeEndSliceBoundTrampoline, &PEGTransformerFactory::FinalizeEndSliceBoundTrampoline}; -static const TransformFrameOps END_SLICE_VALUE_OPS = {"EndSliceValue", - &PEGTransformerFactory::InitializeEndSliceValueTrampoline, - &PEGTransformerFactory::FinalizeEndSliceValueTrampoline}; -static const TransformFrameOps END_SLICE_MINUS_OPS = {"EndSliceMinus", - &PEGTransformerFactory::InitializeEndSliceMinusTrampoline, - &PEGTransformerFactory::FinalizeEndSliceMinusTrampoline}; static const TransformFrameOps STEP_SLICE_BOUND_OPS = {"StepSliceBound", &PEGTransformerFactory::InitializeStepSliceBoundTrampoline, &PEGTransformerFactory::FinalizeStepSliceBoundTrampoline}; @@ -2743,6 +2771,9 @@ static const TransformFrameOps QUALIFY_CLAUSE_OPS = {"QualifyClause", static const TransformFrameOps SAMPLE_CLAUSE_OPS = {"SampleClause", &PEGTransformerFactory::InitializeSampleClauseTrampoline, &PEGTransformerFactory::FinalizeSampleClauseTrampoline}; +static const TransformFrameOps SELECT_SAMPLE_CLAUSE_OPS = { + "SelectSampleClause", &PEGTransformerFactory::InitializeSelectSampleClauseTrampoline, + &PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline}; static const TransformFrameOps WINDOW_CLAUSE_OPS = {"WindowClause", &PEGTransformerFactory::InitializeWindowClauseTrampoline, &PEGTransformerFactory::FinalizeWindowClauseTrampoline}; @@ -2995,15 +3026,15 @@ static const TransformFrameOps BASE_TABLE_ALIAS_SET_OPS = { static const TransformFrameOps UPDATE_ALIAS_OPS = {"UpdateAlias", &PEGTransformerFactory::InitializeUpdateAliasTrampoline, &PEGTransformerFactory::FinalizeUpdateAliasTrampoline}; +static const TransformFrameOps UPDATE_SET_CLAUSE_LIST_OPS = { + "UpdateSetClauseList", &PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline, + &PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline}; static const TransformFrameOps UPDATE_SET_CLAUSE_OPS = {"UpdateSetClause", &PEGTransformerFactory::InitializeUpdateSetClauseTrampoline, &PEGTransformerFactory::FinalizeUpdateSetClauseTrampoline}; static const TransformFrameOps UPDATE_SET_TUPLE_OPS = {"UpdateSetTuple", &PEGTransformerFactory::InitializeUpdateSetTupleTrampoline, &PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline}; -static const TransformFrameOps UPDATE_SET_ELEMENT_LIST_OPS = { - "UpdateSetElementList", &PEGTransformerFactory::InitializeUpdateSetElementListTrampoline, - &PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline}; static const TransformFrameOps UPDATE_SET_ELEMENT_OPS = {"UpdateSetElement", &PEGTransformerFactory::InitializeUpdateSetElementTrampoline, &PEGTransformerFactory::FinalizeUpdateSetElementTrampoline}; @@ -3065,6 +3096,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"ColumnNameKeyword", &COLUMN_NAME_KEYWORD_OPS}, {"FuncNameKeyword", &FUNC_NAME_KEYWORD_OPS}, {"TypeNameKeyword", &TYPE_NAME_KEYWORD_OPS}, + {"BareLabelKeyword", &BARE_LABEL_KEYWORD_OPS}, {"SettingName", &SETTING_NAME_OPS}, {"AlterStatement", &ALTER_STATEMENT_OPS}, {"AlterOptions", &ALTER_OPTIONS_OPS}, @@ -3195,6 +3227,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CatalogReservedSchemaTypeName", &CATALOG_RESERVED_SCHEMA_TYPE_NAME_OPS}, {"SchemaReservedTypeName", &SCHEMA_RESERVED_TYPE_NAME_OPS}, {"TypeModifiers", &TYPE_MODIFIERS_OPS}, + {"NestedType", &NESTED_TYPE_OPS}, {"RowType", &ROW_TYPE_OPS}, {"SetofType", &SETOF_TYPE_OPS}, {"UnionType", &UNION_TYPE_OPS}, @@ -3234,6 +3267,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CopyFileNameIdentifierColId", ©_FILE_NAME_IDENTIFIER_COL_ID_OPS}, {"IdentifierColId", &IDENTIFIER_COL_ID_OPS}, {"CopyFileNameSuffix", ©_FILE_NAME_SUFFIX_OPS}, + {"CopyLegacyOption", ©_LEGACY_OPTION_OPS}, {"CopyOptions", ©_OPTIONS_OPS}, {"CopyOptionList", ©_OPTION_LIST_OPS}, {"SpecializedOptionList", &SPECIALIZED_OPTION_LIST_OPS}, @@ -3447,6 +3481,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"DeallocatePrepare", &DEALLOCATE_PREPARE_OPS}, {"DeleteStatement", &DELETE_STATEMENT_OPS}, {"TruncateStatement", &TRUNCATE_STATEMENT_OPS}, + {"TruncateOptions", &TRUNCATE_OPTIONS_OPS}, + {"TruncateAliasOptions", &TRUNCATE_ALIAS_OPTIONS_OPS}, + {"TruncateDropBehavior", &TRUNCATE_DROP_BEHAVIOR_OPS}, {"TargetOptAlias", &TARGET_OPT_ALIAS_OPS}, {"TargetAlias", &TARGET_ALIAS_OPS}, {"DeleteUsingClause", &DELETE_USING_CLAUSE_OPS}, @@ -3650,6 +3687,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctTail", &IS_DISTINCT_TAIL_OPS}, {"IsComparisonTail", &IS_COMPARISON_TAIL_OPS}, {"IsOtherOperatorTail", &IS_OTHER_OPERATOR_TAIL_OPS}, + {"IsCastTail", &IS_CAST_TAIL_OPS}, {"IsTest", &IS_TEST_OPS}, {"IsLiteral", &IS_LITERAL_OPS}, {"IsLiteralValue", &IS_LITERAL_VALUE_OPS}, @@ -3664,6 +3702,8 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctFromOp", &IS_DISTINCT_FROM_OP_OPS}, {"ComparisonExpression", &COMPARISON_EXPRESSION_OPS}, {"ComparisonExpressionTail", &COMPARISON_EXPRESSION_TAIL_OPS}, + {"ComparisonOperand", &COMPARISON_OPERAND_OPS}, + {"NotComparisonOperand", &NOT_COMPARISON_OPERAND_OPS}, {"ComparisonOperator", &COMPARISON_OPERATOR_OPS}, {"OperatorEqual", &OPERATOR_EQUAL_OPS}, {"OperatorNotEqual", &OPERATOR_NOT_EQUAL_OPS}, @@ -3744,9 +3784,10 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"MethodFunctionArguments", &METHOD_FUNCTION_ARGUMENTS_OPS}, {"SliceExpression", &SLICE_EXPRESSION_OPS}, {"SliceBound", &SLICE_BOUND_OPS}, + {"SliceUpperBound", &SLICE_UPPER_BOUND_OPS}, + {"MinusStepSliceBound", &MINUS_STEP_SLICE_BOUND_OPS}, + {"EndStepSliceBound", &END_STEP_SLICE_BOUND_OPS}, {"EndSliceBound", &END_SLICE_BOUND_OPS}, - {"EndSliceValue", &END_SLICE_VALUE_OPS}, - {"EndSliceMinus", &END_SLICE_MINUS_OPS}, {"StepSliceBound", &STEP_SLICE_BOUND_OPS}, {"PostfixOperator", &POSTFIX_OPERATOR_OPS}, {"SpecialFunctionExpression", &SPECIAL_FUNCTION_EXPRESSION_OPS}, @@ -4014,6 +4055,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"HavingClause", &HAVING_CLAUSE_OPS}, {"QualifyClause", &QUALIFY_CLAUSE_OPS}, {"SampleClause", &SAMPLE_CLAUSE_OPS}, + {"SelectSampleClause", &SELECT_SAMPLE_CLAUSE_OPS}, {"WindowClause", &WINDOW_CLAUSE_OPS}, {"WindowDefinition", &WINDOW_DEFINITION_OPS}, {"SampleEntry", &SAMPLE_ENTRY_OPS}, @@ -4104,9 +4146,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"BaseTableSet", &BASE_TABLE_SET_OPS}, {"BaseTableAliasSet", &BASE_TABLE_ALIAS_SET_OPS}, {"UpdateAlias", &UPDATE_ALIAS_OPS}, + {"UpdateSetClauseList", &UPDATE_SET_CLAUSE_LIST_OPS}, {"UpdateSetClause", &UPDATE_SET_CLAUSE_OPS}, {"UpdateSetTuple", &UPDATE_SET_TUPLE_OPS}, - {"UpdateSetElementList", &UPDATE_SET_ELEMENT_LIST_OPS}, {"UpdateSetElement", &UPDATE_SET_ELEMENT_OPS}, {"UpdateSetColumnTarget", &UPDATE_SET_COLUMN_TARGET_OPS}, {"UseStatement", &USE_STATEMENT_OPS}, @@ -6595,6 +6637,49 @@ PEGTransformerFactory::FinalizeTypeModifiersTrampoline(PEGTransformer &transform return transformer.MakeResult>>(std::move(result)); } +void PEGTransformerFactory::InitializeNestedTypeTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &repeat_opt = list_pr.GetChild(1).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(2 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 1 + child_idx); + } + } else { + process.ReserveChildSlots(2 - 1); + } + process.PushChild({transformer.GetRule("TypeVariations"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(1).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } + auto type_variations = process.TakeResult>(0); + optional> array_bounds {}; + if (dynamic_child_count > 0) { + vector array_bounds_value; + for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { + array_bounds_value.push_back(process.TakeResult(i)); + } + array_bounds = std::move(array_bounds_value); + } + auto result = TransformNestedType(transformer, std::move(type_variations), array_bounds); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -6607,11 +6692,11 @@ void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transfor arena_ptr PEGTransformerFactory::FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - optional> col_id_type_list {}; + optional>>> col_id_type_list {}; if (process.child_results[0]) { - col_id_type_list = process.TakeResult>(0); + col_id_type_list = process.TakeResult>>>(0); } - auto result = TransformRowType(transformer, col_id_type_list); + auto result = TransformRowType(transformer, std::move(col_id_type_list)); return transformer.MakeResult>(std::move(result)); } @@ -6619,13 +6704,13 @@ void PEGTransformerFactory::InitializeSetofTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 0); + process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeSetofTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto type = process.TakeResult(0); - auto result = TransformSetofType(transformer, type); + auto nested_type = process.TakeResult>(0); + auto result = TransformSetofType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6638,8 +6723,8 @@ void PEGTransformerFactory::InitializeUnionTypeTrampoline(PEGTransformer &transf arena_ptr PEGTransformerFactory::FinalizeUnionTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto col_id_type_list = process.TakeResult>(0); - auto result = TransformUnionType(transformer, col_id_type_list); + auto col_id_type_list = process.TakeResult>>>(0); + auto result = TransformUnionType(transformer, std::move(col_id_type_list)); return transformer.MakeResult>(std::move(result)); } @@ -6661,12 +6746,12 @@ PEGTransformerFactory::FinalizeColIdTypeListTrampoline(PEGTransformer &transform auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(0))); auto dynamic_child_count = dynamic_list_items.size(); - vector> col_id_type; + vector>> col_id_type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - col_id_type.push_back(process.TakeResult>(i)); + col_id_type.push_back(process.TakeResult>>(i)); } - auto result = TransformColIdTypeList(transformer, col_id_type); - return transformer.MakeResult>(result); + auto result = TransformColIdTypeList(transformer, std::move(col_id_type)); + return transformer.MakeResult>>>(std::move(result)); } void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transformer, @@ -6680,7 +6765,7 @@ void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transfor process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); } } else { process.ReserveChildSlots(1 - 1); @@ -6696,16 +6781,16 @@ arena_ptr PEGTransformerFactory::FinalizeMapTypeTrampoline auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(dynamic_list_opt.GetResult())); dynamic_child_count = dynamic_list_items.size(); } - optional> type {}; - auto &type_opt = list_pr.GetChild(1).Cast(); - if (type_opt.HasResult()) { - vector type_value; + optional>> nested_type {}; + auto &nested_type_opt = list_pr.GetChild(1).Cast(); + if (nested_type_opt.HasResult()) { + vector> nested_type_value; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - type_value.push_back(process.TakeResult(i)); + nested_type_value.push_back(process.TakeResult>(i)); } - type = std::move(type_value); + nested_type = std::move(nested_type_value); } - auto result = TransformMapType(transformer, type); + auto result = TransformMapType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6717,7 +6802,7 @@ void PEGTransformerFactory::InitializeTupleTypeTrampoline(PEGTransformer &transf process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); } } @@ -6726,11 +6811,11 @@ arena_ptr PEGTransformerFactory::FinalizeTupleTypeTrampoli auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(1))); auto dynamic_child_count = dynamic_list_items.size(); - vector type; + vector> nested_type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - type.push_back(process.TakeResult(i)); + nested_type.push_back(process.TakeResult>(i)); } - auto result = TransformTupleType(transformer, type); + auto result = TransformTupleType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6738,16 +6823,16 @@ void PEGTransformerFactory::InitializeColIdTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 1); + process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 1); process.PushChild({transformer.GetRule("ColId"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeColIdTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto col_id = process.TakeResult(0); - auto type = process.TakeResult(1); - auto result = TransformColIdType(transformer, col_id, type); - return transformer.MakeResult>(result); + auto nested_type = process.TakeResult>(1); + auto result = TransformColIdType(transformer, col_id, std::move(nested_type)); + return transformer.MakeResult>>(std::move(result)); } void PEGTransformerFactory::InitializeArrayBoundsTrampoline(PEGTransformer &transformer, @@ -7090,10 +7175,14 @@ PEGTransformerFactory::FinalizeCopyVariationsTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeCopyTableTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(5); - auto ©_options_opt = list_pr.GetChild(4).Cast(); + process.ReserveChildSlots(6); + auto ©_options_opt = list_pr.GetChild(5).Cast(); if (copy_options_opt.HasResult()) { - process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 4); + process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 5); + } + auto ©_legacy_option_opt = list_pr.GetChild(4).Cast(); + if (copy_legacy_option_opt.HasResult()) { + process.PushChild({transformer.GetRule("CopyLegacyOption"), copy_legacy_option_opt.GetResult()}, 4); } process.PushChild({transformer.GetRule("CopyFileName"), list_pr.GetChild(3)}, 3); process.PushChild({transformer.GetRule("FromOrTo"), list_pr.GetChild(2)}, 2); @@ -7113,12 +7202,16 @@ arena_ptr PEGTransformerFactory::FinalizeCopyTableTrampoli } auto from_or_to = process.TakeResult(2); auto copy_file_name = process.TakeResult>(3); - optional> copy_options {}; + optional copy_legacy_option {}; if (process.child_results[4]) { - copy_options = process.TakeResult>(4); + copy_legacy_option = process.TakeResult(4); + } + optional> copy_options {}; + if (process.child_results[5]) { + copy_options = process.TakeResult>(5); } auto result = TransformCopyTable(transformer, std::move(base_table_name), insert_column_list, from_or_to, - std::move(copy_file_name), copy_options); + std::move(copy_file_name), copy_legacy_option, copy_options); return transformer.MakeResult>(std::move(result)); } @@ -7317,6 +7410,23 @@ PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &tran return transformer.MakeResult(result); } +void PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + process.ReserveChildSlots(0); +} + +arena_ptr +PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + bool has_result {}; + auto &has_result_opt = list_pr.GetChild(0).Cast(); + has_result = has_result_opt.HasResult(); + auto string_literal = TransformStringLiteral(transformer, list_pr.GetChild(2)); + auto result = TransformCopyLegacyOption(transformer, has_result, string_literal); + return transformer.MakeResult(result); +} + void PEGTransformerFactory::InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -11475,13 +11585,34 @@ PEGTransformerFactory::FinalizeEnumSelectTypeTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &repeat_opt = list_pr.GetChild(2).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(1 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 0 + child_idx); + } + } else { + process.ReserveChildSlots(1 - 1); + } } arena_ptr PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } optional> string_literal {}; auto &string_literal_opt = ExtractResultFromParens(list_pr.GetChild(1)).Cast(); if (string_literal_opt.HasResult()) { @@ -11493,7 +11624,15 @@ PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &t } string_literal = std::move(string_literal_value); } - auto result = TransformEnumStringLiteralList(transformer, string_literal); + optional> array_bounds {}; + if (dynamic_child_count > 0) { + vector array_bounds_value; + for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { + array_bounds_value.push_back(process.TakeResult(i)); + } + array_bounds = std::move(array_bounds_value); + } + auto result = TransformEnumStringLiteralList(transformer, string_literal, array_bounds); return transformer.MakeResult>(std::move(result)); } @@ -11666,7 +11805,11 @@ PEGTransformerFactory::FinalizeDeleteStatementTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); + process.ReserveChildSlots(2); + auto &truncate_options_opt = list_pr.GetChild(3).Cast(); + if (truncate_options_opt.HasResult()) { + process.PushChild({transformer.GetRule("TruncateOptions"), truncate_options_opt.GetResult()}, 1); + } process.PushChild({transformer.GetRule("BaseTableName"), list_pr.GetChild(2)}, 0); } @@ -11678,10 +11821,73 @@ PEGTransformerFactory::FinalizeTruncateStatementTrampoline(PEGTransformer &trans auto &has_result_opt = list_pr.GetChild(1).Cast(); has_result = has_result_opt.HasResult(); auto base_table_name = process.TakeResult>(0); - auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name)); + optional truncate_options {}; + if (process.child_results[1]) { + truncate_options = process.TakeResult(1); + } + auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name), truncate_options); return transformer.MakeResult>(std::move(result)); } +void PEGTransformerFactory::InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult(0); + return transformer.MakeResult(result); +} + +void PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + auto &truncate_drop_behavior_opt = list_pr.GetChild(1).Cast(); + if (truncate_drop_behavior_opt.HasResult()) { + process.PushChild({transformer.GetRule("TruncateDropBehavior"), truncate_drop_behavior_opt.GetResult()}, 1); + } + process.PushChild({transformer.GetRule("TargetAlias"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto target_alias = process.TakeResult(0); + optional truncate_drop_behavior {}; + if (process.child_results[1]) { + truncate_drop_behavior = process.TakeResult(1); + } + auto result = TransformTruncateAliasOptions(transformer, target_alias, truncate_drop_behavior); + return transformer.MakeResult(result); +} + +void PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("DropBehavior"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto drop_behavior = process.TakeResult(0); + auto result = TransformTruncateDropBehavior(transformer, drop_behavior); + return transformer.MakeResult(result); +} + void PEGTransformerFactory::InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -12243,7 +12449,13 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableTrampoli void PEGTransformerFactory::InitializeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); + auto list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); + auto dynamic_child_count = list_items.size(); + process.ReserveChildSlots(3 + dynamic_child_count - 1); + for (idx_t i = list_items.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("QualifiedTableFunction"), list_items[child_idx].get()}, 2 + child_idx); + } auto &if_exists_opt = list_pr.GetChild(1).Cast(); if (if_exists_opt.HasResult()) { process.PushChild({transformer.GetRule("IfExists"), if_exists_opt.GetResult()}, 1); @@ -12255,17 +12467,18 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); + auto dynamic_child_count = dynamic_list_items.size(); auto comment_macro_table = process.TakeResult(0); optional if_exists {}; if (process.child_results[1]) { if_exists = process.TakeResult(1); } - vector table_function_name; - auto table_function_name_items = ExtractParseResultsFromList(list_pr.GetChild(2)); - for (auto &table_function_name_item : table_function_name_items) { - table_function_name.push_back(table_function_name_item.get().Cast().identifier); + vector qualified_table_function; + for (idx_t i = 2; i < 2 + dynamic_child_count; i++) { + qualified_table_function.push_back(process.TakeResult(i)); } - auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, table_function_name); + auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, qualified_table_function); return transformer.MakeResult>(std::move(result)); } @@ -15641,7 +15854,7 @@ PEGTransformerFactory::FinalizeLogicalNotExpressionTrampoline(PEGTransformer &tr not_expression = process.TakeResult>(0); } auto is_expression = process.TakeResult>(1); - auto result = TransformLogicalNotExpression(transformer, std::move(not_expression), std::move(is_expression)); + auto result = TransformLogicalNotExpression(transformer, not_expression, std::move(is_expression)); return transformer.MakeResult>(std::move(result)); } @@ -15670,7 +15883,7 @@ PEGTransformerFactory::FinalizeNotExpressionTrampoline(PEGTransformer &transform not_keyword.push_back(process.TakeResult(i)); } auto result = TransformNotExpression(transformer, not_keyword); - return transformer.MakeResult>(std::move(result)); + return transformer.MakeResult>(result); } void PEGTransformerFactory::InitializeNotKeywordTrampoline(PEGTransformer &transformer, @@ -15833,6 +16046,20 @@ PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &tra return transformer.MakeResult(std::move(result)); } +void PEGTransformerFactory::InitializeIsCastTailTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("CastOperator"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto cast_operator = process.TakeResult>(0); + auto result = TransformIsCastTail(transformer, std::move(cast_operator)); + return transformer.MakeResult(std::move(result)); +} + void PEGTransformerFactory::InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -16102,12 +16329,8 @@ PEGTransformerFactory::FinalizeComparisonExpressionTrampoline(PEGTransformer &tr void PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); - process.PushChild({transformer.GetRule("BetweenInLikeExpression"), list_pr.GetChild(2)}, 2); - auto ¬_expression_opt = list_pr.GetChild(1).Cast(); - if (not_expression_opt.HasResult()) { - process.PushChild({transformer.GetRule("NotExpression"), not_expression_opt.GetResult()}, 1); - } + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("ComparisonOperand"), list_pr.GetChild(1)}, 1); process.PushChild({transformer.GetRule("ComparisonOperator"), list_pr.GetChild(0)}, 0); } @@ -16115,16 +16338,49 @@ arena_ptr PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto comparison_operator = process.TakeResult(0); - optional> not_expression {}; - if (process.child_results[1]) { - not_expression = process.TakeResult>(1); - } - auto between_in_like_expression = process.TakeResult>(2); - auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(not_expression), - std::move(between_in_like_expression)); + auto comparison_operand = process.TakeResult>(1); + auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(comparison_operand)); return transformer.MakeResult(std::move(result)); } +void PEGTransformerFactory::InitializeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult>(0); + return transformer.MakeResult>(std::move(result)); +} + +void PEGTransformerFactory::InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("IsExpression"), list_pr.GetChild(1)}, 1); + process.PushChild({transformer.GetRule("NotExpression"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto not_expression = process.TakeResult>(0); + auto is_expression = process.TakeResult>(1); + auto result = TransformNotComparisonOperand(transformer, not_expression, std::move(is_expression)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -16280,18 +16536,46 @@ PEGTransformerFactory::FinalizeBetweenInLikeExpressionTrampoline(PEGTransformer void PEGTransformerFactory::InitializeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); + auto &repeat_opt = list_pr.GetChild(2).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(2 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("CastOperator"), repeat_children[child_idx].get()}, 1 + child_idx); + } + } else { + process.ReserveChildSlots(2 - 1); + } process.PushChild({transformer.GetRule("InClause"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } bool has_result {}; auto &has_result_opt = list_pr.GetChild(0).Cast(); has_result = has_result_opt.HasResult(); auto in_clause = process.TakeResult>(0); - auto result = TransformInPredicate(transformer, has_result, std::move(in_clause)); + optional>> cast_operator {}; + if (dynamic_child_count > 0) { + vector> cast_operator_value; + for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { + cast_operator_value.push_back(process.TakeResult>(i)); + } + cast_operator = std::move(cast_operator_value); + } + auto result = TransformInPredicate(transformer, has_result, std::move(in_clause), std::move(cast_operator)); return transformer.MakeResult(std::move(result)); } @@ -17781,14 +18065,10 @@ PEGTransformerFactory::FinalizeSliceExpressionTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); - auto &step_slice_bound_opt = list_pr.GetChild(2).Cast(); - if (step_slice_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 2); - } - auto &end_slice_bound_opt = list_pr.GetChild(1).Cast(); - if (end_slice_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("EndSliceBound"), end_slice_bound_opt.GetResult()}, 1); + process.ReserveChildSlots(2); + auto &slice_upper_bound_opt = list_pr.GetChild(1).Cast(); + if (slice_upper_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("SliceUpperBound"), slice_upper_bound_opt.GetResult()}, 1); } auto &expression_opt = list_pr.GetChild(0).Cast(); if (expression_opt.HasResult()) { @@ -17802,70 +18082,91 @@ PEGTransformerFactory::FinalizeSliceBoundTrampoline(PEGTransformer &transformer, if (process.child_results[0]) { expression = process.TakeResult>(0); } - optional> end_slice_bound {}; + optional>> slice_upper_bound {}; if (process.child_results[1]) { - end_slice_bound = process.TakeResult>(1); + slice_upper_bound = process.TakeResult>>(1); } - optional> step_slice_bound {}; - if (process.child_results[2]) { - step_slice_bound = process.TakeResult>(2); - } - auto result = TransformSliceBound(transformer, std::move(expression), std::move(end_slice_bound), - std::move(step_slice_bound)); + auto result = TransformSliceBound(transformer, std::move(expression), std::move(slice_upper_bound)); return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - auto &end_slice_value_opt = list_pr.GetChild(1).Cast(); - if (end_slice_value_opt.HasResult()) { - process.PushChild({transformer.GetRule("EndSliceValue"), end_slice_value_opt.GetResult()}, 0); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr -PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - optional> end_slice_value {}; - if (process.child_results[0]) { - end_slice_value = process.TakeResult>(0); - } - auto result = TransformEndSliceBound(transformer, std::move(end_slice_value)); - return transformer.MakeResult>(std::move(result)); +PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult>>(0); + return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + process.PushChild({transformer.GetRule("StepSliceBound"), list_pr.GetChild(2)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto step_slice_bound = process.TakeResult>(0); + auto result = TransformMinusStepSliceBound(transformer, std::move(step_slice_bound)); + return transformer.MakeResult>>(std::move(result)); +} + +void PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + auto &step_slice_bound_opt = list_pr.GetChild(1).Cast(); + if (step_slice_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 1); } - process.PushChild({*child_rule, choice_result}, 0); + process.PushChild({transformer.GetRule("EndSliceBound"), list_pr.GetChild(0)}, 0); } arena_ptr -PEGTransformerFactory::FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult>(0); - return transformer.MakeResult>(std::move(result)); +PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto end_slice_bound = process.TakeResult>(0); + optional> step_slice_bound {}; + if (process.child_results[1]) { + step_slice_bound = process.TakeResult>(1); + } + auto result = TransformEndStepSliceBound(transformer, std::move(end_slice_bound), std::move(step_slice_bound)); + return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, +void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + auto &expression_opt = list_pr.GetChild(1).Cast(); + if (expression_opt.HasResult()) { + process.PushChild({transformer.GetRule("Expression"), expression_opt.GetResult()}, 0); + } } arena_ptr -PEGTransformerFactory::FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, +PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto result = TransformEndSliceMinus(transformer); + optional> expression {}; + if (process.child_results[0]) { + expression = process.TakeResult>(0); + } + auto result = TransformEndSliceBound(transformer, std::move(expression)); return transformer.MakeResult>(std::move(result)); } @@ -18915,15 +19216,15 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(8); - auto &returning_clause_opt = list_pr.GetChild(9).Cast(); + auto &returning_clause_opt = list_pr.GetChild(10).Cast(); if (returning_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("ReturningClause"), returning_clause_opt.GetResult()}, 7); } - auto &on_conflict_clause_opt = list_pr.GetChild(8).Cast(); + auto &on_conflict_clause_opt = list_pr.GetChild(9).Cast(); if (on_conflict_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("OnConflictClause"), on_conflict_clause_opt.GetResult()}, 6); } - process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(7)}, 5); + process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(8)}, 5); auto &insert_column_list_opt = list_pr.GetChild(6).Cast(); if (insert_column_list_opt.HasResult()) { process.PushChild({transformer.GetRule("InsertColumnList"), insert_column_list_opt.GetResult()}, 4); @@ -18946,6 +19247,7 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & arena_ptr PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); optional with_clause {}; if (process.child_results[0]) { with_clause = process.TakeResult(0); @@ -18963,6 +19265,9 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[4]) { insert_column_list = process.TakeResult>(4); } + bool has_result {}; + auto &has_result_opt = list_pr.GetChild(7).Cast(); + has_result = has_result_opt.HasResult(); auto insert_values = process.TakeResult(5); optional> on_conflict_clause {}; if (process.child_results[6]) { @@ -18972,9 +19277,10 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[7]) { returning_clause = process.TakeResult>>(7); } - auto result = TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), - by_name_or_position, insert_column_list, std::move(insert_values), - std::move(on_conflict_clause), std::move(returning_clause)); + auto result = + TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), + by_name_or_position, insert_column_list, has_result, std::move(insert_values), + std::move(on_conflict_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -19327,18 +19633,18 @@ void PEGTransformerFactory::InitializeOnConflictUpdateTrampoline(PEGTransformer if (where_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("WhereClause"), where_clause_opt.GetResult()}, 1); } - process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 0); + process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 0); } arena_ptr PEGTransformerFactory::FinalizeOnConflictUpdateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto update_set_clause = process.TakeResult>(0); + auto update_set_clause_list = process.TakeResult>(0); optional> where_clause {}; if (process.child_results[1]) { where_clause = process.TakeResult>(1); } - auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause), std::move(where_clause)); + auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause_list), std::move(where_clause)); return transformer.MakeResult>(std::move(result)); } @@ -21479,7 +21785,11 @@ PEGTransformerFactory::FinalizeInnerTableRefTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableSubqueryTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(4); + process.ReserveChildSlots(5); + auto &sample_clause_opt = list_pr.GetChild(4).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 4); + } auto &table_alias_opt = list_pr.GetChild(3).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 3); @@ -21511,8 +21821,12 @@ PEGTransformerFactory::FinalizeTableSubqueryTrampoline(PEGTransformer &transform if (process.child_results[3]) { table_alias = process.TakeResult(3); } - auto result = - TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), table_alias); + optional> sample_clause {}; + if (process.child_results[4]) { + sample_clause = process.TakeResult>(4); + } + auto result = TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), + table_alias, std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -21581,7 +21895,11 @@ PEGTransformerFactory::FinalizeTableAliasColonTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeValuesRefTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); + process.ReserveChildSlots(4); + auto &sample_clause_opt = list_pr.GetChild(3).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 3); + } auto &table_alias_opt = list_pr.GetChild(2).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 2); @@ -21604,7 +21922,12 @@ arena_ptr PEGTransformerFactory::FinalizeValuesRefTrampoli if (process.child_results[2]) { table_alias = process.TakeResult(2); } - auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias); + optional> sample_clause {}; + if (process.child_results[3]) { + sample_clause = process.TakeResult>(3); + } + auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias, + std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -22077,7 +22400,11 @@ PEGTransformerFactory::FinalizeTableFunctionTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableFunctionLateralOptTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(5); + process.ReserveChildSlots(6); + auto &sample_clause_opt = list_pr.GetChild(5).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 5); + } auto &table_alias_opt = list_pr.GetChild(4).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 4); @@ -22111,8 +22438,13 @@ PEGTransformerFactory::FinalizeTableFunctionLateralOptTrampoline(PEGTransformer if (process.child_results[4]) { table_alias = process.TakeResult(4); } + optional> sample_clause {}; + if (process.child_results[5]) { + sample_clause = process.TakeResult>(5); + } auto result = TransformTableFunctionLateralOpt(transformer, lateral, qualified_table_function, - std::move(table_function_arguments), with_ordinality, table_alias); + std::move(table_function_arguments), with_ordinality, table_alias, + std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -22283,15 +22615,17 @@ PEGTransformerFactory::FinalizeFunctionArgumentTrampoline(PEGTransformer &transf void PEGTransformerFactory::InitializeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("NamedParameter"), list_pr.GetChild(0)}, 0); + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("Expression"), list_pr.GetChild(2)}, 1); + process.PushChild({transformer.GetRule("TypeFuncName"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto named_parameter = process.TakeResult(0); - auto result = TransformNamedFunctionArgument(transformer, std::move(named_parameter)); + auto type_func_name = process.TakeResult(0); + auto expression = process.TakeResult>(1); + auto result = TransformNamedFunctionArgument(transformer, type_func_name, std::move(expression)); return transformer.MakeResult(std::move(result)); } @@ -23298,6 +23632,21 @@ PEGTransformerFactory::FinalizeSampleClauseTrampoline(PEGTransformer &transforme return transformer.MakeResult>(std::move(result)); } +void PEGTransformerFactory::InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("SampleEntry"), list_pr.GetChild(1)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto sample_entry = process.TakeResult>(0); + auto result = TransformSelectSampleClause(transformer, std::move(sample_entry)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -24250,12 +24599,12 @@ PEGTransformerFactory::FinalizeExpressionOptIdentifierTrampoline(PEGTransformer GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); auto expression = process.TakeResult>(0); - optional identifier {}; - auto &identifier_opt = list_pr.GetChild(1).Cast(); - if (identifier_opt.HasResult()) { - identifier = identifier_opt.GetResult().Cast().identifier; + optional bare_col_label {}; + auto &bare_col_label_opt = list_pr.GetChild(1).Cast(); + if (bare_col_label_opt.HasResult()) { + bare_col_label = bare_col_label_opt.GetResult().Cast().identifier; } - auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), identifier); + auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), bare_col_label); return transformer.MakeResult>(std::move(result)); } @@ -24827,7 +25176,7 @@ void PEGTransformerFactory::InitializeUpdateStatementTrampoline(PEGTransformer & if (from_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("FromClause"), from_clause_opt.GetResult()}, 3); } - process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 2); + process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 2); process.PushChild({transformer.GetRule("UpdateTarget"), list_pr.GetChild(2)}, 1); auto &with_clause_opt = list_pr.GetChild(0).Cast(); if (with_clause_opt.HasResult()) { @@ -24843,7 +25192,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo with_clause = process.TakeResult(0); } auto update_target = process.TakeResult>(1); - auto update_set_clause = process.TakeResult>(2); + auto update_set_clause_list = process.TakeResult>(2); optional> from_clause {}; if (process.child_results[3]) { from_clause = process.TakeResult>(3); @@ -24857,7 +25206,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo returning_clause = process.TakeResult>>(5); } auto result = TransformUpdateStatement(transformer, std::move(with_clause), std::move(update_target), - std::move(update_set_clause), std::move(from_clause), + std::move(update_set_clause_list), std::move(from_clause), std::move(where_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -24937,6 +25286,32 @@ PEGTransformerFactory::FinalizeUpdateAliasTrampoline(PEGTransformer &transformer return transformer.MakeResult(result); } +void PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = list_items.size(); + process.ReserveChildSlots(1 + dynamic_child_count - 1); + for (idx_t i = list_items.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("UpdateSetClause"), list_items[child_idx].get()}, 0 + child_idx); + } +} + +arena_ptr +PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = dynamic_list_items.size(); + vector> update_set_clause; + for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { + update_set_clause.push_back(process.TakeResult>(i)); + } + auto result = TransformUpdateSetClauseList(transformer, std::move(update_set_clause)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -24979,32 +25354,6 @@ PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline(PEGTransformer &transfor return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = list_items.size(); - process.ReserveChildSlots(1 + dynamic_child_count - 1); - for (idx_t i = list_items.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("UpdateSetElement"), list_items[child_idx].get()}, 0 + child_idx); - } -} - -arena_ptr -PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = dynamic_list_items.size(); - vector>> update_set_element; - for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - update_set_element.push_back(process.TakeResult>>(i)); - } - auto result = TransformUpdateSetElementList(transformer, std::move(update_set_element)); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -25019,7 +25368,7 @@ PEGTransformerFactory::FinalizeUpdateSetElementTrampoline(PEGTransformer &transf auto update_set_column_target = process.TakeResult(0); auto expression = process.TakeResult>(1); auto result = TransformUpdateSetElement(transformer, update_set_column_target, std::move(expression)); - return transformer.MakeResult>>(std::move(result)); + return transformer.MakeResult>(std::move(result)); } void PEGTransformerFactory::InitializeUpdateSetColumnTargetTrampoline(PEGTransformer &transformer, @@ -25101,29 +25450,91 @@ arena_ptr PEGTransformerFactory::FinalizeUseTargetTrampoli void PEGTransformerFactory::InitializeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process && + (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || + choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || + choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { + return; + } + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr PEGTransformerFactory::FinalizeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto schema_name = list_pr.GetChild(0).Cast().identifier; - auto result = TransformSchemaNameAsUseTarget(transformer, schema_name); + string child; + if (process.child_results[0]) { + child = process.TakeResult(0); + } else { + auto &list_pr = process.parse_result.Cast(); + auto &choice_result = list_pr.Child(0).GetResult(); + if (choice_result.type == ParseResultType::IDENTIFIER) { + child = choice_result.Cast().identifier.GetIdentifierName(); + } else if (choice_result.type == ParseResultType::KEYWORD) { + child = choice_result.Cast().keyword; + } else if (choice_result.type == ParseResultType::STRING) { + child = choice_result.Cast().result; + } else if (choice_result.type == ParseResultType::OPERATOR) { + child = choice_result.Cast().operator_token; + } else { + child = TransformIdentifierOrKeyword(transformer, choice_result); + } + } + auto result = TransformSchemaNameAsUseTarget(transformer, child); return transformer.MakeResult(result); } void PEGTransformerFactory::InitializeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process && + (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || + choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || + choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { + return; + } + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr PEGTransformerFactory::FinalizeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto catalog_name = list_pr.GetChild(0).Cast().identifier; - auto result = TransformCatalogNameAsUseTarget(transformer, catalog_name); + string child; + if (process.child_results[0]) { + child = process.TakeResult(0); + } else { + auto &list_pr = process.parse_result.Cast(); + auto &choice_result = list_pr.Child(0).GetResult(); + if (choice_result.type == ParseResultType::IDENTIFIER) { + child = choice_result.Cast().identifier.GetIdentifierName(); + } else if (choice_result.type == ParseResultType::KEYWORD) { + child = choice_result.Cast().keyword; + } else if (choice_result.type == ParseResultType::STRING) { + child = choice_result.Cast().result; + } else if (choice_result.type == ParseResultType::OPERATOR) { + child = choice_result.Cast().operator_token; + } else { + child = TransformIdentifierOrKeyword(transformer, choice_result); + } + } + auto result = TransformCatalogNameAsUseTarget(transformer, child); return transformer.MakeResult(result); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp index 3bc06b644..74a0b8c0e 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp @@ -44,7 +44,8 @@ static void SetGenericCopyOptionExpression(GenericCopyOption ©_option, uniqu ExpressionClassToString(child->GetExpressionClass())); } } - } else if (expression->GetExpressionType() == ExpressionType::FUNCTION) { + } else if (expression->GetExpressionType() == ExpressionType::FUNCTION || + expression->GetExpressionType() == ExpressionType::VALUE_PARAMETER) { copy_option.expression = std::move(expression); } else if (expression->GetExpressionType() == ExpressionType::STAR) { copy_option.children.push_back(Value("*")); diff --git a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp index 4bd7f30df..0f152888e 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp @@ -10,7 +10,7 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, const optional> &insert_column_list, - InsertValues insert_values, optional> on_conflict_clause, + bool has_overriding_clause, InsertValues insert_values, optional> on_conflict_clause, optional>> returning_clause) { auto result = make_uniq(); auto &node = *result->node; @@ -26,6 +26,10 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( throw ParserException( "You can not provide both a column list and DEFAULT VALUES, please remove one of the two"); } + if (has_overriding_clause && insert_values.default_values) { + throw ParserException("OVERRIDING SYSTEM|USER VALUE can not be combined with DEFAULT VALUES, " + "please remove one of the two"); + } if (insert_values.default_values) { node.default_values = true; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp index 0424b6a21..0c168a38d 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp @@ -85,12 +85,15 @@ arena_ptr PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto &pivot_columns = list_pr.Child(2); + auto &pivot_aggregates = list_pr.Child(3); + auto &pivot_group = list_pr.Child(4); + if (!pivot_columns.HasResult() && !pivot_aggregates.HasResult() && !pivot_group.HasResult()) { + throw ParserException("PIVOT requires an ON, USING or GROUP BY clause"); + } if (!process.child_results[4]) { bool has_parameters = transformer.ParamCount() > process.manual_state; process.SetChildResult(4, transformer.MakeResult(has_parameters)); - auto &pivot_columns = list_pr.Child(2); - auto &pivot_aggregates = list_pr.Child(3); - auto &pivot_group = list_pr.Child(4); bool pushed_child = false; if (pivot_group.HasResult()) { process.PushChild({pivot_group.GetResult()}, 3); @@ -110,8 +113,6 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor } auto source = process.TakeResult>(0); auto has_parameters = process.TakeResult(4); - auto &pivot_columns = list_pr.Child(2); - auto &pivot_group = list_pr.Child(4); auto select_node = make_uniq(); if (!pivot_columns.HasResult()) { select_node->from_table = std::move(source); @@ -160,7 +161,6 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor select_node->select_list.push_back(make_uniq()); auto pivot_ref = make_uniq(); pivot_ref->source = std::move(source); - auto &pivot_aggregates = list_pr.Child(3); if (pivot_aggregates.HasResult()) { pivot_ref->aggregates = process.TakeResult>>(2); } else { diff --git a/src/duckdb/src/parser/peg/transformer/transform_select.cpp b/src/duckdb/src/parser/peg/transformer/transform_select.cpp index b06a869b8..31a17c55c 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_select.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_select.cpp @@ -317,9 +317,10 @@ PEGTransformerFactory::FinalizeSimpleSelectTrampoline(PEGTransformer &transforme } FunctionArgument PEGTransformerFactory::TransformNamedFunctionArgument(PEGTransformer &transformer, - MacroParameter named_parameter) { - named_parameter.expression->SetAlias(named_parameter.name); - return FunctionArgument(named_parameter.name, std::move(named_parameter.expression)); + const Identifier &type_func_name, + unique_ptr expression) { + expression->SetAlias(type_func_name); + return FunctionArgument(type_func_name, std::move(expression)); } FunctionArgument PEGTransformerFactory::TransformPositionalFunctionArgument(PEGTransformer &transformer, @@ -769,7 +770,10 @@ JoinType PEGTransformerFactory::TransformInnerJoin(PEGTransformer &transformer) unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, vector table_function_arguments, const optional &with_ordinality, - const optional &table_alias) { + const optional &table_alias, optional> sample_clause) { + if (lateral.value_or(false) && sample_clause) { + throw ParserException("TABLESAMPLE cannot be applied to a LATERAL table function"); + } auto result = make_uniq(); result->with_ordinality = @@ -779,6 +783,9 @@ unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( result->alias = table_alias->name; result->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + result->sample = std::move(*sample_clause); + } return std::move(result); } @@ -924,6 +931,7 @@ vector PEGTransformerFactory::GroupByExpressionUnfolding(GroupByExp for (auto &child_expr : group_by_expr.children) { auto child_sets = GroupByExpressionUnfolding(child_expr, map, result); result_sets.insert(result_sets.end(), child_sets.begin(), child_sets.end()); + CheckGroupingSetMax(result_sets.size()); } } else if (group_by_expr.type == GroupByExpressionInfoType::CUBE || group_by_expr.type == GroupByExpressionInfoType::ROLLUP) { @@ -1348,10 +1356,14 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme const optional &table_alias_colon, const optional &lateral, unique_ptr subquery_reference, - const optional &table_alias) { + const optional &table_alias, + optional> sample_clause) { if (table_alias_colon && table_alias) { throw ParserException("Table reference %s cannot have two aliases", subquery_reference->ToString()); } + if (lateral.value_or(false) && sample_clause) { + throw ParserException("TABLESAMPLE cannot be applied to a LATERAL subquery"); + } if (table_alias_colon) { subquery_reference->alias = *table_alias_colon; } @@ -1359,6 +1371,9 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme subquery_reference->alias = table_alias->name; subquery_reference->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + subquery_reference->sample = std::move(*sample_clause); + } return subquery_reference; } @@ -1395,7 +1410,8 @@ Identifier PEGTransformerFactory::TransformTableAliasColon(PEGTransformer &trans unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias) { + const optional &table_alias, + optional> sample_clause) { if (table_alias_colon && table_alias) { throw ParserException("Table reference cannot have two aliases"); } @@ -1407,6 +1423,9 @@ unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &t subquery_ref->alias = table_alias->name; subquery_ref->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + subquery_ref->sample = std::move(*sample_clause); + } return std::move(subquery_ref); } @@ -1657,6 +1676,11 @@ unique_ptr PEGTransformerFactory::TransformSampleClause(PEGTransf return sample_entry; } +unique_ptr PEGTransformerFactory::TransformSelectSampleClause(PEGTransformer &transformer, + unique_ptr sample_entry) { + return sample_entry; +} + vector> PEGTransformerFactory::TransformWindowClause(PEGTransformer &transformer, vector> window_definition) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_update.cpp b/src/duckdb/src/parser/peg/transformer/transform_update.cpp index d8c447127..439621d07 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_update.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_update.cpp @@ -81,20 +81,26 @@ unique_ptr PEGTransformerFactory::TransformUpdateSetTuple(PEGTran return result; } -unique_ptr PEGTransformerFactory::TransformUpdateSetElementList( - PEGTransformer &transformer, vector>> update_set_element) { +unique_ptr +PEGTransformerFactory::TransformUpdateSetClauseList(PEGTransformer &transformer, + vector> update_set_info) { auto result = make_uniq(); - for (auto &element : update_set_element) { - result->columns.emplace_back(std::move(element.first)); - result->expressions.push_back(std::move(element.second)); + for (auto &info : update_set_info) { + for (idx_t i = 0; i < info->columns.size(); i++) { + result->columns.emplace_back(std::move(info->columns[i])); + result->expressions.push_back(std::move(info->expressions[i])); + } } return result; } -pair> -PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, - unique_ptr expression) { - return {update_set_column_target, std::move(expression)}; +unique_ptr PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, + const string &update_set_column_target, + unique_ptr expression) { + auto result = make_uniq(); + result->columns.emplace_back(update_set_column_target); + result->expressions.push_back(std::move(expression)); + return result; } string PEGTransformerFactory::TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, diff --git a/src/duckdb/src/parser/peg/transformer/transform_use.cpp b/src/duckdb/src/parser/peg/transformer/transform_use.cpp index e702245ac..67b487a30 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_use.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_use.cpp @@ -17,19 +17,17 @@ unique_ptr PEGTransformerFactory::TransformUseStatement(PEGTransfo return make_uniq("schema", std::move(value_expr), SetScope::AUTOMATIC); } -// UseTarget <- UseTargetCatalogSchema / SchemaName / CatalogName +// UseTarget <- UseTargetCatalogSchema / SchemaNameAsUseTarget / CatalogNameAsUseTarget +// SchemaNameAsUseTarget <- SchemaName / StringLiteral QualifiedName PEGTransformerFactory::TransformSchemaNameAsUseTarget(PEGTransformer &transformer, - const Identifier &schema_name) { - QualifiedName result; - result = QualifiedName(schema_name); - return result; + const string &schema_name) { + return QualifiedName(Identifier(schema_name)); } +// CatalogNameAsUseTarget <- CatalogName / StringLiteral QualifiedName PEGTransformerFactory::TransformCatalogNameAsUseTarget(PEGTransformer &transformer, - const Identifier &catalog_name) { - QualifiedName result; - result = QualifiedName(catalog_name); - return result; + const string &catalog_name) { + return QualifiedName(Identifier(catalog_name)); } // UseTargetCatalogSchema <- CatalogName '.' ReservedSchemaName DotIdentifier* diff --git a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp index eca1e184c..22b371bbb 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp @@ -58,6 +58,8 @@ VacuumOptions PEGTransformerFactory::TransformVacuumParensOptions(PEGTransformer } if (StringUtil::CIEquals(option, "analyze")) { options.analyze = true; + } else { + throw ParserException("unrecognized VACUUM option \"%s\"", option); } } return options; diff --git a/src/duckdb/src/planner/bind_context.cpp b/src/duckdb/src/planner/bind_context.cpp index a2d68d4d2..bf8423944 100644 --- a/src/duckdb/src/planner/bind_context.cpp +++ b/src/duckdb/src/planner/bind_context.cpp @@ -557,7 +557,7 @@ string CandidateColumnMessage(const vector &candidate_columns, const void BindContext::GenerateAllColumnExpressions(StarExpression &expr, vector> &new_select_list) { - if (bindings_list.empty()) { + if (bindings_list.empty() && expr.RelationName().empty()) { throw BinderException("* expression without FROM clause!"); } StarBindState star_state(new_select_list); @@ -625,6 +625,21 @@ void BindContext::GenerateAllColumnExpressions(StarExpression &expr, if (!binding) { binding = GetMatchingBinding(expr.RelationName(), expr); if (!binding) { + // Expand the nearest enclosing match into column references. Expression binding then + // resolves their correlation depth, just as it does for explicit qualified columns. + auto &scopes = binder.GetEnclosingScopes(); + for (idx_t i = scopes.size(); i > 0; i--) { + auto &scope_context = scopes[i - 1].get().GetBinder().bind_context; + ErrorData scope_error; + if (scope_context.GetBinding(expr.RelationName(), scope_error) || + scope_context.GetMatchingBinding(expr.RelationName(), expr)) { + scope_context.GenerateAllColumnExpressions(expr, new_select_list); + return; + } + } + if (bindings_list.empty()) { + throw BinderException("* expression without FROM clause!"); + } error.Throw(); } is_struct_ref = true; diff --git a/src/duckdb/src/planner/binder.cpp b/src/duckdb/src/planner/binder.cpp index bedc1860c..30aad5b4c 100644 --- a/src/duckdb/src/planner/binder.cpp +++ b/src/duckdb/src/planner/binder.cpp @@ -146,6 +146,11 @@ BoundStatement Binder::Bind(QueryNode &node) { } BoundStatement Binder::Bind(TableRef &ref) { + for (auto &column_alias : ref.column_name_alias) { + if (column_alias.empty()) { + throw BinderException(ref, "column alias cannot be empty"); + } + } BoundStatement result; switch (ref.type) { case TableReferenceType::BASE_TABLE: diff --git a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp index 9dfe3d128..89bf6a80b 100644 --- a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp @@ -127,6 +127,21 @@ BindResult BaseSelectBinder::BindAggregate(FunctionExpression &aggr, AggregateFu if (inside_aggregate) { throw BinderException(aggr, "aggregate function calls cannot be nested"); } + // an aggregate is computed over the rows of the query, not over the elements an enclosing lambda is applied to - + // so the parameters of the enclosing lambdas can not be referenced in the aggregate + struct InaccessibleLambdaBindings { + InaccessibleLambdaBindings(ExpressionBinder &binder_p, idx_t count) + : binder(binder_p), previous_count(binder.inaccessible_lambda_binding_count) { + binder.inaccessible_lambda_binding_count = count; + } + ~InaccessibleLambdaBindings() { + binder.inaccessible_lambda_binding_count = previous_count; + } + ExpressionBinder &binder; + idx_t previous_count; + }; + InaccessibleLambdaBindings inaccessible_lambdas(*this, lambda_bindings ? lambda_bindings->size() : 0); + // first bind the child of the aggregate expression (if any) this->bound_aggregate = true; unique_ptr bound_filter; diff --git a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp index 23945d2a6..6c57f4cd6 100644 --- a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp @@ -60,6 +60,9 @@ void ExpressionBinder::QualifyColumnNames(ExpressionBinder &expression_binder, u BindResult ExpressionBinder::BindExpression(LambdaRefExpression &lambda_ref, idx_t depth) { D_ASSERT(lambda_bindings && lambda_ref.LambdaIndex() < lambda_bindings->size()); + if (lambda_ref.LambdaIndex() < inaccessible_lambda_binding_count) { + throw BinderException(lambda_ref, "aggregate functions cannot reference lambda parameters"); + } return (*lambda_bindings)[lambda_ref.LambdaIndex()].Bind(lambda_ref, depth); } diff --git a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp index 79bd7e184..37c6c38c1 100644 --- a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp @@ -106,6 +106,25 @@ struct ListReduceRebindResult { bool override_has_accumulator_type = false; }; +//! Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen accumulator type +//! when decimals are involved +static void CastDecimalLambdaToAccumulatorType(ClientContext &context, BindResult &bind_lambda_result, + const LogicalType &accumulator_type) { + auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); + const auto return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); + if (return_type == accumulator_type || + (!TypeContainsDecimal(accumulator_type) && !TypeContainsDecimal(return_type))) { + return; + } + auto cast_expr = BoundCastExpression::AddCastToType(context, std::move(rebound_lambda_expr.LambdaExprMutable()), + accumulator_type); + if (!cast_expr) { + throw BinderException("Could not cast lambda return type %s to accumulator type %s", return_type.ToString(), + accumulator_type.ToString()); + } + rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); +} + ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t depth, const vector &function_child_types, bind_lambda_function_t bind_lambda_function, @@ -158,22 +177,7 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t bind_lambda_result.error.Throw(); } - auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); - // Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen - // accumulator type when decimals are involved. - if (TypeContainsDecimal(accumulator_type) || - TypeContainsDecimal(rebound_lambda_expr.LambdaExpr()->GetReturnType())) { - if (rebound_lambda_expr.LambdaExpr()->GetReturnType() != accumulator_type) { - const auto old_return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); - auto cast_expr = BoundCastExpression::AddCastToType( - context, std::move(rebound_lambda_expr.LambdaExprMutable()), accumulator_type); - if (!cast_expr) { - throw BinderException("Could not cast lambda return type %s to accumulator type %s", - old_return_type.ToString(), accumulator_type.ToString()); - } - rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); - } - } + CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); result.did_rebind = true; result.capture_child_types = std::move(rebind_child_types); } else if (accumulator_type != list_child_type) { @@ -188,6 +192,7 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t if (bind_lambda_result.HasError()) { bind_lambda_result.error.Throw(); } + CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); result.did_rebind = true; result.override_accumulator_type_storage = accumulator_type; result.override_has_accumulator_type = true; @@ -461,7 +466,8 @@ BindResult ExpressionBinder::BindLambdaFunction(FunctionExpression &function, Sc auto &lambda_expr = args[lambda_expr_idx].GetExpressionMutable()->Cast(); unique_ptr lambda_expr_copy; - const bool is_list_reduce = func.name == "list_reduce"; + // the aliases of list_reduce (e.g. reduce) are separate catalog entries + const bool is_list_reduce = func.name == "list_reduce" || func.alias_of == "list_reduce"; if (is_list_reduce) { // We have to make this copy before binding in case we have to double-bind lambda_expr_copy = lambda_expr.Copy(); diff --git a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp index ca14f9277..5953ec8ba 100644 --- a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp @@ -7,6 +7,7 @@ #include "duckdb/common/exception/binder_exception.hpp" #include "duckdb/common/unique_ptr.hpp" #include "duckdb/function/scalar_macro_function.hpp" +#include "duckdb/main/client_context.hpp" #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/parser/expression/subquery_expression.hpp" @@ -204,6 +205,8 @@ void ExpressionBinder::UnfoldMacroExpression(FunctionExpression &function, Scala BindResult ExpressionBinder::BindMacro(FunctionExpression &function, ScalarMacroCatalogEntry ¯o_func, idx_t depth, unique_ptr &expr) { auto stack_checker = StackCheck(*expr, 3); + // expanding nested macros can take a long time, allow the query to be interrupted + context.InterruptCheck(); // unfold the macro expression UnfoldMacroExpression(function, macro_func, expr, depth); diff --git a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp index 634332130..12c63762b 100644 --- a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp @@ -80,6 +80,7 @@ BindResult ExpressionBinder::BindExpression(TypeExpression &type_expr, idx_t dep for (auto ¶m : unbound_parameters) { // Otherwise, try to fold it to a constant value ConstantBinder binder(this->binder, context, StringUtil::Format("Type parameter for type '%s'", type_name)); + binder.ContinueStackDepth(*this); auto expr = param->Copy(); auto bound_expr = binder.Bind(expr); diff --git a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp index 77a0e953d..d44038f98 100644 --- a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp @@ -226,7 +226,9 @@ BindResult BaseSelectBinder::BindWindowExpression(WindowExpression &window, idx_ // bind the aggregate FunctionBinder function_binder(binder); - auto window_bound_aggregate = function_binder.BindAggregateFunction(func, std::move(arguments), error, nullptr); + auto window_bound_aggregate = function_binder.BindAggregateFunction( + func, std::move(arguments), error, nullptr, + window.Distinct() ? AggregateType::DISTINCT : AggregateType::NON_DISTINCT); // No function found, throw an error if (!window_bound_aggregate) { error.AddQueryLocation(window); diff --git a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp index c0709ba68..d798f33ae 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp @@ -142,6 +142,9 @@ void CTEBindState::Bind(CTEBinding &binding) { // names are picked from the LHS, unless aliases are explicitly specified names = query.names; for (idx_t i = 0; i < aliases.size() && i < names.size(); i++) { + if (aliases[i].empty()) { + throw BinderException("column alias cannot be empty"); + } names[i] = aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp index 8bf83d31b..9ce269546 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp @@ -42,6 +42,9 @@ BoundStatement Binder::BindNode(RecursiveCTENode &statement) { // names are picked from the LHS, unless aliases are explicitly specified result.names = left.names; for (idx_t i = 0; i < statement.aliases.size() && i < result.names.size(); i++) { + if (statement.aliases[i].empty()) { + throw BinderException("column alias cannot be empty"); + } result.names[i] = statement.aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp index 27ce506ff..49843c812 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp @@ -725,9 +725,6 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from } bind_state.AddRegularColumn(); - if (can_group_by_all && is_window) { - throw BinderException("Cannot group on a window clause"); - } if (can_group_by_all && result.unnests.SelectList().size() > unnest_count) { throw BinderException("Cannot group on an UNNEST or UNLIST clause"); } @@ -735,6 +732,10 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from if (select_binder.BoundAggregates()) { throw BinderException("Cannot mix aggregates with non-aggregated columns!"); } + if (is_window) { + // a window over aggregates (e.g. sum(sum(x)) OVER ()) is fine, a window over a group is not + throw BinderException("Cannot group on a window clause"); + } // we are forcing aggregates, and the node has columns bound // this entry becomes a group group_by_all_indexes.push_back(i); diff --git a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp index 52387f016..5b4e01ca8 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp @@ -5,6 +5,7 @@ #include "duckdb/planner/expression_binder.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/common/limits.hpp" +#include "duckdb/main/client_context.hpp" #include "duckdb/main/config.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/parser/expression/comparison_expression.hpp" @@ -18,6 +19,8 @@ namespace duckdb { unique_ptr Binder::BindTableMacro(FunctionExpression &function, TableMacroCatalogEntry ¯o_func, idx_t depth) { + // expanding nested macros can take a long time, allow the query to be interrupted + context.InterruptCheck(); // validate the arguments and separate positional and default arguments vector> positional_arguments; InsertionOrderPreservingMap, Identifier, identifier_map_t> named_arguments; diff --git a/src/duckdb/src/planner/binder/statement/bind_attach.cpp b/src/duckdb/src/planner/binder/statement/bind_attach.cpp index 4a19a052b..81718c46e 100644 --- a/src/duckdb/src/planner/binder/statement/bind_attach.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_attach.cpp @@ -30,7 +30,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { if (stmt.info->parsed_path) { TableFunctionBinder path_binder(*this, context, "Attach", "Attach path"); auto bound_path = path_binder.Bind(stmt.info->parsed_path); - auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path); + auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path, true); if (path_val.IsNull()) { throw BinderException("ATTACH path expression must not evaluate to NULL"); } @@ -43,7 +43,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { unordered_map kv_options; for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for ATTACH option \"" + entry.first + "\""); } diff --git a/src/duckdb/src/planner/binder/statement/bind_connect.cpp b/src/duckdb/src/planner/binder/statement/bind_connect.cpp index 59f485d09..23823cbba 100644 --- a/src/duckdb/src/planner/binder/statement/bind_connect.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_connect.cpp @@ -19,7 +19,7 @@ BoundStatement Binder::Bind(ConnectStatement &stmt) { TableFunctionBinder option_binder(*this, context, "Connect", "Connect parameter"); for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for CONNECT option \"" + entry.first + "\""); diff --git a/src/duckdb/src/planner/binder/statement/bind_copy.cpp b/src/duckdb/src/planner/binder/statement/bind_copy.cpp index 756aa4d9e..6a8d67aee 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy.cpp @@ -782,7 +782,7 @@ void Binder::BindCopyFormat(CopyInfo &info) { if (info.file_path_expression) { auto inputs = BindCopyOption(context, *this, "filename", info.file_path_expression, conversion_policy); if (inputs.size() != 1 || inputs[0].type().id() != LogicalTypeId::VARCHAR) { - throw InternalException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); + throw BinderException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); } if (!info.file_path.empty()) { throw InternalException("Both a file path and a file path expression were provided for COPY - only one of " diff --git a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp index 7596bfa3c..af07d6889 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp @@ -51,14 +51,13 @@ unique_ptr Binder::BindCopyDatabaseSchema(Catalog &from_databas unique_ptr Binder::BindCopyDatabaseData(Catalog &source_catalog, const Identifier &target_database_name) { - auto source_schemas = source_catalog.GetSchemas(context); - - // We can just use ExtractEntries here because the order doesn't matter - ExportEntries entries; - PhysicalExport::ExtractEntries(context, source_schemas, entries); + auto entries = PhysicalExport::GetNaiveExportOrder(context, source_catalog); vector> insert_nodes; - for (auto &table_ref : entries.tables) { + for (auto &table_ref : entries) { + if (table_ref.get().type != CatalogType::TABLE_ENTRY) { + continue; + } auto &table = table_ref.get().Cast(); // generate the insert statement InsertStatement insert_stmt; diff --git a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp index 25be3c9ac..13c4247b3 100644 --- a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp @@ -43,6 +43,11 @@ static void VerifyCompressionType(ClientContext &context, optional_ptrCast(); for (auto &col : base.columns.Logical()) { auto compression_type = col.CompressionType(); + if (col.Generated() && compression_type != CompressionType::COMPRESSION_AUTO) { + throw BinderException("Can't set a compression type on generated column %s, generated columns are " + "not stored", + col.Name()); + } auto compression_availability_result = CompressionTypeIsAvailable(compression_type, storage_manager); if (!compression_availability_result.IsAvailable()) { if (compression_availability_result.IsDeprecated()) { diff --git a/src/duckdb/src/planner/binder/statement/bind_export.cpp b/src/duckdb/src/planner/binder/statement/bind_export.cpp index 81f22f66f..7869d95b1 100644 --- a/src/duckdb/src/planner/binder/statement/bind_export.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_export.cpp @@ -46,10 +46,11 @@ string SanitizeExportIdentifier(const Identifier &str) { return result; } -bool ReferencedTableIsOrdered(const Identifier &referenced_table, catalog_entry_vector_t &ordered) { +bool ReferencedTableIsOrdered(const TableCatalogEntry &referencing_table, const Identifier &referenced_table, + catalog_entry_vector_t &ordered) { for (auto &entry : ordered) { auto &table_entry = entry.get().Cast(); - if (table_entry.name == referenced_table) { + if (&table_entry.ParentSchema() == &referencing_table.ParentSchema() && table_entry.name == referenced_table) { // The referenced table is already ordered return true; } @@ -79,7 +80,7 @@ void ScanForeignKeyTable(catalog_entry_vector_t &ordered, catalog_entry_vector_t // This table references a table, don't move it yet move_to_ordered = false; break; - } else if (!ReferencedTableIsOrdered(fk.info.table, ordered)) { + } else if (!ReferencedTableIsOrdered(table_entry, fk.info.table, ordered)) { // The table that it references isn't ordered yet move_to_ordered = false; break; diff --git a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp index bb986b527..0bbee6fd6 100644 --- a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp @@ -15,7 +15,7 @@ namespace duckdb { Value Binder::BindExternalResourceValue(unique_ptr &expr) { TableFunctionBinder binder(*this, context, "External resource", "External resource parameter"); auto bound = binder.Bind(expr); - return ExpressionExecutor::EvaluateScalar(context, *bound); + return ExpressionExecutor::EvaluateScalar(context, *bound, true); } void Binder::BindExternalResourceParams(case_insensitive_map_t> &parsed_params, diff --git a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp index c053c9189..20b47314c 100644 --- a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp @@ -186,6 +186,39 @@ void RewriteMergeBindings(unique_ptr &expr, const vector FindMergeJoin(LogicalOperator &op, LogicalOperator &source, + LogicalOperator &target, idx_t &source_idx) { + if (op.children.size() == 2) { + for (idx_t child_idx = 0; child_idx < 2; child_idx++) { + if (ContainsOperator(*op.children[child_idx], source) && + ContainsOperator(*op.children[1 - child_idx], target)) { + source_idx = child_idx; + return op; + } + } + } + for (auto &child : op.children) { + auto result = FindMergeJoin(*child, source, target, source_idx); + if (result) { + return result; + } + } + return nullptr; +} + void RewriteMergeBindings(LogicalOperator &op, const vector &source_bindings, TableIndex new_table_index) { LogicalOperatorVisitor::EnumerateExpressions( @@ -317,6 +350,7 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { } else { join.type = JoinType::INNER; } + auto &source_op = *source_binding.plan; join.left = make_uniq(std::move(source_binding), std::move(source_binder)); join.right = make_uniq(std::move(bound_table), std::move(target_binder)); if (node.join_condition) { @@ -327,16 +361,14 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { auto bound_join_node = Bind(join); auto root = std::move(bound_join_node.plan); - auto join_ref = reference(*root); - while (join_ref.get().children.size() == 1) { - join_ref = *join_ref.get().children[0]; - } - if (join_ref.get().children.size() != 2) { - throw NotImplementedException("Expected a join after binding a join operator - but got a %s", - join_ref.get().type); + idx_t source_idx = 0; + auto merge_join = FindMergeJoin(*root, source_op, get, source_idx); + if (!merge_join) { + throw NotImplementedException("Expected a join after binding a join operator - but got a %s", root->type); } + auto join_ref = reference(*merge_join); // CreatePlan can turn a RIGHT join into a LEFT join - in that case the children are reversed - bool inverted = join.type == JoinType::RIGHT && join_ref.get().Cast().join_type == JoinType::LEFT; + bool inverted = source_idx == 1; auto &source = join_ref.get().children[inverted ? 1 : 0]; // bind WHEN_MATCHED merge actions (can contain references to both source and target) diff --git a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp index 5fef48a72..46ecedf6b 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp @@ -62,8 +62,8 @@ BoundStatement Binder::BindWithReplacementScan(ClientContext &context, BaseTable // Then the database-wide ones, including the built-in file scans if (!replacement_function) { - for (const auto &scan : DBConfig::GetConfig(context).replacement_scans) { - if (auto result = scan.function(context, input, scan.data.get())) { + for (const auto &scan : DBConfig::GetConfig(context).replacement_scans.Snapshot()) { + if (auto result = scan->function(context, input, scan->data.get())) { replacement_function = std::move(result); break; } @@ -304,6 +304,8 @@ BoundStatement Binder::Bind(BaseTableRef &ref) { case CatalogType::VIEW_ENTRY: { // the node is a view: get the query that the view represents auto &view_catalog_entry = table_or_view->Cast(); + // expanding nested views can take a long time, allow the query to be interrupted + context.InterruptCheck(); // We need to use a new binder for the view that doesn't reference any CTEs // defined for this binder so there are no collisions between the CTEs defined // for the view and for the current query diff --git a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp index 031e644aa..453117558 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp @@ -271,11 +271,35 @@ static unique_ptr PivotInitialAggregate(ClientContext &context, Pivo return subquery_stage1; } +//! The key of a pivot value: the length of the value, followed by the value - or "N" for NULL values. +//! Prefixing the length makes the concatenated keys of multiple pivot values unambiguous. +static string PivotValueKey(const Value &value) { + if (value.IsNull()) { + return "N"; + } + auto str = value.ToString(); + return to_string(str.size()) + ":" + str; +} + unique_ptr ConstructPivotExpression(unique_ptr pivot_expr) { + // coalesce(strlen(pivot::VARCHAR)::VARCHAR || ':' || pivot::VARCHAR, 'N'), see PivotValueKey auto cast = make_uniq(LogicalType::VARCHAR, std::move(pivot_expr)); + vector> strlen_children; + strlen_children.push_back(cast->Copy()); + auto length = make_uniq(LogicalType::VARCHAR, + make_uniq("strlen", std::move(strlen_children))); + vector> prefix_children; + prefix_children.push_back(std::move(length)); + prefix_children.push_back(ConstantExpression::String(":")); + auto prefix = make_uniq("||", std::move(prefix_children)); + vector> key_children; + key_children.push_back(std::move(prefix)); + key_children.push_back(std::move(cast)); + auto key = make_uniq("||", std::move(key_children)); + vector> coalesce_children; - coalesce_children.push_back(std::move(cast)); - coalesce_children.push_back(ConstantExpression::String("NULL")); + coalesce_children.push_back(std::move(key)); + coalesce_children.push_back(ConstantExpression::String("N")); auto coalesce = make_uniq(ExpressionType::OPERATOR_COALESCE, std::move(coalesce_children)); return std::move(coalesce); } @@ -447,8 +471,9 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { } if (unique_names.size() != aggregates.size()) { - throw InternalException("Pivot aggregate mismatch: %llu unique names, %llu aggregates", unique_names.size(), - aggregates.size()); + // identical aggregates with different names are bound to a single aggregate + throw BinderException("PIVOT: identical aggregates must have the same name - use a single aggregate, or " + "make the aggregates differ"); } identifier_map_t name_to_position; @@ -489,13 +514,11 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { auto name = pivot_value.name + aggr_name; string pivot_str; - for (auto &value : pivot_value.values) { - auto str = value.ToString(); - if (pivot_str.empty()) { - pivot_str = std::move(str); - } else { - pivot_str += "_" + str; + for (idx_t value_idx = 0; value_idx < pivot_value.values.size(); value_idx++) { + if (value_idx > 0) { + pivot_str += "_"; } + pivot_str += PivotValueKey(pivot_value.values[value_idx]); } result.bound_pivot.pivot_values.push_back(std::move(pivot_str)); names.push_back(Identifier(std::move(name))); diff --git a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp index 697da9c54..de12d298e 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp @@ -275,6 +275,9 @@ static void WarnShowDescribesQuery(ClientContext &context) { BoundStatement Binder::BindShow(ShowRef &ref) { BoundStatement result; if (ref.GetTableName().empty()) { + if (!ref.query) { + throw BinderException("SHOW requires a setting name, a table name or a query"); + } // "SHOW (SELECT ...)": describe the columns of the query. Deprecated in favor of DESCRIBE, and not affected // by show_behavior (a parenthesized query is neither a setting nor a named table). WarnShowDescribesQuery(context); diff --git a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp index 30f39a605..0332bf655 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp @@ -36,6 +36,15 @@ static TableFunctionBindType GetTableFunctionBindType(TableFunctionCatalogEntry } } if (all_scalar) { + for (idx_t function_idx = 0; function_idx < table_function.functions.Size(); function_idx++) { + const auto &function = *table_function.functions.GetFunctionByOffset(function_idx); + for (auto ¶m : function.GetSignature().GetParameters()) { + if (param.GetType().id() == LogicalTypeId::TABLE) { + throw BinderException("Table function %s requires a subquery as its TABLE parameter", + table_function.name); + } + } + } return TableFunctionBindType::STANDARD_TABLE_FUNCTION; } // if we have non-scalar parameters - we need to look at the function definition to decide how to bind diff --git a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp index f98bd5d44..07437da74 100644 --- a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp @@ -1,4 +1,5 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" + #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/catalog/catalog_entry/aggregate_function_catalog_entry.hpp" @@ -91,6 +92,11 @@ bool BoundAggregateExpression::Equals(const BaseExpression &other_p) const { if (!Expression::Equals(*children[i], *other.children[i])) { return false; } + // expression equality ignores the return type, but a collation lives in the return type and decides + // which argument values compare equal - two aggregates that disagree on it are not the same aggregate + if (!children[i]->GetReturnType().EqualsIncludingCollation(other.children[i]->GetReturnType())) { + return false; + } } if (state_export_mode != other.state_export_mode) { return false; diff --git a/src/duckdb/src/planner/expression/bound_cast_expression.cpp b/src/duckdb/src/planner/expression/bound_cast_expression.cpp index 896f4c36b..26d925cb2 100644 --- a/src/duckdb/src/planner/expression/bound_cast_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_cast_expression.cpp @@ -109,6 +109,48 @@ unique_ptr BoundCastExpression::AddArrayCastToList(ClientContext &co return BoundCastExpression::AddCastToType(context, std::move(expr), LogicalType::LIST(child_type)); } +//! Whether a cast between nested types is invertible: the structure has to stay the same, and all child casts have to +//! be invertible +static bool NestedCastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { + if (source_type.id() != target_type.id()) { + return false; + } + switch (source_type.id()) { + case LogicalTypeId::LIST: + return BoundCastExpression::CastIsInvertible(ListType::GetChildType(source_type), + ListType::GetChildType(target_type)); + case LogicalTypeId::ARRAY: + if (ArrayType::IsAnySize(source_type) || ArrayType::IsAnySize(target_type) || + ArrayType::GetSize(source_type) != ArrayType::GetSize(target_type)) { + return false; + } + return BoundCastExpression::CastIsInvertible(ArrayType::GetChildType(source_type), + ArrayType::GetChildType(target_type)); + case LogicalTypeId::MAP: + return BoundCastExpression::CastIsInvertible(MapType::KeyType(source_type), MapType::KeyType(target_type)) && + BoundCastExpression::CastIsInvertible(MapType::ValueType(source_type), MapType::ValueType(target_type)); + case LogicalTypeId::STRUCT: + case LogicalTypeId::TUPLE: { + // adding or removing fields is not invertible + auto &source_children = StructType::GetChildTypes(source_type); + auto &target_children = StructType::GetChildTypes(target_type); + if (source_children.size() != target_children.size()) { + return false; + } + for (idx_t child_idx = 0; child_idx < source_children.size(); child_idx++) { + if (source_children[child_idx].first != target_children[child_idx].first || + !BoundCastExpression::CastIsInvertible(source_children[child_idx].second, + target_children[child_idx].second)) { + return false; + } + } + return true; + } + default: + return false; + } +} + bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { D_ASSERT(source_type.IsValid() && target_type.IsValid()); if (source_type.id() == LogicalTypeId::BOOLEAN || target_type.id() == LogicalTypeId::BOOLEAN) { @@ -123,6 +165,9 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (source_type.id() == LogicalTypeId::VARIANT || target_type.id() == LogicalTypeId::VARIANT) { return false; } + if (source_type.IsNested() || target_type.IsNested()) { + return NestedCastIsInvertible(source_type, target_type); + } if (source_type.id() == LogicalTypeId::DECIMAL || target_type.id() == LogicalTypeId::DECIMAL) { uint8_t source_width, target_width; uint8_t source_scale, target_scale; @@ -137,7 +182,22 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (target_scale < source_scale) { return false; } - return true; + if (target_type.IsUnsigned()) { + // decimals can be negative + return false; + } + // the target needs to fit all digits before the decimal point + // integral types do not fit all numbers of their decimal width, e.g. SMALLINT (width 5) does not fit 99999 + auto source_digits = source_width - source_scale; + auto target_digits = target_width - target_scale; + if (source_type.id() == LogicalTypeId::HUGEINT || source_type.id() == LogicalTypeId::UHUGEINT) { + // (u)hugeint values can have up to 39 digits + source_digits++; + } + if (target_type.id() != LogicalTypeId::DECIMAL) { + target_digits--; + } + return source_digits <= target_digits; } switch (source_type.id()) { case LogicalTypeId::TIMESTAMP: @@ -169,6 +229,12 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const break; } break; + case LogicalTypeId::TIME_NS: + // a cast to microseconds rounds the nanoseconds + if (target_type.id() == LogicalTypeId::TIME) { + return false; + } + break; case LogicalTypeId::VARCHAR: case LogicalTypeId::BIT: case LogicalTypeId::TIME_TZ: diff --git a/src/duckdb/src/planner/expression_binder.cpp b/src/duckdb/src/planner/expression_binder.cpp index 1c227e951..7af091211 100644 --- a/src/duckdb/src/planner/expression_binder.cpp +++ b/src/duckdb/src/planner/expression_binder.cpp @@ -32,6 +32,10 @@ void ExpressionBinder::InitializeStackCheck() { } } +void ExpressionBinder::ContinueStackDepth(const ExpressionBinder &parent) { + stack_depth = parent.stack_depth; +} + StackChecker ExpressionBinder::StackCheck(const ParsedExpression &expr, idx_t extra_stack) { D_ASSERT(stack_depth != DConstants::INVALID_INDEX); auto max_expression_depth = Settings::Get(context); diff --git a/src/duckdb/src/planner/expression_binder/check_binder.cpp b/src/duckdb/src/planner/expression_binder/check_binder.cpp index be5ceace5..519488add 100644 --- a/src/duckdb/src/planner/expression_binder/check_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/check_binder.cpp @@ -35,8 +35,9 @@ BindResult ExpressionBinder::BindQualifiedColumnName(ColumnRefExpression &colref if (colref.ColumnNames()[0] == table_name) { struct_start++; } - auto result = make_uniq_base(colref.ColumnNames().back()); - for (idx_t i = struct_start; i + 1 < colref.ColumnNames().size(); i++) { + // the first name (after the table name) is the column, the remaining names are struct fields + auto result = make_uniq_base(colref.ColumnNames()[struct_start]); + for (idx_t i = struct_start + 1; i < colref.ColumnNames().size(); i++) { result = CreateStructExtract(std::move(result), colref.ColumnNames()[i]); } return BindExpression(result, 0); diff --git a/src/duckdb/src/planner/expression_binder/index_binder.cpp b/src/duckdb/src/planner/expression_binder/index_binder.cpp index f5c2341f0..19b38df7f 100644 --- a/src/duckdb/src/planner/expression_binder/index_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/index_binder.cpp @@ -1,9 +1,11 @@ #include "duckdb/planner/expression_binder/index_binder.hpp" #include "duckdb/catalog/catalog.hpp" +#include "duckdb/planner/binder.hpp" #include "duckdb/parser/parsed_data/create_index_info.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" +#include "duckdb/planner/binder.hpp" #include "duckdb/planner/column_binding.hpp" #include "duckdb/execution/index/bound_index.hpp" #include "duckdb/execution/index/unbound_index.hpp" @@ -80,19 +82,34 @@ unique_ptr IndexBinder::BindCreateIndex(ClientContext &context, // Add the dependencies. auto &dependencies = create_index_info->dependencies; auto &catalog = table_entry.ParentCatalog(); - SetCatalogLookupCallback([&dependencies, &catalog](CatalogEntry &entry) { + SetCatalogLookupCallback([&dependencies, &catalog, &table_entry](CatalogEntry &entry) { if (&catalog != &entry.ParentCatalog()) { return; } - // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table - dependencies.AddDependency(entry, DependencyDependentFlags()); + if (&entry == &table_entry) { + // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table + dependencies.AddDependency(entry, DependencyDependentFlags()); + return; + } + // other entries the index expressions use (e.g. macros) cannot be dropped, replaced or altered while the index + // exists (note that blocking alone is the legacy value that is read as non-blocking for indexes) + dependencies.AddDependency(entry, DependencyDependentFlags().SetBlocking().SetAlterBlocking()); }); // Bind the index expressions. + // The expressions are bound again when the index is loaded, so they cannot depend on the session or the query + // they are bound in (e.g. now(), getvariable()) - such functions are folded to constants while binding. + auto &properties = binder.GetStatementProperties(); + const auto statement_requires_rebind = properties.always_require_rebind; vector> expressions; for (auto &expr : create_index_info->expressions) { + properties.always_require_rebind = false; expressions.push_back(Bind(expr)); + if (properties.always_require_rebind) { + throw BinderException("Index keys cannot contain expressions with side effects."); + } } + properties.always_require_rebind = statement_requires_rebind; auto &get = plan->Cast(); InitCreateIndexInfo(get, *create_index_info); diff --git a/src/duckdb/src/planner/filter/dynamic_filter.cpp b/src/duckdb/src/planner/filter/dynamic_filter.cpp index a95f643a1..4e235b845 100644 --- a/src/duckdb/src/planner/filter/dynamic_filter.cpp +++ b/src/duckdb/src/planner/filter/dynamic_filter.cpp @@ -24,13 +24,13 @@ void DynamicFilterData::SetValue(Value val) { if (val.IsNull()) { return; } - lock_guard l(lock); + annotated_lock_guard l(lock); constant = std::move(val); initialized = true; } void DynamicFilterData::Reset() { - lock_guard l(lock); + annotated_lock_guard l(lock); initialized = false; } diff --git a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp index 222b68dbc..3a0a18b78 100644 --- a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp @@ -19,6 +19,9 @@ #include "duckdb/storage/statistics/numeric_stats.hpp" #include "duckdb/storage/buffer_manager.hpp" +#include +#include + namespace duckdb { static constexpr idx_t MAX_NUM_SECTORS = (1ULL << 26); @@ -27,11 +30,23 @@ static constexpr idx_t MIN_NUM_BITS = 512; static constexpr idx_t LOG_SECTOR_SIZE = 6; // a sector is 64 bits, log2(64) = 6 static constexpr idx_t SHIFT_MASK = 0x3F3F3F3F3F3F3F3F; // 6 bits for 64 positions static constexpr idx_t N_BITS = 4; // the number of bits to set per hash +static constexpr idx_t BITS_PER_SECTOR = 1ULL << LOG_SECTOR_SIZE; + +//! A sector is one entry of the bit array, so the two have to agree on how wide it is +static_assert(BITS_PER_SECTOR == sizeof(uint64_t) * 8, "a bloom filter sector must be exactly one array entry"); + +//! A filter is sized from a cardinality estimate, so never let it claim more than this share of the memory budget +static constexpr idx_t MAX_MEMORY_FRACTION = 64; void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { BufferManager &buffer_manager = BufferManager::GetBufferManager(context_p); - num_sectors = GetNumberOfSectors(number_of_rows); + // a capped filter stays complete - fewer sectors only raise the false positive rate, and Seal drops a + // filter that ended up too dense to reject anything + const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; + const idx_t budget_bytes = buffer_manager.GetMaxMemory() / MAX_MEMORY_FRACTION; + const idx_t budget_sectors = MaxValue(PreviousPowerOfTwo(budget_bytes / sizeof(uint64_t)), min_sectors); + num_sectors = MinValue(GetNumberOfSectors(number_of_rows), budget_sectors); bitmask = num_sectors - 1; buf_ = buffer_manager.GetBufferAllocator().Allocate(64 + num_sectors * sizeof(uint64_t)); @@ -40,24 +55,79 @@ void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { std::fill_n(bf, num_sectors, 0); initialized = true; + sealed = false; +} + +void BloomFilter::Fold(idx_t new_num_sectors) { + D_ASSERT(initialized); + D_ASSERT(!sealed); + D_ASSERT(IsPowerOfTwo(new_num_sectors)); + D_ASSERT(new_num_sectors <= num_sectors); + const uint64_t new_bitmask = new_num_sectors - 1; + for (idx_t i = new_num_sectors; i < num_sectors; i++) { + bf[i & new_bitmask] |= bf[i]; + } + num_sectors = new_num_sectors; + bitmask = new_bitmask; } -void BloomFilter::Merge(const BloomFilter &other) { +double BloomFilter::Density() const { D_ASSERT(initialized); - D_ASSERT(other.initialized); - D_ASSERT(num_sectors == other.num_sectors); - D_ASSERT(bitmask == other.bitmask); + idx_t bits_set = 0; for (idx_t i = 0; i < num_sectors; i++) { - bf[i] |= other.bf[i]; + bits_set += static_cast(std::bitset(bf[i]).count()); } + return static_cast(bits_set) / static_cast(num_sectors * BITS_PER_SECTOR); } -void BloomFilter::Reset() { - buf_.Reset(); - num_sectors = 0; - bitmask = 0; - initialized = false; - bf = nullptr; +//! False positive rate the sizing in GetNumberOfSectors aims for. With m bits, n keys and k probes per key a +//! given bit stays zero with probability (1 - 1/m)^(n*k) ~ e^(-k*n/m), so all k probed bits are set - a false +//! positive - with probability (1 - e^(-k*n/m))^k. Here m/n is MIN_NUM_BITS_PER_KEY and k is N_BITS. +static double TargetFalsePositiveRate() { + const double load = static_cast(N_BITS) / static_cast(MIN_NUM_BITS_PER_KEY); + return std::pow(1.0 - std::exp(-load), static_cast(N_BITS)); +} + +void BloomFilter::Seal(ClientContext &context) { + if (!initialized || sealed) { + sealed = true; + return; + } + const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; + double density = Density(); + // a density of exactly zero means no key was ever inserted, so there is nothing to fold down + if (density > 0.0) { + // each fold ORs two sectors together, so the expected density goes from p to 1 - (1 - p)^2 + const double target = TargetFalsePositiveRate(); + // target_sectors is only a candidate size - nothing is folded until the loop has settled on one + idx_t target_sectors = num_sectors; + while (target_sectors > min_sectors) { + const double folded_density = 1.0 - (1.0 - density) * (1.0 - density); + if (std::pow(folded_density, static_cast(N_BITS)) > target) { + break; + } + density = folded_density; + target_sectors >>= 1; + } + if (target_sectors < num_sectors) { + Fold(target_sectors); + // the filter was sized from an estimate, so hand the memory folding freed up back to the allocator + auto &allocator = BufferManager::GetBufferManager(context).GetBufferAllocator(); + auto compacted_buf = allocator.Allocate(64 + num_sectors * sizeof(uint64_t)); + auto compacted_bf = + reinterpret_cast((64ULL + reinterpret_cast(compacted_buf.get())) & ~63ULL); + std::copy_n(bf, num_sectors, compacted_bf); + buf_ = std::move(compacted_buf); + bf = compacted_bf; + } + } + sealed = true; + // a filter that accepts more than the scan-time policy tolerates gets paused on its first vectors anyway, + // so it is not worth the memory it holds or the hashing it costs + float selectivity_threshold; + idx_t n_vectors_to_check; + GetThresholdAndVectorsToCheck(SelectivityOptionalFilterType::BF, selectivity_threshold, n_vectors_to_check); + useful = std::pow(density, static_cast(N_BITS)) < static_cast(selectivity_threshold); } idx_t BloomFilter::GetNumberOfSectors(idx_t number_of_rows) { @@ -79,7 +149,25 @@ inline uint64_t GetMask(const hash_t hash) { return mask; } -void BloomFilter::InsertHashes(const Vector &hashes_v) const { +void BloomFilter::InsertHashes(const Vector &hashes_v, const SelectionVector &sel, const idx_t count) { + D_ASSERT(hashes_v.GetType() == LogicalType::HASH); + if (count == 0) { + return; + } + // VectorOperations::Hash produces a constant vector for a constant input, and a flat one otherwise + if (hashes_v.GetVectorType() == VectorType::CONSTANT_VECTOR) { + InsertOne(*ConstantVector::GetData(hashes_v)); + return; + } + D_ASSERT(hashes_v.GetVectorType() == VectorType::FLAT_VECTOR); + const auto hashes = FlatVector::GetData(hashes_v); + for (idx_t i = 0; i < count; i++) { + // sel can be the incremental selection vector, so this cannot use get_index_unsafe + InsertOne(hashes[sel.get_index(i)]); + } +} + +void BloomFilter::InsertHashes(const Vector &hashes_v) { for (auto hash : hashes_v.Values()) { InsertOne(hash.GetValue()); } @@ -112,17 +200,23 @@ idx_t BloomFilter::LookupHashes(const Vector &hashes_v, const SelectionVector &s return found_count; } -inline void BloomFilter::InsertOne(const hash_t hash) const { +inline void BloomFilter::InsertOne(const hash_t hash) { D_ASSERT(initialized); + D_ASSERT(!sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); - slot.fetch_or(mask, std::memory_order_relaxed); + // duplicate keys find their bits already set - checking first keeps the shared sector read-only then, + // instead of a contended read-modify-write + if ((slot.load(std::memory_order_relaxed) & mask) != mask) { + slot.fetch_or(mask, std::memory_order_relaxed); + } } bool BloomFilter::LookupOne(const uint64_t hash) const { D_ASSERT(initialized); + D_ASSERT(sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); @@ -131,10 +225,10 @@ bool BloomFilter::LookupOne(const uint64_t hash) const { return (bf_entry & mask) == mask; } -BloomFilterFunctionData::BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, +BloomFilterFunctionData::BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p) - : filter(filter_p), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), + : filter(std::move(filter_p)), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p), selectivity_threshold(selectivity_threshold_p), n_vectors_to_check(n_vectors_to_check_p) { } diff --git a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp index 13e56d61d..4b0a0cbf8 100644 --- a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp @@ -21,6 +21,7 @@ DynamicFilterData::DynamicFilterData(ExpressionType comparison_type_p, Value con } unique_ptr DynamicFilterData::ToExpression(const Expression &column) const { + annotated_shared_lock guard(lock); return BoundComparisonExpression::Create(comparison_type, column.Copy(), make_uniq(constant)); } @@ -120,7 +121,7 @@ static idx_t DynamicFilterSelect(DataChunk &args, ExpressionState &state, option ExpressionType comparison_type; Value constant; { - lock_guard lock(func_data.filter_data->lock); + annotated_shared_lock lock(func_data.filter_data->lock); comparison_type = func_data.filter_data->comparison_type; constant = func_data.filter_data->constant; } @@ -150,7 +151,7 @@ FilterPropagateResult DynamicFilterScalarFun::FilterPrune(const FunctionStatisti if (!data.filter_data) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } - lock_guard lock(data.filter_data->lock); + annotated_shared_lock lock(data.filter_data->lock); if (!data.filter_data->initialized) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } diff --git a/src/duckdb/src/planner/filter/table_filter_functions.cpp b/src/duckdb/src/planner/filter/table_filter_functions.cpp index a4237539e..5e84362cf 100644 --- a/src/duckdb/src/planner/filter/table_filter_functions.cpp +++ b/src/duckdb/src/planner/filter/table_filter_functions.cpp @@ -118,7 +118,8 @@ void TableFilterFunctionSerialize(Serializer &serializer, const optional_ptr TableFilterFunctionDeserialize(Deserializer &deserializer, BoundScalarFunction &function) { auto key_type = function.GetArguments().empty() ? LogicalType::ANY : function.GetArguments()[0]; if (function.GetName() == BloomFilterScalarFun::NAME) { - return make_uniq(nullptr, false, string(), key_type, 0.0f, idx_t(0)); + return make_uniq(shared_ptr(), false, string(), key_type, 0.0f, + idx_t(0)); } if (function.GetName() == PrefixRangeScalarFun::NAME) { return make_uniq(nullptr, true, string(), key_type, 0.0f, idx_t(0)); diff --git a/src/duckdb/src/planner/operator/logical_join.cpp b/src/duckdb/src/planner/operator/logical_join.cpp index c083f190f..ab5ec92f9 100644 --- a/src/duckdb/src/planner/operator/logical_join.cpp +++ b/src/duckdb/src/planner/operator/logical_join.cpp @@ -2,6 +2,7 @@ #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression_iterator.hpp" +#include "duckdb/planner/operator/logical_filter.hpp" #include "duckdb/main/config.hpp" namespace duckdb { @@ -83,6 +84,21 @@ void LogicalJoin::GetExpressionBindings(const Expression &root_expr, unordered_s }); } +unique_ptr LogicalJoin::PromoteJoinSide(unique_ptr child, + vector projection_map) { + if (projection_map.empty()) { + return child; + } + auto filter = make_uniq(); + filter->projection_map = std::move(projection_map); + filter->children.push_back(std::move(child)); + if (filter->children[0]->has_estimated_cardinality) { + filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); + } + filter->ResolveOperatorTypes(); + return std::move(filter); +} + void LogicalJoin::MoveJoinState(LogicalJoin &source, LogicalJoin &target) { D_ASSERT(source.join_type == target.join_type); target.mark_index = source.mark_index; diff --git a/src/duckdb/src/planner/planner.cpp b/src/duckdb/src/planner/planner.cpp index 1b5881abb..1ff2280ba 100644 --- a/src/duckdb/src/planner/planner.cpp +++ b/src/duckdb/src/planner/planner.cpp @@ -240,9 +240,12 @@ void Planner::CreatePlan(SQLStatement &statement) { auto max_tree_depth = Settings::Get(context); CheckTreeDepth(*plan, max_tree_depth); - RewriteTriggersToDependent(*this->binder, *this->plan); - RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); - this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); + // A LogicalPlanStatement carries an already planned (and possibly optimized) plan + if (statement.type != StatementType::LOGICAL_PLAN_STATEMENT) { + RewriteTriggersToDependent(*this->binder, *this->plan); + RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); + this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); + } D_ASSERT(!ContainsDependentJoin(*this->plan)); D_ASSERT(VerifyPlannedExpressions(*this->plan)); D_ASSERT(VerifyCanonicalComparisonJoins(*this->plan)); diff --git a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp index a66dba15e..339cfba43 100644 --- a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp +++ b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp @@ -118,11 +118,7 @@ BoundExpressionSQLExportState::PreserveCollation(const LogicalType &type, BoundE result.GetValue() = make_uniq(std::move(collation), std::move(result.GetValue())); } } else if (BoundExpressionSQLExportState::HasNestedCollation(type)) { - auto issue = BoundExpressionSQLExportState::UnsupportedFeature( - path, "nested_result_collation", "Nested result collations require a typed SQL representation"); - issue.facts.emplace_back("logical_type", Value(type.ToString())); - issue.facts.emplace_back("varchar_collations", Value(SQLExportHelpers::TypeCollationSignature(type))); - return BoundExpressionSQLExportResult::Failure({std::move(issue)}); + return CastToConstructedType(type, std::move(result.GetValue()), path); } return result; } @@ -439,13 +435,25 @@ BoundExpressionSQLExportState::ExportComparison(const BoundFunctionExpression &e D_ASSERT(!expression.BindInfo()); D_ASSERT(ChildrenAreConsistentWithArguments(expression.GetChildren(), expression.Function().GetArguments())); D_ASSERT(expression.GetReturnType() == expression.Function().GetReturnType()); - D_ASSERT(expression.GetChildren()[0]->GetReturnType() == expression.GetChildren()[1]->GetReturnType()); + auto &left_type = expression.GetChildren()[0]->GetReturnType(); + auto &right_type = expression.GetChildren()[1]->GetReturnType(); + // the regex range filter compares VARCHAR values with BLOB bounds, as the bounds need not be valid UTF-8 + const bool varchar_blob = (left_type.id() == LogicalTypeId::VARCHAR && right_type.id() == LogicalTypeId::BLOB) || + (left_type.id() == LogicalTypeId::BLOB && right_type.id() == LogicalTypeId::VARCHAR); + D_ASSERT(left_type == right_type || varchar_blob); vector> children; vector issues; ExportChildren(expression.GetChildren(), path, children, issues); if (!issues.empty()) { return BoundExpressionSQLExportResult::Failure(std::move(issues)); } + if (varchar_blob) { + // encode reinterprets the VARCHAR bytes as a BLOB, so the bytes are compared as before + auto &varchar_child = left_type.id() == LogicalTypeId::VARCHAR ? children[0] : children[1]; + vector> arguments; + arguments.push_back(std::move(varchar_child)); + varchar_child = SQLExportHelpers::SystemFunction("encode", std::move(arguments)); + } return BoundExpressionSQLExportResult::Success(make_uniq( expression.GetExpressionType(), std::move(children[0]), std::move(children[1]))); } diff --git a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp index f3e359ac7..21298cdce 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp @@ -331,6 +331,9 @@ bool LogicalPlanSQLExportHelpers::ComposeOrder(LogicalOperator &op, LogicalPlanS AddSubstitutions(state, child, *source); auto expressions = CollectExpressions(op); auto &modifier = select.modifiers[0]->Cast(); + if (modifier.orders.size() != expressions.size()) { + return false; + } vector> orders; for (idx_t i = 0; i < expressions.size(); i++) { auto &expression = *SemanticInput(expressions[i].get()); diff --git a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp index afcd594b6..b17415c21 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp @@ -332,9 +332,13 @@ BoundExpressionSQLExportState::BuildAggregateCall(const BoundAggregateExpression auto order_bys = make_uniq(); if (expression.GetOrderBys()) { for (auto &order : expression.GetOrderBys()->orders) { - order_bys->orders.emplace_back(order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), - std::move(children[child_index++]))); + auto &child = children[child_index++]; + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } + order_bys->orders.emplace_back( + order.type, order.null_order, + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } } result->FilterMutable() = std::move(filter); diff --git a/src/duckdb/src/planner/sql_export/sql_export_joins.cpp b/src/duckdb/src/planner/sql_export/sql_export_joins.cpp index a8c70b1c0..f840ef38d 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_joins.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_joins.cpp @@ -29,22 +29,6 @@ static string MarkConditionUnsupportedReason(const LogicalComparisonJoin &join) if (!comparisons_only || !has_supported_conjunction) { return "The MARK condition requires conjunction execution semantics"; } - for (auto &condition : join.conditions) { - switch (condition.GetComparisonType()) { - case ExpressionType::COMPARE_DISTINCT_FROM: - return "MARK DISTINCT FROM comparisons cannot preserve NULL semantics"; - case ExpressionType::COMPARE_LESSTHAN: - case ExpressionType::COMPARE_GREATERTHAN: - case ExpressionType::COMPARE_LESSTHANOREQUALTO: - case ExpressionType::COMPARE_GREATERTHANOREQUALTO: - if (condition.GetLHS().GetReturnType().IsNested() || condition.GetRHS().GetReturnType().IsNested()) { - return "MARK ordering comparisons on nested types cannot preserve NULL semantics"; - } - break; - default: - break; - } - } return string(); } diff --git a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp index 2806aa549..f0a40204a 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp @@ -233,6 +233,9 @@ ExportOrderModifier(LogicalOperator &op, LogicalPlanSQLExportContext &context, c if (expression.HasError()) { return Result::Failure(expression); } + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *expression.GetValue())) { + continue; + } modifier->orders.emplace_back( order.type, order.null_order, SQLExportHelpers::OrderExpression(SemanticExpressionType(*order.expression, expression_context), @@ -262,6 +265,10 @@ static LogicalPlanSQLExportResult ExportOrderedRelation(LogicalOperator &op, Log if (modifier.HasError()) { return LogicalPlanSQLExportResult::Failure(modifier); } + if (modifier.GetValue()->orders.empty()) { + LogicalPlanSQLExportHelpers::SetChildScope(*select, std::move(child.GetValue()), plain); + return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); + } select->modifiers.push_back(std::move(modifier.GetValue())); if (!plain && LogicalPlanSQLExportHelpers::ComposeOrder(op, child.GetValue(), *select, expression_context, path)) { return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); @@ -469,7 +476,9 @@ LogicalPlanSQLExportResult LogicalDistinct::ToSQL(LogicalPlanSQLExportContext &c if (order.HasError()) { return LogicalPlanSQLExportResult::Failure(order); } - select->modifiers.push_back(std::move(order.GetValue())); + if (!order.GetValue()->orders.empty()) { + select->modifiers.push_back(std::move(order.GetValue())); + } } select->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(child.GetValue())); return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); diff --git a/src/duckdb/src/planner/sql_export/sql_export_window.cpp b/src/duckdb/src/planner/sql_export/sql_export_window.cpp index 9f329bb12..64dab0866 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_window.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_window.cpp @@ -82,6 +82,19 @@ static optional_ptr RangeOffset(const BoundWindowExpression &e return &offset; } +static bool IsOrderDependentBoundary(WindowBoundary boundary) { + switch (boundary) { + case WindowBoundary::EXPR_PRECEDING_RANGE: + case WindowBoundary::EXPR_FOLLOWING_RANGE: + case WindowBoundary::CURRENT_ROW_GROUPS: + case WindowBoundary::EXPR_PRECEDING_GROUPS: + case WindowBoundary::EXPR_FOLLOWING_GROUPS: + return true; + default: + return false; + } +} + static WindowSQLFrame ReconstructWindowFrame(const BoundWindowExpression &expression) { WindowSQLFrame frame; if (expression.OrderBy().size() == 1) { @@ -178,10 +191,17 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression for (idx_t i = 0; i < expression.Partitions().size(); i++) { window->PartitionsMutable().push_back(std::move(children[ordinal++])); } + // RANGE offsets and GROUPS frames are defined in terms of the ORDER BY + const bool frame_needs_order = + IsOrderDependentBoundary(expression.WindowStart()) || IsOrderDependentBoundary(expression.WindowEnd()); for (auto &order : expression.OrderBy()) { + auto &child = children[ordinal++]; + if (!frame_needs_order && SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } window->OrderByMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } auto &named_arguments = function.GetNamedArguments(); auto positional_count = function.GetPositionalArgumentCount(); @@ -204,9 +224,13 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression window->EndExprMutable() = std::move(children[ordinal++]); } for (auto &order : expression.ArgOrders()) { + auto &child = children[ordinal++]; + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } window->ArgOrdersMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } window->IgnoreNullsMutable() = expression.IgnoreNulls(); window->HasIgnoreNullsMutable() = expression.IgnoreNulls(); diff --git a/src/duckdb/src/planner/statement_preprocessor.cpp b/src/duckdb/src/planner/statement_preprocessor.cpp index a7c210d39..219cb2ae0 100644 --- a/src/duckdb/src/planner/statement_preprocessor.cpp +++ b/src/duckdb/src/planner/statement_preprocessor.cpp @@ -13,96 +13,12 @@ #include "duckdb/common/string_util.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/function/function_binder.hpp" -#include "duckdb/parser/statement/transaction_statement.hpp" -#include "duckdb/parser/expression/constant_expression.hpp" -#include "duckdb/parser/statement/set_statement.hpp" -#include "duckdb/common/enums/current_transaction_state.hpp" namespace duckdb { -enum class PreprocessingTransactionHandling : uint8_t { - // Not in a transaction and should wrap in an implicit BEGIN/COMMIT - WRAP_IN_TRANSACTION, - // Already in an active transaction — set invalidation policy for multi-statement bodies - SET_INVALIDATION_POLICY, - // No transaction handling needed (single statement, or no transaction context applies) - NONE -}; - -void AddStatements(vector> &body_statements, - const PreprocessingTransactionHandling transaction_handling, - vector> &result_statements) { - if (transaction_handling == PreprocessingTransactionHandling::WRAP_IN_TRANSACTION) { - auto begin_info = make_uniq( - TransactionType::BEGIN_TRANSACTION, TransactionInvalidationPolicy::ALL_ERRORS_INVALIDATE_TRANSACTION, true); - auto begin_stmt = make_uniq(std::move(begin_info)); - begin_stmt->query = begin_stmt->ToString(); - result_statements.push_back(std::move(begin_stmt)); - } else if (transaction_handling == PreprocessingTransactionHandling::SET_INVALIDATION_POLICY) { - // Here we do a `SET current_transaction_invalidation_policy='ALL_ERRORS_INVALIDATE_TRANSACTION';`, for the - // current transaction, to make sure multistatements/pragmas are fully transactional, and invalidate even with - // minor errors such as binder, parser, etc. - auto set_stmt = make_uniq("current_transaction_invalidation_policy", - ConstantExpression::String("ALL_ERRORS_INVALIDATE_TRANSACTION"), - SetScope::GLOBAL); - set_stmt->query = set_stmt->ToString(); - result_statements.push_back(std::move(set_stmt)); - } - - // insert body_statements into result_statements - result_statements.insert(result_statements.end(), std::make_move_iterator(body_statements.begin()), - std::make_move_iterator(body_statements.end())); - - if (transaction_handling == PreprocessingTransactionHandling::WRAP_IN_TRANSACTION) { - auto commit_info = make_uniq( - TransactionType::COMMIT, TransactionInvalidationPolicy::ALL_ERRORS_INVALIDATE_TRANSACTION, true); - auto commit_stmt = make_uniq(std::move(commit_info)); - commit_stmt->query = commit_stmt->ToString(); - result_statements.push_back(std::move(commit_stmt)); - } else if (transaction_handling == PreprocessingTransactionHandling::SET_INVALIDATION_POLICY) { - auto set_stmt = make_uniq( - "current_transaction_invalidation_policy", ConstantExpression::String("STANDARD_POLICY"), SetScope::GLOBAL); - set_stmt->query = set_stmt->ToString(); - result_statements.push_back(std::move(set_stmt)); - } -} - StatementPreprocessor::StatementPreprocessor(ClientContext &context) : context(context) { } -PreprocessingTransactionHandling GetTransactionHandling(vector> &body_statements, - CurrentTransactionState full_transaction_state, - bool skip_handling = false) { - if (body_statements.size() <= 1 || skip_handling) { - return PreprocessingTransactionHandling::NONE; - } - if (full_transaction_state == NOT_IN_ACTIVE_TRANSACTION) { - return PreprocessingTransactionHandling::WRAP_IN_TRANSACTION; - } - if (full_transaction_state == IN_ACTIVE_TRANSACTION) { - return PreprocessingTransactionHandling::SET_INVALIDATION_POLICY; - } - return PreprocessingTransactionHandling::NONE; -} - -void UnpackMultiStatement(MultiStatement &multi_statement, const CurrentTransactionState current_transaction_state, - vector> &new_statements) { -#ifdef DEBUG // MultiStatement should not contain transaction statements - for (auto &sub_statement : multi_statement.statements) { - D_ASSERT(sub_statement->type != StatementType::TRANSACTION_STATEMENT); - } -#endif - bool has_select = false; - for (auto &stmt : multi_statement.statements) { - if (stmt->type == StatementType::SELECT_STATEMENT) { - // Pivot statements have select, and we don't want to wrap those in transactions. - has_select = true; - } - } - auto handling = GetTransactionHandling(multi_statement.statements, current_transaction_state, has_select); - AddStatements(multi_statement.statements, handling, new_statements); -} - vector> StatementPreprocessor::TryReparsePragma(unique_ptr statement) const { // Try reparsing const auto info = statement->Cast().info->Copy(); @@ -122,78 +38,43 @@ vector> StatementPreprocessor::TryReparsePragma(unique_ return res; } -void StatementPreprocessor::Preprocess(ClientContextLock &lock, vector> &statements, - CurrentTransactionState transaction_context_state) { - // Quick check: do we need preprocessing at all? - bool needs_preprocessing = false; - // Only PRAGMA and multi-statements touch the catalog and need a transaction. - bool needs_transaction = false; - for (auto &stmt : statements) { - if (stmt->type == StatementType::PRAGMA_STATEMENT || stmt->type == StatementType::MULTI_STATEMENT) { - needs_preprocessing = true; - needs_transaction = true; - } else if (stmt->type == StatementType::TRANSACTION_STATEMENT) { - needs_preprocessing = true; - } +PreprocessedStatement StatementPreprocessor::ExpandStatement(unique_ptr statement) const { + PreprocessedStatement result; + switch (statement->type) { + case StatementType::MULTI_STATEMENT: { + auto &multi_statement = statement->Cast(); + result.statements = std::move(multi_statement.statements); + break; } - if (!needs_preprocessing) { - return; + case StatementType::PRAGMA_STATEMENT: + result.statements = TryReparsePragma(std::move(statement)); + break; + default: + result.statements.push_back(std::move(statement)); + return result; } - if (!needs_transaction) { - // Transaction statements only update chained_transaction_state. Wrapping them in a transaction would start a - // throwaway autocommit transaction for a BEGIN (consuming a transaction id), and would make a ROLLBACK of an - // aborted transaction fail. - PreprocessInternal(lock, statements, transaction_context_state); - return; - } - - context.RunFunctionInTransactionInternal(lock, - [&] { PreprocessInternal(lock, statements, transaction_context_state); }); -} - -void StatementPreprocessor::PreprocessInternal(ClientContextLock &lock, vector> &statements, - const CurrentTransactionState transaction_context_state) { - vector> new_statements; - for (idx_t i = 0; i < statements.size(); i++) { - auto query = statements[i]->query; - const CurrentTransactionState full_transaction_state = - (transaction_context_state == IN_ACTIVE_TRANSACTION || chained_transaction_state == IN_ACTIVE_TRANSACTION) - ? IN_ACTIVE_TRANSACTION - : NOT_IN_ACTIVE_TRANSACTION; - - switch (statements[i]->type) { - case StatementType::PRAGMA_STATEMENT: { - auto reparsed_statements = TryReparsePragma(std::move(statements[i])); - const auto handling = GetTransactionHandling(reparsed_statements, full_transaction_state); - AddStatements(reparsed_statements, handling, new_statements); - break; - } - case StatementType::MULTI_STATEMENT: { - auto &multi_statement = statements[i]->Cast(); - UnpackMultiStatement(multi_statement, full_transaction_state, new_statements); - break; - } - case StatementType::TRANSACTION_STATEMENT: { - auto &transaction_stmt = statements[i]->Cast(); - - if (transaction_stmt.info->type == TransactionType::BEGIN_TRANSACTION) { - new_statements.push_back(std::move(statements[i])); - chained_transaction_state = IN_ACTIVE_TRANSACTION; - break; + if (result.statements.size() > 1) { + for (auto &expanded : result.statements) { + // The statements run as one query in one transaction, which a transaction statement would end or take over + if (expanded->type == StatementType::TRANSACTION_STATEMENT) { + throw InvalidInputException( + "A statement that expands into several cannot contain a transaction statement: %s", + expanded->query); } - if (transaction_stmt.info->type == TransactionType::COMMIT || - transaction_stmt.info->type == TransactionType::ROLLBACK) { - chained_transaction_state = NOT_IN_ACTIVE_TRANSACTION; - } - new_statements.push_back(std::move(statements[i])); - break; - } - default: { - new_statements.push_back(std::move(statements[i])); - } } } + return result; +} - statements = std::move(new_statements); +PreprocessedStatement StatementPreprocessor::PreprocessStatement(ClientContextLock &lock, + unique_ptr statement) { + if (statement->type != StatementType::PRAGMA_STATEMENT && statement->type != StatementType::MULTI_STATEMENT) { + return ExpandStatement(std::move(statement)); + } + // Only PRAGMA and multi-statements touch the catalog and need a transaction + PreprocessedStatement result; + context.RunFunctionInTransactionInternal(lock, [&] { result = ExpandStatement(std::move(statement)); }); + return result; } + } // namespace duckdb diff --git a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp index 5e198a4b1..8c9dd8106 100644 --- a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp +++ b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp @@ -2631,9 +2631,8 @@ static bool CanRestrictCorrelationDomain(LogicalOperator &op, unordered_set(binder.context) && - config.options.disabled_optimizers.find(OptimizerType::DELIMINATOR) == config.options.disabled_optimizers.end(); + cte_deliminator_enabled = Settings::Get(binder.context) && + !config.IsOptimizerDisabled(OptimizerType::DELIMINATOR); } BindingReplacementGraph DelimJoinCTERewriter::MaterializeDelimJoinAsCTE(unique_ptr &plan, diff --git a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp index 457740d4c..69038c5d7 100644 --- a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp +++ b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp @@ -1107,12 +1107,22 @@ FlattenDependentJoins::UnnestingState FlattenDependentJoins::PushDownJoin(unique } if (join_type == JoinType::MARK) { - if (!left_requires_domain && right_requires_domain) { + if (right_requires_domain) { auto right_state = PushDownChild(plan, propagate_null_values, state, false, 1); auto left_state = PushDownChild(plan, propagate_null_values, right_state.bindings, false, 0); - AddCorrelatedJoinConditions(plan->Cast(), left_state.bindings, right_state.bindings); + auto &comparison_join = plan->Cast(); + auto conditions = std::move(comparison_join.conditions); + comparison_join.conditions.clear(); + AddCorrelatedJoinConditions(comparison_join, left_state.bindings, right_state.bindings); + // Keep the correlation keys before the IN predicate for per-group NULL and empty-set handling. + comparison_join.mark_types.insert(comparison_join.mark_types.begin(), delim_types.begin(), + delim_types.end()); + for (auto &condition : conditions) { + comparison_join.conditions.push_back(std::move(condition)); + } + RewriteCorrelatedBindings(*plan, left_state.bindings); left_state.replacement_graph.Merge(right_state.replacement_graph); return left_state; } diff --git a/src/duckdb/src/planner/table_filter_state.cpp b/src/duckdb/src/planner/table_filter_state.cpp index 932ea641b..33bfb58bb 100644 --- a/src/duckdb/src/planner/table_filter_state.cpp +++ b/src/duckdb/src/planner/table_filter_state.cpp @@ -348,7 +348,7 @@ class BloomFilterExecutor final : public ExpressionFilterExecutor { } } - optional_ptr filter; + shared_ptr filter; bool filters_null_values; Vector hashes; SelectionVector bloom_sel; diff --git a/src/duckdb/src/storage/block_allocator.cpp b/src/duckdb/src/storage/block_allocator.cpp index d7088a345..60b19d2d7 100644 --- a/src/duckdb/src/storage/block_allocator.cpp +++ b/src/duckdb/src/storage/block_allocator.cpp @@ -329,7 +329,7 @@ data_ptr_t BlockAllocator::AllocateData(const idx_t size) const { } void BlockAllocator::FreeData(const data_ptr_t pointer, const idx_t size) const { - deallocated_since_flush.fetch_add(size, std::memory_order_relaxed); + estimated_unflushed_memory.fetch_add(size, std::memory_order_relaxed); if (!IsActive() || !IsInPool(pointer)) { return allocator.FreeData(pointer, size); } @@ -368,7 +368,7 @@ void BlockAllocator::ThreadFlush(bool allocator_background_threads, idx_t thresh } void BlockAllocator::FlushAll(const optional_idx extra_memory) const { - deallocated_since_flush.store(0, std::memory_order_relaxed); + estimated_unflushed_memory.store(0, std::memory_order_relaxed); if (IsActive() && IsEnabled() && extra_memory.IsValid()) { FreeInternal(extra_memory.GetIndex()); } @@ -377,22 +377,35 @@ void BlockAllocator::FlushAll(const optional_idx extra_memory) const { } } -idx_t BlockAllocator::GetDeallocatedSinceFlush() const { - return deallocated_since_flush.load(std::memory_order_relaxed); +idx_t BlockAllocator::GetEstimatedUnflushedMemory() const { + return estimated_unflushed_memory.load(std::memory_order_relaxed); } -bool BlockAllocator::TryFlushDeallocated(const idx_t threshold) const { +bool BlockAllocator::TryFlushDeallocated(const idx_t threshold, const idx_t memory_limit, + const idx_t used_memory) const { if (!SupportsFlush()) { return false; } - auto deallocated = deallocated_since_flush.load(std::memory_order_relaxed); + auto deallocated = estimated_unflushed_memory.load(std::memory_order_relaxed); if (deallocated < threshold) { return false; } - // the thread that claims the accumulated amount performs the flush - if (!deallocated_since_flush.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { + // the thread that claims the accumulated amount checks whether a flush is needed + if (!estimated_unflushed_memory.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { return false; } + if (!IsActive()) { + const auto mapped_memory = allocator.GetMappedMemory(); + if (mapped_memory.IsValid()) { + // Freed pages cannot occupy more than the allocator's entire mapped footprint. + const auto unflushed = MinValue(deallocated, mapped_memory.GetIndex()); + if (used_memory + unflushed <= memory_limit) { + // Keep the upper bound in subsequent pressure checks, adding any concurrent frees. + estimated_unflushed_memory.fetch_add(unflushed, std::memory_order_relaxed); + return false; + } + } + } FlushAll(deallocated); return true; } diff --git a/src/duckdb/src/storage/buffer/buffer_pool.cpp b/src/duckdb/src/storage/buffer/buffer_pool.cpp index dc5e68a38..6106abb0a 100644 --- a/src/duckdb/src/storage/buffer/buffer_pool.cpp +++ b/src/duckdb/src/storage/buffer/buffer_pool.cpp @@ -586,7 +586,7 @@ void BufferPool::FlushAllocator(const optional_idx extra_memory) { void BufferPool::FlushOnBulkDeallocation() { const auto threshold = GetBulkDeallocationFlushThreshold(); - const auto deallocated = block_allocator.GetDeallocatedSinceFlush(); + const auto deallocated = block_allocator.GetEstimatedUnflushedMemory(); if (deallocated < threshold) { return; } @@ -595,7 +595,7 @@ void BufferPool::FlushOnBulkDeallocation() { if (used_memory + deallocated <= maximum_memory.load(std::memory_order_relaxed)) { return; } - block_allocator.TryFlushDeallocated(threshold); + block_allocator.TryFlushDeallocated(threshold, maximum_memory.load(std::memory_order_relaxed), used_memory); } vector BufferPool::GetEvictionQueueInfo() const { diff --git a/src/duckdb/src/storage/checkpoint_manager.cpp b/src/duckdb/src/storage/checkpoint_manager.cpp index 05574c6ce..08f6f4ede 100644 --- a/src/duckdb/src/storage/checkpoint_manager.cpp +++ b/src/duckdb/src/storage/checkpoint_manager.cpp @@ -613,7 +613,12 @@ void CheckpointWriter::WriteSequence(SequenceCatalogEntry &seq, Serializer &seri void CheckpointReader::ReadSequence(CatalogTransaction transaction, Deserializer &deserializer) { auto info = ReadCreateInfo(deserializer, CatalogType::SEQUENCE_ENTRY, "sequence"); auto &sequence_info = info->Cast(); - catalog.CreateSequence(transaction, sequence_info); + auto entry = catalog.CreateSequence(transaction, sequence_info); + auto &storage_manager = catalog.GetAttached().GetStorageManager(); + if (entry && storage_manager.HasStorageVersion() && + StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { + entry->Cast().DeriveLastValue(); + } } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst.cpp b/src/duckdb/src/storage/compression/dict_fsst.cpp index 0d9cd73f3..71d024ea1 100644 --- a/src/duckdb/src/storage/compression/dict_fsst.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst.cpp @@ -160,17 +160,15 @@ void DictFSSTCompressionStorage::StringFetchRow(ColumnSegment &segment, ColumnFe // Select //===--------------------------------------------------------------------===// void DictFSSTSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { auto &scan_state = state.scan_state->Cast(); - if (scan_state.mode == DictFSSTMode::FSST_ONLY) { - // for FSST only - auto start = state.GetPositionInSegment(); - scan_state.Select(result, start, sel, sel_count); + if (scan_state.mode != DictFSSTMode::FSST_ONLY && scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + // scan + slice, so that we can emit a dictionary vector + DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); + result.Slice(sel, sel_count); return; } - // fallback: scan + slice - DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); - result.Slice(sel, sel_count); + scan_state.Select(result, result_offset, state.GetPositionInSegment(), sel, sel_count); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp index a6e555a82..d07d8bbcb 100644 --- a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp @@ -157,19 +157,25 @@ const SelectionVector &CompressedStringScanState::GetSelVec(idx_t start, idx_t s } void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_offset, idx_t start, idx_t scan_count) { - // Create a decompression buffer of sufficient size if we don't already have one. + Select(result, result_offset, start, *FlatVector::IncrementalSelectionVector(), scan_count); + result.Verify(); +} + +void CompressedStringScanState::Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, + idx_t sel_count) { + const auto scan_count = sel_count == 0 ? 0 : sel.get_index(sel_count - 1) + 1; auto &selvec = GetSelVec(start, scan_count); //! (index 0 is reserved for NULL, which we don't have in this mode) const idx_t start_offset = mode == DictFSSTMode::FSST_ONLY ? start + 1 : 0; - auto result_data = FlatVector::Writer(result, scan_count, result_offset); + auto result_data = FlatVector::Writer(result, sel_count, result_offset); if (dictionary) { // We have prepared the full dictionary, we can reference these strings directly auto dictionary_values = FlatVector::GetData(dictionary->data); - for (idx_t i = 0; i < scan_count; i++) { + for (idx_t i = 0; i < sel_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(i + start_offset); + auto string_number = selvec.get_index(start_offset + sel.get_index(i)); if (string_number == 0) { result_data.WriteNull(); continue; @@ -177,9 +183,9 @@ void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_of result_data.WriteStringRef(dictionary_values[string_number]); } } else { - for (idx_t i = 0; i < scan_count; i++) { + for (idx_t i = 0; i < sel_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(start_offset + i); + auto string_number = selvec.get_index(start_offset + sel.get_index(i)); if (string_number == 0) { result_data.WriteNull(); continue; @@ -193,25 +199,6 @@ void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_of result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); } } - result.Verify(); -} - -void CompressedStringScanState::Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count) { - D_ASSERT(!dictionary); - D_ASSERT(mode == DictFSSTMode::FSST_ONLY); - idx_t start_offset = start + 1; - auto result_data = FlatVector::Writer(result, sel_count); - for (idx_t i = 0; i < sel_count; i++) { - // Lookup dict offset in index buffer - auto string_number = start_offset + sel.get_index(i); - if (decompress_position > string_number) { - throw InternalException("DICT_FSST: not performing a sequential scan?"); - } - for (; decompress_position < string_number; decompress_position++) { - decompress_offset += string_lengths[decompress_position]; - } - result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); - } } bool CompressedStringScanState::AllowDictionaryScan(idx_t scan_count) { diff --git a/src/duckdb/src/storage/compression/fsst.cpp b/src/duckdb/src/storage/compression/fsst.cpp index b30090418..c6c724e5a 100644 --- a/src/duckdb/src/storage/compression/fsst.cpp +++ b/src/duckdb/src/storage/compression/fsst.cpp @@ -55,7 +55,7 @@ struct FSSTStorage { static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); static void SetDictionary(ColumnSegment &segment, BufferHandle &handle, StringDictionaryContainer container); static StringDictionaryContainer GetDictionary(ColumnSegment &segment, BufferHandle &handle); @@ -768,7 +768,7 @@ void FSSTStorage::StringScan(ColumnSegment &segment, ColumnScanState &state, idx // Select //===--------------------------------------------------------------------===// void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { auto &scan_state = state.scan_state->Cast(); auto start = state.GetPositionInSegment(); D_ASSERT(start <= segment.count.load()); @@ -783,7 +783,7 @@ void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t v for (idx_t i = 0; i < sel_count; i++) { idx_t index = sel.get_index(i); D_ASSERT(index < vector_count); - result_data[i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); + result_data[result_offset + i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); } } diff --git a/src/duckdb/src/storage/compression/numeric_constant.cpp b/src/duckdb/src/storage/compression/numeric_constant.cpp index 94d751c24..fa9da5fb2 100644 --- a/src/duckdb/src/storage/compression/numeric_constant.cpp +++ b/src/duckdb/src/storage/compression/numeric_constant.cpp @@ -251,14 +251,23 @@ void ConstantFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row // Select //===--------------------------------------------------------------------===// void ConstantSelectValidity(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { - ConstantScanFunctionValidity(segment, state, sel_count, result); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType scan_type) { + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + ConstantScanFunctionValidity(segment, state, sel_count, result); + return; + } + ConstantScanPartialValidity(segment, state, sel_count, result, result_offset); } template void ConstantSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { - ConstantScanFunction(segment, state, vector_count, result); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + ConstantScanFunction(segment, state, vector_count, result); + return; + } + ConstantScanPartial(segment, state, sel_count, result, result_offset); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/rle.cpp b/src/duckdb/src/storage/compression/rle.cpp index e5e0df6e2..bbdbbc0b0 100644 --- a/src/duckdb/src/storage/compression/rle.cpp +++ b/src/duckdb/src/storage/compression/rle.cpp @@ -519,19 +519,21 @@ void RLEScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, V //===--------------------------------------------------------------------===// template void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { auto &scan_state = state.scan_state->Cast>(); // If we are scanning an entire Vector and it contains only a single run we don't need to select at all auto current_run = scan_state.ValidateAndGetCurrentRun(); - if (scan_state.position_in_entry < current_run.length && + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR && scan_state.position_in_entry < current_run.length && CanEmitConstantVector(scan_state.position_in_entry, current_run.length, vector_count)) { RLEScanConstant(scan_state, current_run, vector_count, result); return; } - auto result_data = FlatVector::Writer(result, sel_count); + auto result_data = FlatVector::Writer(result, sel_count, result_offset); + // Keep the current run until the selection advances past it. + idx_t run_end = current_run.length - scan_state.position_in_entry; idx_t prev_idx = 0; for (idx_t i = 0; i < sel_count; i++) { auto next_idx = sel.get_index(i); @@ -539,14 +541,16 @@ void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_coun throw InternalException("Error in RLESelect - selection vector indices are not ordered"); } D_ASSERT(next_idx < vector_count); - // skip forward to the next index - scan_state.SkipInternal(next_idx - prev_idx); - // read the element - result_data.WriteValue(scan_state.ValidateAndGetCurrentRun().value); - // move the next to the prev + while (next_idx >= run_end) { + scan_state.ForwardToNextRun(); + current_run = scan_state.ValidateAndGetCurrentRun(); + run_end += current_run.length; + } + result_data.WriteValue(current_run.value); prev_idx = next_idx; } - // skip the tail + // Restore the position at the last selected row before skipping the tail. + scan_state.position_in_entry = current_run.length - (run_end - prev_idx); scan_state.SkipInternal(vector_count - prev_idx); } diff --git a/src/duckdb/src/storage/compression/string_uncompressed.cpp b/src/duckdb/src/storage/compression/string_uncompressed.cpp index 7348b0934..9abdb7f15 100644 --- a/src/duckdb/src/storage/compression/string_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/string_uncompressed.cpp @@ -158,7 +158,8 @@ void UncompressedStringStorage::StringScan(ColumnSegment &segment, ColumnScanSta // Select //===--------------------------------------------------------------------===// void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, - Vector &result, const SelectionVector &sel, idx_t sel_count) { + Vector &result, const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType) { // clear any previously locked buffers and get the primary buffer handle auto &scan_state = state.scan_state->Cast(); auto layout = StringSegmentLayout::Read(scan_state.handle, segment); @@ -174,7 +175,7 @@ void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState & auto selection_index = sel.get_index(i); D_ASSERT(selection_index < vector_count); auto entry = strings.GetEntry(selection_index); - result_data[i] = FetchStringFromEntry(state.context, segment, result, entry); + result_data[result_offset + i] = FetchStringFromEntry(state.context, segment, result, entry); } } diff --git a/src/duckdb/src/storage/compression/validity_uncompressed.cpp b/src/duckdb/src/storage/compression/validity_uncompressed.cpp index a9778897b..6ab8fbe5b 100644 --- a/src/duckdb/src/storage/compression/validity_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/validity_uncompressed.cpp @@ -520,7 +520,7 @@ void ValidityScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_cou // Select //===--------------------------------------------------------------------===// void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { auto start = state.GetPositionInSegment(); if (sel_count == 0) { return; @@ -528,18 +528,21 @@ void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector D_ASSERT(start <= segment.count && vector_count <= segment.count - start); D_ASSERT(sel_count <= vector_count); - result.Flatten(); + if (result_offset == 0) { + result.Flatten(); + } auto &scan_state = state.scan_state->Cast(); + auto input_data = ValidityScanData(scan_state.reader, start, vector_count); + auto input_start = start % ValidityMask::BITS_PER_VALUE; auto &result_mask = FlatVector::ValidityMutable(result); for (idx_t i = 0; i < sel_count; i++) { auto selected_index = sel.get_index(i); D_ASSERT(selected_index < vector_count); - auto source_idx = start + selected_index; - auto entry_offset = source_idx / ValidityMask::BITS_PER_VALUE * sizeof(validity_t); - auto entry = scan_state.reader.Get(entry_offset); + auto source_idx = input_start + selected_index; + auto entry = input_data[source_idx / ValidityMask::BITS_PER_VALUE]; if (!ValidityMask::RowIsValid(entry, source_idx % ValidityMask::BITS_PER_VALUE)) { - result_mask.SetInvalid(i); + result_mask.SetInvalid(result_offset + i); } } } @@ -638,12 +641,14 @@ void ValidityRevertAppend(ColumnSegment &segment, idx_t new_count) { //===--------------------------------------------------------------------===// CompressionFunction ValidityUncompressed::GetFunction(PhysicalType data_type) { D_ASSERT(data_type == PhysicalType::BIT); - return CompressionFunction(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, + CompressionFunction result(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, ValidityAnalyze, ValidityFinalAnalyze, UncompressedFunctions::InitCompression, UncompressedFunctions::Compress, UncompressedFunctions::FinalizeCompress, ValidityInitScan, ValidityScan, ValidityScanPartial, ValidityFetchRow, UncompressedFunctions::EmptySkip, ValidityInitSegment, ValidityInitAppend, ValidityAppend, ValidityFinalizeAppend, ValidityRevertAppend); + result.select = ValiditySelect; + return result; } } // namespace duckdb diff --git a/src/duckdb/src/storage/data_table.cpp b/src/duckdb/src/storage/data_table.cpp index a7884c327..2f9cec660 100644 --- a/src/duckdb/src/storage/data_table.cpp +++ b/src/duckdb/src/storage/data_table.cpp @@ -902,6 +902,8 @@ void DataTable::InitializeLocalStorage(LocalAppendState &state, DuckTableEntry & GetTableName(), TableModification()); } + // The transaction-local indexes are created with the local storage, and only for indexes that are bound + info->BindIndexes(context); auto &local_storage = LocalStorage::Get(context, db); local_storage.InitializeStorage(state, *this, table); state.constraint_state = InitializeConstraintState(table, bound_constraints); diff --git a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp index 5d9dbd3cb..3c9b26937 100644 --- a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp +++ b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp @@ -616,6 +616,12 @@ bool CachingFileHandle::TryGetNetworkThroughput(NetworkThroughputEstimate &resul void CachingFileHandle::ReadAndRecord(QueryContext context, data_ptr_t buffer, idx_t nr_bytes, idx_t location) { auto handle = GetFileHandle(); + if (handle->file_system.IsLocalFileSystem()) { + // Pre-fault local read buffers before the kernel fills them. + for (idx_t offset = 0; offset < nr_bytes; offset += 4096) { + buffer[offset] = 0; + } + } const auto read_start = steady_clock::now(); handle->Read(context, buffer, nr_bytes, location); auto cache_valid_until = handle->file_system.GetCacheValidUntil(*handle); diff --git a/src/duckdb/src/storage/statistics/numeric_stats.cpp b/src/duckdb/src/storage/statistics/numeric_stats.cpp index ab56e0446..14640c6a1 100644 --- a/src/duckdb/src/storage/statistics/numeric_stats.cpp +++ b/src/duckdb/src/storage/statistics/numeric_stats.cpp @@ -31,6 +31,25 @@ BaseStatistics NumericStats::CreateUnknown(LogicalType type) { return result; } +//! The type whose limits are used for the min/max of empty statistics +static LogicalType GetEmptyStatsLimitType(const LogicalType &type) { + if (type.id() != LogicalTypeId::ENUM) { + return type; + } + // the dictionary of an ENUM can be empty or have a single entry: use the limits of the index type instead, so + // that the min is larger than the max until a value is added + switch (type.InternalType()) { + case PhysicalType::UINT8: + return LogicalType::UTINYINT; + case PhysicalType::UINT16: + return LogicalType::USMALLINT; + case PhysicalType::UINT32: + return LogicalType::UINTEGER; + default: + throw InternalException("Invalid Internal Type for ENUMs"); + } +} + BaseStatistics NumericStats::CreateEmpty(LogicalType type) { BaseStatistics result(std::move(type)); result.InitializeEmpty(); @@ -41,11 +60,13 @@ BaseStatistics NumericStats::CreateEmpty(LogicalType type) { case PhysicalType::DOUBLE: InitializeEmptyFloatingPointStats(result); break; - default: - SetMin(result, Value::MaximumValue(result.GetType())); - SetMax(result, Value::MinimumValue(result.GetType())); + default: { + auto limit_type = GetEmptyStatsLimitType(result.GetType()); + SetMin(result, Value::MaximumValue(limit_type)); + SetMax(result, Value::MinimumValue(limit_type)); break; } + } return result; } diff --git a/src/duckdb/src/storage/statistics/variant_stats.cpp b/src/duckdb/src/storage/statistics/variant_stats.cpp index ddc90feaf..5440fb2d3 100644 --- a/src/duckdb/src/storage/statistics/variant_stats.cpp +++ b/src/duckdb/src/storage/statistics/variant_stats.cpp @@ -696,8 +696,8 @@ bool VariantStats::MergeShredding(const BaseStatistics &stats, const BaseStatist return true; } else { D_ASSERT(!stats_typed_value_type.IsNested()); - if (stats_typed_value_type.id() != other_typed_value_type.id()) { - //! other is not the same type, can't merge + if (stats_typed_value_type != other_typed_value_type) { + //! other is not the same type (e.g. a DECIMAL of a different width or scale), can't merge return false; } auto new_typed_stats = stats_typed_value.Copy(); diff --git a/src/duckdb/src/storage/table/column_data.cpp b/src/duckdb/src/storage/table/column_data.cpp index 66f3d82e0..c820ee4f8 100644 --- a/src/duckdb/src/storage/table/column_data.cpp +++ b/src/duckdb/src/storage/table/column_data.cpp @@ -262,24 +262,71 @@ idx_t ColumnData::ScanVector(ColumnScanState &state, Vector &result, idx_t remai } void ColumnData::SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count) { + idx_t sel_count, ScanVectorType scan_type) { BeginScanVectorInternal(state); - auto ¤t = state.current->GetNode(); - if (state.current->GetRowStart() + current.count - state.offset_in_column < target_count) { - throw InternalException("ColumnData::SelectVector should be able to fetch everything from one segment"); - } - if (state.scan_options && state.scan_options->force_fetch_row) { - auto start = state.GetPositionInSegment(); - for (idx_t i = 0; i < sel_count; i++) { - auto source_idx = sel.get_index(i); - ColumnFetchState fetch_state; - current.FetchRow(fetch_state, UnsafeNumericCast(start + source_idx), result, i); + // scratch space to rebase the selection vector to the start of a segment scan + // this owns its data, as compression functions can retain the selection vector (e.g. by slicing the result) + SelectionVector rebased_sel; + idx_t sel_offset = 0; + idx_t scanned_count = 0; + // the position up to which the scan state of the current segment has been advanced + // segments without any selected rows are not scanned, so they must be skipped in the next scan + idx_t segment_internal_index = state.offset_in_column; + while (scanned_count < target_count) { + auto ¤t = state.current->GetNode(); + auto current_start = state.current->GetRowStart(); + idx_t scan_count = + MinValue(target_count - scanned_count, current_start + current.count - state.offset_in_column); + segment_internal_index = state.offset_in_column; + // gather the selected rows that fall within this segment + idx_t segment_sel_count = 0; + while (sel_offset + segment_sel_count < sel_count && + sel.get_index(sel_offset + segment_sel_count) < scanned_count + scan_count) { + segment_sel_count++; + } + if (segment_sel_count > 0) { + // the indices must be relative to the start of the segment scan - for the first segment this is + // already the case, so we can forward the selection vector of the caller directly + reference segment_sel(sel); + if (scanned_count > 0) { + if (!rebased_sel.IsSet()) { + rebased_sel.Initialize(); + } + for (idx_t i = 0; i < segment_sel_count; i++) { + rebased_sel.set_index(i, sel.get_index(sel_offset + i) - scanned_count); + } + segment_sel = rebased_sel; + } + if (state.scan_options && state.scan_options->force_fetch_row) { + for (idx_t i = 0; i < segment_sel_count; i++) { + ColumnFetchState fetch_state; + auto source_idx = state.offset_in_column + segment_sel.get().get_index(i) - current_start; + current.FetchRow(fetch_state, UnsafeNumericCast(source_idx), result, sel_offset + i); + } + } else { + current.Select(state, scan_count, result, segment_sel.get(), segment_sel_count, sel_offset, scan_type); + } + segment_internal_index = state.offset_in_column + scan_count; + } + sel_offset += segment_sel_count; + scanned_count += scan_count; + state.offset_in_column += scan_count; + if (scanned_count < target_count) { + auto next = data.GetNextSegment(*state.current); + if (!next) { + break; + } + state.previous_states.emplace_back(std::move(state.scan_state)); + state.current = next; + state.current->GetNode().InitializeScan(state); + state.segment_checked = false; } - } else { - current.Select(state, target_count, result, sel, sel_count); } - state.offset_in_column += target_count; - state.internal_index = state.offset_in_column; + if (scan_type == ScanVectorType::SCAN_FLAT_VECTOR) { + // not every compression function sets the size of the result + FlatVector::SetSize(result, count_t(sel_count)); + } + state.internal_index = segment_internal_index; } void ColumnData::FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/column_segment.cpp b/src/duckdb/src/storage/table/column_segment.cpp index 36f1f2f1b..641f4deef 100644 --- a/src/duckdb/src/storage/table/column_segment.cpp +++ b/src/duckdb/src/storage/table/column_segment.cpp @@ -151,11 +151,16 @@ void ColumnSegment::Scan(ColumnScanState &state, idx_t scan_count, Vector &resul } void ColumnSegment::Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, - idx_t sel_count) { + idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { if (!function.get().select) { throw InternalException("ColumnSegment::Select not implemented for this compression method"); } - function.get().select(*this, state, scan_count, result, sel, sel_count); + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + D_ASSERT(result_offset == 0); + } else { + D_ASSERT(result.GetVectorType() == VectorType::FLAT_VECTOR); + } + function.get().select(*this, state, scan_count, result, sel, sel_count, result_offset, scan_type); } void ColumnSegment::Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/standard_column_data.cpp b/src/duckdb/src/storage/table/standard_column_data.cpp index 734db61b9..ac2fa3ed2 100644 --- a/src/duckdb/src/storage/table/standard_column_data.cpp +++ b/src/duckdb/src/storage/table/standard_column_data.cpp @@ -113,14 +113,13 @@ void StandardColumnData::Select(TransactionData transaction, idx_t vector_index, bool validity_has_select = validity_compression && validity_compression->select; auto target_count = GetVectorCount(vector_index); auto scan_type = GetVectorScanType(state, target_count, result); - bool scan_entire_vector = scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR; - if (!has_select || !validity_has_select || !scan_entire_vector) { - // we are not scanning an entire vector - this can have several causes (updates, etc) + if (!has_select || !validity_has_select || HasUpdates() || validity->HasUpdates()) { + // the updates need to be merged into the entire vector, so we cannot select ColumnData::Select(transaction, vector_index, state, result, sel, sel_count); return; } - SelectVector(state, result, target_count, sel, sel_count); - validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count); + SelectVector(state, result, target_count, sel, sel_count, scan_type); + validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count, scan_type); } void StandardColumnData::InitializeAppend(ColumnAppendState &state) { diff --git a/src/duckdb/src/storage/wal_replay.cpp b/src/duckdb/src/storage/wal_replay.cpp index 54611faa2..fa1b476ca 100644 --- a/src/duckdb/src/storage/wal_replay.cpp +++ b/src/duckdb/src/storage/wal_replay.cpp @@ -1176,6 +1176,11 @@ void WriteAheadLogDeserializer::ReplaySequenceValue() { // fetch the sequence from the catalog auto &seq = catalog.GetEntry(context, ReplayEntryName(catalog, entry.qualified_name)); seq.ReplayValue(entry.usage_count, entry.counter, entry.last_value); + auto &storage_manager = db.GetStorageManager(); + if (storage_manager.HasStorageVersion() && + StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { + seq.DeriveLastValue(); + } } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/transaction/meta_transaction.cpp b/src/duckdb/src/transaction/meta_transaction.cpp index b3cf2b9e2..d74a02b47 100644 --- a/src/duckdb/src/transaction/meta_transaction.cpp +++ b/src/duckdb/src/transaction/meta_transaction.cpp @@ -245,9 +245,10 @@ AttachedDatabase &MetaTransaction::UseDatabase(shared_ptr &dat if (entry == referenced_databases.end()) { auto used_entry = used_databases.emplace(db_ref.GetName(), db_ref); if (!used_entry.second) { - // return used_entry.first->second.get(); - throw InternalException( - "Database name %s was already used by a different database for this meta transaction", + // e.g. a database was detached and another one attached under the same name in this transaction + throw TransactionException( + "Database name %s was already used by a different database in this transaction - commit or roll back " + "the transaction before using the name for another database", db_ref.GetName()); } referenced_databases.emplace(reference(db_ref), database); diff --git a/src/duckdb/third_party/httplib/httplib.hpp b/src/duckdb/third_party/httplib/httplib.hpp index ae0a35db6..5675d7611 100644 --- a/src/duckdb/third_party/httplib/httplib.hpp +++ b/src/duckdb/third_party/httplib/httplib.hpp @@ -194,14 +194,6 @@ #define CPPHTTPLIB_RECV_FLAGS 0 #endif -#ifndef CPPHTTPLIB_SEND_FLAGS -#ifdef MSG_NOSIGNAL -#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL -#else -#define CPPHTTPLIB_SEND_FLAGS 0 -#endif -#endif - #ifndef CPPHTTPLIB_LISTEN_BACKLOG #define CPPHTTPLIB_LISTEN_BACKLOG 128 #endif @@ -329,6 +321,14 @@ using socket_t = int; #endif #endif //_WIN32 +#ifndef CPPHTTPLIB_SEND_FLAGS +#ifdef MSG_NOSIGNAL +#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL +#else +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif +#endif + #if defined(__APPLE__) #include #endif diff --git a/src/duckdb/third_party/ska_sort/ska_sort.hpp b/src/duckdb/third_party/ska_sort/ska_sort.hpp index 3296a8c1b..5347ea7d7 100644 --- a/src/duckdb/third_party/ska_sort/ska_sort.hpp +++ b/src/duckdb/third_party/ska_sort/ska_sort.hpp @@ -1070,25 +1070,39 @@ struct TypeAt<0, First, More...> // { // }; -template -inline void StdSortFallback(It begin, It end, ExtractKey & extract_key) -{ - // LNK note that we use the full comparison (not just extracted key) here - static const auto comp = [&](const typename std::remove_reference::type & l, const typename std::remove_reference::type & r){ return l < r; }; - static const auto fallback = [&](const It &fb_begin, const It &fb_end) { - duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); - }; - duckdb_vergesort::vergesort(begin, end, comp, fallback); +// LNK note that we use the full comparison (not just extracted key) here +template +struct FullComparison +{ + typedef typename std::remove_reference())>::type value_type; + bool operator()(const value_type & l, const value_type & r) const + { + return l < r; + } +}; + +// Comparison sort of a radix bucket whose extracted keys are all equal: vergesort over the full comparison, with +// the sorted runs it finds merged by the caller's merge policy (see duckdb_vergesort::inplace_merge_policy), so +// that the merge scratch memory of the nested sort is the one the caller controls +template +inline void StdSortFallback(It begin, It end, ExtractKey &, const Merge & merge) +{ + FullComparison comp; + const auto fallback = [&](const It &fb_begin, const It &fb_end) { + duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); + }; + duckdb_vergesort::vergesort(begin, end, comp, fallback, merge); } template -inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey & extract_key) +inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey &) { if (num_elements <= 1) return true; if (num_elements >= StdSortThreshold) return false; - StdSortFallback(begin, end, extract_key); + // below vergesort's minimum size (128), where it would hand the whole range to pdqsort without merging anything + duckdb_pdqsort::pdqsort_branchless(begin, end, FullComparison()); return true; } @@ -1422,8 +1436,8 @@ struct SortStarter> template struct SortStarter { - template - static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, void * next_sort_data = nullptr) + template + static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, const Merge & merge) { if (StdSortIfLessThanThreshold(begin, end, num_elements, extract_key)) return; @@ -1433,22 +1447,26 @@ struct SortStarter // next_sort = nullptr; // LNK: different templating for DuckDB + // The sort data pointer that the radix passes carry along is the merge policy for the comparison sort of the + // buckets that the radix passes cannot order (the sub keys of DuckDB's sort keys never look at it) void (*next_sort)(It, It, size_t, ExtractKey &, void *) = nullptr; + void * next_sort_data = nullptr; if (extract_key.requires_next_sort) { - next_sort = [](It b, It e, size_t, ExtractKey& ek, void*) { - StdSortFallback(b, e, ek); + next_sort = [](It b, It e, size_t, ExtractKey& ek, void* merge_policy) { + StdSortFallback(b, e, ek, *static_cast(merge_policy)); }; + next_sort_data = const_cast(static_cast(&merge)); } InplaceSorter::sort(begin, end, num_elements, extract_key, next_sort, next_sort_data); } }; -template -void inplace_radix_sort(It begin, It end, ExtractKey & extract_key) +template +void inplace_radix_sort(It begin, It end, ExtractKey & extract_key, const Merge & merge) { typedef SubKey SubKeyType; - SortStarter::sort(begin, end, end - begin, extract_key); + SortStarter::sort(begin, end, end - begin, extract_key, merge); } struct IdentityFunctor @@ -1461,10 +1479,18 @@ struct IdentityFunctor }; } +// Sorts [begin, end) by the radix of extract_key; buckets whose extracted keys are equal (when extract_key requires +// a next sort) are sorted by the full comparison with vergesort, whose run merges go through the given merge policy +template +static void ska_sort(It begin, It end, ExtractKey && extract_key, const Merge & merge) +{ + detail::inplace_radix_sort<128, 1024>(begin, end, extract_key, merge); +} + template static void ska_sort(It begin, It end, ExtractKey && extract_key) { - detail::inplace_radix_sort<128, 1024>(begin, end, extract_key); + ska_sort(begin, end, extract_key, duckdb_vergesort::inplace_merge_policy()); } template diff --git a/src/duckdb/third_party/vergesort/vergesort.h b/src/duckdb/third_party/vergesort/vergesort.h index 1d7275318..e1322da65 100644 --- a/src/duckdb/third_party/vergesort/vergesort.h +++ b/src/duckdb/third_party/vergesort/vergesort.h @@ -47,6 +47,19 @@ namespace duckdb_vergesort { + // Merge policy used for the final pairwise run merges: a callable merge(first, middle, last, compare) that + // merges the sorted ranges [first, middle) and [middle, last). The default is std::inplace_merge, which takes + // its temporary buffer from operator new; callers that want to control that memory pass their own policy. + struct inplace_merge_policy + { + template + void operator()(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare) const + { + std::inplace_merge(first, middle, last, compare); + } + }; + namespace detail { // In-place merge where [first, middle1), [middle1, middle2) @@ -178,9 +191,9 @@ namespace duckdb_vergesort } } - template + template void vergesort(RandomAccessIterator first, RandomAccessIterator last, - Compare compare, std::random_access_iterator_tag, Fallback fallback) + Compare compare, std::random_access_iterator_tag, Fallback fallback, Merge merge) { typedef typename std::iterator_traits::difference_type difference_type; difference_type dist = last - first; @@ -321,7 +334,7 @@ namespace duckdb_vergesort for (typename std::list::iterator it = runs.begin() ; it != runs.end() && it != detail::prev(runs.end()) ; ++it) { - std::inplace_merge(begin, *it, *detail::next(it), compare); + merge(begin, *it, *detail::next(it), compare); // Remove the middle iterator and advance it = runs.erase(it); @@ -331,12 +344,19 @@ namespace duckdb_vergesort } } + template + void vergesort(BidirectionalIterator first, BidirectionalIterator last, + Compare compare, Fallback fallback, Merge merge) + { + typedef typename std::iterator_traits::iterator_category category; + detail::vergesort(first, last, compare, category(), fallback, merge); + } + template void vergesort(BidirectionalIterator first, BidirectionalIterator last, Compare compare, Fallback fallback) { - typedef typename std::iterator_traits::iterator_category category; - detail::vergesort(first, last, compare, category(), fallback); + vergesort(first, last, compare, fallback, inplace_merge_policy()); } template From e75a63be99b57cad333bab52f6f7fc59c3a57d08 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Thu, 15 Oct 2026 19:26:44 +0000 Subject: [PATCH 03/13] Update vendored DuckDB sources to f702a1f8010 --- .../aggregate/algebraic/avg.cpp | 5 +- .../aggregate/distributive/bitstring_agg.cpp | 11 +- .../aggregate/distributive/kurtosis.cpp | 95 +-- .../aggregate/distributive/skew.cpp | 78 +- .../aggregate/distributive/sum.cpp | 36 +- .../holistic/approximate_quantile.cpp | 61 +- .../aggregate/algebraic/covar.hpp | 7 +- .../aggregate/algebraic/stddev.hpp | 7 +- .../aggregate/regression/regr_count.hpp | 8 +- .../scalar/array/array_value.cpp | 2 - .../core_functions/scalar/date/date_part.cpp | 14 +- .../core_functions/scalar/date/date_trunc.cpp | 12 +- .../scalar/generic/current_setting.cpp | 2 - .../scalar/generic/type_functions.cpp | 6 +- .../core_functions/scalar/list/list_sort.cpp | 21 +- .../core_functions/scalar/map/switch.cpp | 2 - .../core_functions/scalar/math/numeric.cpp | 7 +- .../scalar/string/parse_path.cpp | 186 +++-- .../core_functions/scalar/string/replace.cpp | 13 +- .../icu/collation/collation_collator.cpp | 6 - .../icu/collation/collation_normalizer.cpp | 2 +- .../collation/include/collation_collator.hpp | 8 - src/duckdb/extension/icu/icu-datefunc.cpp | 11 - src/duckdb/extension/icu/icu-datepart.cpp | 10 +- src/duckdb/extension/icu/icu-datesub.cpp | 14 - src/duckdb/extension/icu/icu-datetrunc.cpp | 15 - src/duckdb/extension/icu/icu-makedate.cpp | 6 +- src/duckdb/extension/icu/icu-strptime.cpp | 41 +- src/duckdb/extension/icu/icu-timebucket.cpp | 33 +- src/duckdb/extension/icu/icu-timezone.cpp | 4 - .../extension/icu/include/icu-datefunc.hpp | 4 - .../json/json_functions/json_create.cpp | 19 +- .../json/json_functions/json_transform.cpp | 39 +- src/duckdb/extension/json/json_reader.cpp | 10 +- .../extension/parquet/column_reader.cpp | 58 +- .../delta_length_byte_array_decoder.cpp | 45 +- .../parquet/include/parquet_dbp_decoder.hpp | 5 +- .../parquet/include/parquet_reader.hpp | 3 - .../extension/parquet/parquet_extension.cpp | 3 - .../extension/parquet/parquet_metadata.cpp | 30 +- .../extension/parquet/parquet_reader.cpp | 113 +-- .../extension/parquet/parquet_shredding.cpp | 5 - .../parquet/read_single_parquet_file.cpp | 21 - .../reader/row_number_column_reader.cpp | 9 - ...nerated_extension_loader_package_build.cpp | 8 +- .../catalog_entry/duck_table_entry.cpp | 36 +- .../catalog_entry/sequence_catalog_entry.cpp | 17 - .../common/allocator/allocator_jemalloc.cpp | 17 - .../common/allocator/allocator_standard.cpp | 4 - src/duckdb/src/common/bind_helpers.cpp | 7 +- src/duckdb/src/common/enum_util.cpp | 40 +- src/duckdb/src/common/exception.cpp | 1 + src/duckdb/src/common/file_buffer.cpp | 6 - src/duckdb/src/common/gzip_file_system.cpp | 8 +- src/duckdb/src/common/hive_partitioning.cpp | 4 +- src/duckdb/src/common/local_file_system.cpp | 7 +- .../multi_file/multi_file_column_mapper.cpp | 54 +- .../src/common/multi_file/multi_file_list.cpp | 3 +- src/duckdb/src/common/opener_file_system.cpp | 3 +- .../src/common/operator/cast_operators.cpp | 37 +- src/duckdb/src/common/sort/sort.cpp | 6 +- src/duckdb/src/common/sort/sorted_run.cpp | 18 +- .../src/common/sort/sorted_run_merger.cpp | 7 +- .../tree_renderer/html_tree_renderer.cpp | 2 +- src/duckdb/src/common/types/bignum.cpp | 32 +- src/duckdb/src/common/types/geometry.cpp | 3 - src/duckdb/src/common/types/hugeint.cpp | 259 +++++- src/duckdb/src/common/types/interval.cpp | 4 +- .../types/row/tuple_data_collection.cpp | 9 +- src/duckdb/src/common/types/time.cpp | 29 +- .../variant/parquet_variant_iterator.cpp | 7 - src/duckdb/src/common/types/vector_buffer.cpp | 17 +- .../src/common/vector/constant_vector.cpp | 58 +- src/duckdb/src/common/vector/list_vector.cpp | 2 +- .../expression_executor/execute_function.cpp | 43 +- .../src/execution/index/fixed_size_buffer.cpp | 6 +- src/duckdb/src/execution/join_hashtable.cpp | 48 +- .../operator/aggregate/physical_window.cpp | 19 +- .../scanner/string_value_scanner.cpp | 36 +- .../table_function/read_single_csv_file.cpp | 3 - .../csv_scanner/util/csv_reader_options.cpp | 21 - .../csv_scanner/util/csv_validator.cpp | 2 +- .../operator/join/physical_asof_join.cpp | 16 +- .../operator/join/physical_hash_join.cpp | 155 +--- .../operator/order/physical_top_n.cpp | 19 +- .../persistent/physical_batch_insert.cpp | 4 +- .../persistent/physical_copy_to_file.cpp | 19 +- .../operator/persistent/physical_insert.cpp | 5 +- .../projection/physical_projection.cpp | 10 - .../execution/operator/set/physical_union.cpp | 13 +- .../physical_plan/plan_asof_join.cpp | 6 - .../physical_plan/plan_comparison_join.cpp | 4 +- .../physical_plan/plan_set_operation.cpp | 20 +- .../function/aggregate/distributive/count.cpp | 1 - .../aggregate/sorted_aggregate_function.cpp | 47 +- src/duckdb/src/function/cast/array_casts.cpp | 8 +- src/duckdb/src/function/cast/bignum_casts.cpp | 14 +- .../src/function/cast/cast_statistics.cpp | 4 - src/duckdb/src/function/cast/string_cast.cpp | 30 +- src/duckdb/src/function/cast/union_casts.cpp | 5 - .../function/cast/variant/from_variant.cpp | 19 +- src/duckdb/src/function/function_binder.cpp | 15 - .../src/function/pragma/pragma_functions.cpp | 34 +- .../function/scalar/generic/getvariable.cpp | 2 - .../src/function/scalar/map/map_contains.cpp | 41 - .../function/scalar/operator/arithmetic.cpp | 29 +- .../src/function/scalar/sequence/nextval.cpp | 11 - .../src/function/scalar/string/contains.cpp | 69 +- .../src/function/scalar/string/length.cpp | 23 +- .../src/function/scalar/string/regexp.cpp | 9 - .../scalar/system/aggregate_export.cpp | 20 +- .../src/function/table/arrow_conversion.cpp | 235 +++--- src/duckdb/src/function/table/checkpoint.cpp | 4 +- src/duckdb/src/function/table/range.cpp | 3 +- .../table/system/create_external_resource.cpp | 5 - .../table/system/enable_profiling.cpp | 17 - .../table/system/external_resources.cpp | 3 - .../function/table/system/logging_utils.cpp | 10 - .../function/table/version/pragma_version.cpp | 6 +- .../function/variant/variant_shredding.cpp | 6 +- .../function/window/window_value_function.cpp | 30 +- .../catalog_entry/sequence_catalog_entry.hpp | 2 - .../src/include/duckdb/common/allocator.hpp | 2 - .../src/include/duckdb/common/enum_util.hpp | 8 - .../common/exception/http_exception.hpp | 5 - .../duckdb/common/hive_partitioning.hpp | 4 +- .../src/include/duckdb/common/hugeint.hpp | 343 +------- .../src/include/duckdb/common/mutex.hpp | 70 -- .../operator/decimal_cast_operators.hpp | 15 +- .../common/operator/integer_cast_operator.hpp | 23 +- .../duckdb/common/sorting/buffered_merge.hpp | 192 ----- .../duckdb/common/sorting/sorted_run.hpp | 2 - .../duckdb/common/thread_annotation/mutex.hpp | 16 - .../common/types/arrow_string_view_type.hpp | 4 +- .../src/include/duckdb/common/types/time.hpp | 5 +- .../execution/expression_executor_state.hpp | 2 - .../duckdb/execution/join_hashtable.hpp | 25 +- .../duckdb/execution/merge_sort_tree.hpp | 4 +- .../persistent/physical_batch_insert.hpp | 4 +- .../operator/persistent/physical_insert.hpp | 4 +- .../projection/physical_projection.hpp | 1 - .../duckdb/execution/physical_operator.hpp | 5 - .../function/cast/variant/json_to_variant.hpp | 9 +- .../cast/variant/primitive_to_variant.hpp | 24 +- .../function/cast/variant/to_variant_fwd.hpp | 5 +- .../duckdb/function/compression_function.hpp | 6 +- .../src/include/duckdb/function/function.hpp | 6 +- .../duckdb/function/scalar/generic_common.hpp | 3 - .../src/include/duckdb/logging/log_type.hpp | 4 +- .../include/duckdb/main/client_context.hpp | 2 - src/duckdb/src/include/duckdb/main/config.hpp | 52 +- .../include/duckdb/main/extension_entries.hpp | 8 +- .../include/duckdb/main/extension_manager.hpp | 10 +- .../duckdb/main/http/http_retry_budget.hpp | 162 +--- .../include/duckdb/main/http/http_util.hpp | 23 +- .../duckdb/main/profiler/gathered_metrics.hpp | 38 +- .../duckdb/main/profiler/profiling_utils.hpp | 21 +- .../include/duckdb/main/query_profiler.hpp | 2 +- .../duckdb/main/secret/default_secrets.hpp | 3 - .../src/include/duckdb/main/secret/secret.hpp | 11 +- .../duckdb/main/statement_iterator.hpp | 3 - .../join_order/join_order_optimizer.hpp | 6 - .../relation_statistics.hpp | 15 - .../optimizer/remove_unused_columns.hpp | 2 - .../duckdb/parallel/scan_read_ahead.hpp | 24 - .../parser/peg/ast/expression_chain.hpp | 5 +- .../duckdb/parser/peg/compiled_grammar.hpp | 13 +- .../duckdb/parser/peg/inlined_grammar.hpp | 435 +--------- .../keyword_helper/default_keyword_maps.hpp | 2 - .../src/include/duckdb/parser/peg/matcher.hpp | 3 +- .../parser/peg/matcher/identifier_matcher.hpp | 4 - .../peg/matcher/number_literal_matcher.hpp | 8 - .../peg/transformer/peg_transformer.hpp | 187 ++--- .../duckdb/planner/expression_binder.hpp | 5 - .../duckdb/planner/filter/bloom_filter.hpp | 6 +- .../planner/filter/table_filter_functions.hpp | 43 +- .../duckdb/planner/operator/logical_join.hpp | 4 - .../duckdb/planner/sql_export_helpers.hpp | 7 - .../duckdb/storage/block_allocator.hpp | 12 +- .../compression/dict_fsst/decompression.hpp | 2 +- .../storage/compression/empty_validity.hpp | 2 +- .../duckdb/storage/string_uncompressed.hpp | 2 +- .../duckdb/storage/table/column_data.hpp | 2 +- .../duckdb/storage/table/column_segment.hpp | 3 +- src/duckdb/src/logging/log_types.cpp | 8 +- src/duckdb/src/main/capi/v1/arrow-c.cpp | 11 +- src/duckdb/src/main/client_context.cpp | 11 +- .../src/main/client_context_execution.cpp | 1 - src/duckdb/src/main/config.cpp | 23 +- .../src/main/extension/extension_helper.cpp | 3 - src/duckdb/src/main/extension_manager.cpp | 14 +- .../src/main/http/http_retry_budget.cpp | 333 +------- src/duckdb/src/main/http/http_util.cpp | 177 +--- src/duckdb/src/main/parser_context.cpp | 44 +- .../src/main/profiler/gathered_metrics.cpp | 7 +- .../src/main/profiler/profiling_utils.cpp | 4 +- src/duckdb/src/main/query_profiler.cpp | 6 +- .../src/main/secret/default_secrets.cpp | 8 +- src/duckdb/src/main/secret/secret.cpp | 35 - src/duckdb/src/main/secret/secret_manager.cpp | 17 +- .../src/main/settings/custom_settings.cpp | 13 +- src/duckdb/src/main/statement_iterator.cpp | 4 +- .../optimizer/common_subplan_optimizer.cpp | 86 +- src/duckdb/src/optimizer/filter_combiner.cpp | 56 +- src/duckdb/src/optimizer/filter_pushdown.cpp | 46 +- .../src/optimizer/grouping_sets_optimizer.cpp | 7 - .../join_order/join_order_optimizer.cpp | 16 +- .../optimizer/join_order/relation_manager.cpp | 12 +- .../multi_stage_aggregate_rewriter.cpp | 93 +-- src/duckdb/src/optimizer/optimizer.cpp | 6 +- .../pushdown/pushdown_cross_product.cpp | 54 -- .../pushdown/pushdown_inner_join.cpp | 16 +- .../pushdown/pushdown_projection.cpp | 24 +- .../pushdown/pushdown_semi_anti_join.cpp | 4 +- .../relation_statistics.cpp | 18 - .../relation_statistics_helper.cpp | 27 +- .../relation_statistics_operator.cpp | 21 +- .../src/optimizer/remove_duplicate_groups.cpp | 6 +- .../src/optimizer/remove_unused_columns.cpp | 30 +- .../rule/comparison_simplification.cpp | 6 - .../rule/date_trunc_simplification.cpp | 28 - .../src/optimizer/rule/monotone_preimage.cpp | 51 +- .../statistics/operator/propagate_join.cpp | 59 +- .../src/optimizer/topn_window_elimination.cpp | 22 +- src/duckdb/src/parallel/pipeline_executor.cpp | 4 +- src/duckdb/src/parallel/scan_read_ahead.cpp | 108 +-- .../src/parser/peg/autocomplete_core.cpp | 1 - .../src/parser/peg/compiled_grammar.cpp | 2 - .../keyword_helper/default_keyword_maps.cpp | 6 - .../parsed_grammar_keyword_helper.cpp | 30 +- src/duckdb/src/parser/peg/keyword_map.cpp | 361 -------- src/duckdb/src/parser/peg/matcher_factory.cpp | 1 - .../parser/peg/tokenizer/base_tokenizer.cpp | 5 +- .../transformer/peg_transformer_factory.cpp | 3 +- .../peg/transformer/transform_common.cpp | 70 +- .../parser/peg/transformer/transform_copy.cpp | 21 +- .../transformer/transform_create_sequence.cpp | 10 - .../transformer/transform_create_table.cpp | 3 - .../peg/transformer/transform_create_type.cpp | 7 +- .../peg/transformer/transform_create_view.cpp | 3 - .../peg/transformer/transform_delete.cpp | 16 +- .../peg/transformer/transform_describe.cpp | 3 - .../parser/peg/transformer/transform_drop.cpp | 10 +- .../peg/transformer/transform_expression.cpp | 174 ++-- .../transform_generated_trampoline.cpp | 775 ++++-------------- .../transform_generic_copy_option.cpp | 3 +- .../peg/transformer/transform_insert.cpp | 6 +- .../peg/transformer/transform_pivot.cpp | 12 +- .../peg/transformer/transform_select.cpp | 36 +- .../peg/transformer/transform_update.cpp | 24 +- .../parser/peg/transformer/transform_use.cpp | 16 +- .../peg/transformer/transform_vacuum.cpp | 2 - src/duckdb/src/planner/bind_context.cpp | 17 +- src/duckdb/src/planner/binder.cpp | 5 - .../expression/bind_aggregate_expression.cpp | 15 - .../expression/bind_columnref_expression.cpp | 3 - .../expression/bind_function_expression.cpp | 40 +- .../expression/bind_macro_expression.cpp | 3 - .../expression/bind_type_expression.cpp | 1 - .../expression/bind_window_expression.cpp | 4 +- .../binder/query_node/bind_cte_node.cpp | 3 - .../query_node/bind_recursive_cte_node.cpp | 3 - .../binder/query_node/bind_select_node.cpp | 7 +- .../query_node/bind_table_macro_node.cpp | 3 - .../planner/binder/statement/bind_attach.cpp | 4 +- .../planner/binder/statement/bind_connect.cpp | 2 +- .../planner/binder/statement/bind_copy.cpp | 2 +- .../binder/statement/bind_copy_database.cpp | 11 +- .../binder/statement/bind_create_table.cpp | 5 - .../planner/binder/statement/bind_export.cpp | 7 +- .../statement/bind_external_resource.cpp | 2 +- .../binder/statement/bind_merge_into.cpp | 48 +- .../binder/tableref/bind_basetableref.cpp | 6 +- .../planner/binder/tableref/bind_pivot.cpp | 43 +- .../planner/binder/tableref/bind_showref.cpp | 3 - .../binder/tableref/bind_table_function.cpp | 9 - .../expression/bound_aggregate_expression.cpp | 6 - .../expression/bound_cast_expression.cpp | 68 +- src/duckdb/src/planner/expression_binder.cpp | 4 - .../expression_binder/check_binder.cpp | 5 +- .../expression_binder/index_binder.cpp | 23 +- .../src/planner/filter/dynamic_filter.cpp | 4 +- .../filter/table_filter_bloom_function.cpp | 128 +-- .../filter/table_filter_dynamic_function.cpp | 5 +- .../planner/filter/table_filter_functions.cpp | 3 +- .../src/planner/operator/logical_join.cpp | 16 - src/duckdb/src/planner/planner.cpp | 9 +- .../bound_expression_sql_exporter.cpp | 14 +- .../sql_export/sql_export_composition.cpp | 3 - .../sql_export/sql_export_functions.cpp | 10 +- .../sql_export/sql_export_relations.cpp | 11 +- .../planner/sql_export/sql_export_window.cpp | 28 +- .../subquery/delim_join_cte_rewriter.cpp | 5 +- .../subquery/flatten_dependent_join.cpp | 14 +- src/duckdb/src/planner/table_filter_state.cpp | 2 +- src/duckdb/src/storage/block_allocator.cpp | 29 +- src/duckdb/src/storage/buffer/buffer_pool.cpp | 4 +- src/duckdb/src/storage/checkpoint_manager.cpp | 7 +- .../src/storage/compression/dict_fsst.cpp | 14 +- .../compression/dict_fsst/decompression.cpp | 37 +- src/duckdb/src/storage/compression/fsst.cpp | 6 +- .../storage/compression/numeric_constant.cpp | 17 +- src/duckdb/src/storage/compression/rle.cpp | 22 +- .../compression/string_uncompressed.cpp | 5 +- .../compression/validity_uncompressed.cpp | 19 +- src/duckdb/src/storage/data_table.cpp | 2 - .../caching_file_system.cpp | 6 - .../src/storage/statistics/numeric_stats.cpp | 27 +- .../src/storage/statistics/variant_stats.cpp | 4 +- src/duckdb/src/storage/table/column_data.cpp | 77 +- .../src/storage/table/column_segment.cpp | 9 +- .../storage/table/standard_column_data.cpp | 9 +- src/duckdb/src/storage/wal_replay.cpp | 5 - .../src/transaction/meta_transaction.cpp | 7 +- src/duckdb/third_party/httplib/httplib.hpp | 16 +- src/duckdb/third_party/ska_sort/ska_sort.hpp | 64 +- src/duckdb/third_party/vergesort/vergesort.h | 30 +- 317 files changed, 2172 insertions(+), 6828 deletions(-) delete mode 100644 src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp diff --git a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp index 2b7768041..bbe22c410 100644 --- a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp @@ -239,9 +239,8 @@ struct IntervalAverageOperation : public BaseSumOperation(shifted_days / hugeint_t(count)); + days_remainder *= Interval::MICROS_PER_DAY; + micros_remainder += days_remainder / count; target.micros += micros_remainder; } } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp index 343915caa..3f1b8affc 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp @@ -123,12 +123,15 @@ struct BitStringAggOperation { if (min > max) { throw InvalidInputException("Invalid explicit bitstring range: Minimum (%d) > maximum (%d)", min, max); } - // the difference of two (at most 64-bit) values does not always fit in their own type, but it fits in a hugeint - const auto range = Hugeint::Convert(max) - Hugeint::Convert(min); - if (range >= Hugeint::Convert(NumericLimits::Maximum())) { + INPUT_TYPE result; + if (!TrySubtractOperator::Operation(max, min, result)) { return NumericLimits::Maximum(); } - return Hugeint::Cast(range) + 1; + auto val = NumericCast(result); + if (val == NumericLimits::Maximum()) { + return val; + } + return val + 1; } template diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp index a5e0a5a39..c7b4853a5 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp @@ -1,4 +1,3 @@ -#include "duckdb/common/operator/add.hpp" #include "core_functions/aggregate/distributive_functions.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" @@ -9,17 +8,15 @@ namespace duckdb { namespace { -//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's -//! method used by var_samp: M2..M4 are exactly zero for constant input instead of rounding noise of power sums struct KurtosisState { - static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3", "m4"}; + static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub", "sum_four"}; using STATE_TYPE = StructStateType; idx_t n; - double mean; // M1 - double m2; // M2 = sum((x - mean)^2) - double m3; // M3 = sum((x - mean)^3) - double m4; // M4 = sum((x - mean)^4) + double sum; + double sum_sqr; + double sum_cub; + double sum_four; }; struct KurtosisFlagBiasCorrection {}; @@ -31,29 +28,18 @@ struct KurtosisOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - // a block of identical values has mean "input" and zero central moments - STATE constant; - constant.n = count; - constant.mean = static_cast(input); - constant.m2 = 0; - constant.m3 = 0; - constant.m4 = 0; - Combine(constant, state, unary_input.input); + for (idx_t i = 0; i < count; i++) { + Operation(state, input, unary_input); + } } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { - const double n1 = static_cast(state.n); + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { state.n++; - const double n = static_cast(state.n); - const double delta = static_cast(input) - state.mean; - const double delta_n = delta / n; - const double delta_n2 = delta_n * delta_n; - const double term1 = delta * delta_n * n1; - state.mean += delta_n; - state.m4 += term1 * delta_n2 * (n * n - 3 * n + 3) + 6 * delta_n2 * state.m2 - 4 * delta_n * state.m3; - state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; - state.m2 += term1; + state.sum += input; + state.sum_sqr += pow(input, 2); + state.sum_cub += pow(input, 3); + state.sum_four += pow(input, 4); } template @@ -61,52 +47,45 @@ struct KurtosisOperation { if (source.n == 0) { return; } - if (target.n == 0) { - target = source; - return; - } - idx_t count; - if (!TryAddOperator::Operation(target.n, source.n, count)) { - throw OutOfRangeException("Overflow in aggregate state combine"); - } - const double na = static_cast(target.n); - const double nb = static_cast(source.n); - const double n = static_cast(count); - const double delta = source.mean - target.mean; - const double delta_n = delta / n; - const double delta_n2 = delta_n * delta_n; - target.m4 += source.m4 + delta * delta_n * delta_n2 * na * nb * (na * na - na * nb + nb * nb) + - 6 * delta_n2 * (na * na * source.m2 + nb * nb * target.m2) + - 4 * delta_n * (na * source.m3 - nb * target.m3); - target.m3 += - source.m3 + delta * delta_n2 * na * nb * (na - nb) + 3 * delta_n * (na * source.m2 - nb * target.m2); - target.m2 += source.m2 + delta * delta_n * na * nb; - target.mean += delta_n * nb; - target.n = count; + target.n += source.n; + target.sum += source.sum; + target.sum_sqr += source.sum_sqr; + target.sum_cub += source.sum_cub; + target.sum_four += source.sum_four; } template static void Finalize(STATE &state, TARGET_TYPE &target, AggregateFinalizeData &finalize_data) { - if (state.n <= 1) { + auto n = (double)state.n; + if (n <= 1) { finalize_data.ReturnNull(); return; } - if (std::is_same::value && state.n <= 3) { + if (std::is_same::value && n <= 3) { finalize_data.ReturnNull(); return; } - // kurtosis is undefined for zero variance (M2 is exactly 0 for constant input) - if (state.m2 <= 0) { + double temp = 1 / n; + //! This is necessary due to linux 32 bits + long double temp_aux = 1 / n; + if (state.sum_sqr - state.sum * state.sum * temp == 0 || + state.sum_sqr - state.sum * state.sum * temp_aux == 0) { + finalize_data.ReturnNull(); + return; + } + double m4 = + temp * (state.sum_four - 4 * state.sum_cub * state.sum * temp + + 6 * state.sum_sqr * state.sum * state.sum * temp * temp - 3 * pow(state.sum, 4) * pow(temp, 3)); + + double m2 = temp * (state.sum_sqr - state.sum * state.sum * temp); + if (m2 <= 0) { // m2 shouldn't be below 0 but floating points are weird finalize_data.ReturnNull(); return; } - const double n = static_cast(state.n); - // m4 / m2^2 with the population moments m4 = M4 / n and m2 = M2 / n - const double m4_over_m2_sqr = n * (state.m4 / state.m2) / state.m2; if (std::is_same::value) { - target = m4_over_m2_sqr - 3; + target = m4 / (m2 * m2) - 3; } else { - target = (n - 1) * ((n + 1) * m4_over_m2_sqr - 3 * (n - 1)) / ((n - 2) * (n - 3)); + target = (n - 1) * ((n + 1) * m4 / (m2 * m2) - 3 * (n - 1)) / ((n - 2) * (n - 3)); } if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("Kurtosis is out of range!"); diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp index f354c98bf..8443c7e55 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp @@ -3,47 +3,37 @@ #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/common/algorithm.hpp" +#include namespace duckdb { namespace { -//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's -//! method used by var_samp: M2 and M3 are exactly zero for constant input instead of rounding noise of power sums struct SkewState { - static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3"}; + static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub"}; using STATE_TYPE = StructStateType; idx_t n; - double mean; // M1 - double m2; // M2 = sum((x - mean)^2) - double m3; // M3 = sum((x - mean)^3) + double sum; + double sum_sqr; + double sum_cub; }; struct SkewnessOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - // a block of identical values has mean "input" and zero central moments - STATE constant; - constant.n = count; - constant.mean = static_cast(input); - constant.m2 = 0; - constant.m3 = 0; - Combine(constant, state, unary_input.input); + for (idx_t i = 0; i < count; i++) { + Operation(state, input, unary_input); + } } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { - const double n1 = static_cast(state.n); + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { state.n++; - const double n = static_cast(state.n); - const double delta = static_cast(input) - state.mean; - const double delta_n = delta / n; - const double term1 = delta * delta_n * n1; - state.mean += delta_n; - state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; - state.m2 += term1; + state.sum += input; + state.sum_sqr += pow(input, 2); + state.sum_cub += pow(input, 3); } template @@ -51,22 +41,11 @@ struct SkewnessOperation { if (source.n == 0) { return; } - if (target.n == 0) { - target = source; - return; - } - const auto count = target.n + source.n; - D_ASSERT(count >= target.n); // This is a check that we are not overflowing - const double na = static_cast(target.n); - const double nb = static_cast(source.n); - const double n = static_cast(count); - const double delta = source.mean - target.mean; - const double delta_n = delta / n; - target.m3 += source.m3 + delta * delta_n * delta_n * na * nb * (na - nb) + - 3 * delta_n * (na * source.m2 - nb * target.m2); - target.m2 += source.m2 + delta * delta_n * na * nb; - target.mean += delta_n * nb; - target.n = count; + + target.n += source.n; + target.sum += source.sum; + target.sum_sqr += source.sum_sqr; + target.sum_cub += source.sum_cub; } template @@ -75,15 +54,26 @@ struct SkewnessOperation { finalize_data.ReturnNull(); return; } - // skewness is undefined for zero variance (M2 is exactly 0 for constant input) - if (state.m2 <= 0) { + double n = state.n; + double temp = 1 / n; + double raw_m2 = state.sum_sqr - state.sum * state.sum * temp; + // Only treat finite second-moment noise as zero; overflow should still surface as out-of-range. + if (Value::DoubleIsFinite(raw_m2) && Value::DoubleIsFinite(state.sum_sqr) && + // Scale the tolerance by the accumulated squared magnitude instead of using a fixed epsilon. + std::abs(raw_m2) <= std::numeric_limits::epsilon() * std::max(1.0, std::abs(state.sum_sqr))) { + finalize_data.ReturnNull(); + return; + } + double variance = temp * raw_m2; + if (variance <= 0) { finalize_data.ReturnNull(); return; } - const double n = static_cast(state.n); - // g1 = (M3 / n) / (M2 / n)^1.5 = sqrt(n) * M3 / M2^1.5, with the bias correction sqrt(n (n - 1)) / (n - 2) - const double g1 = std::sqrt(n) * (state.m3 / state.m2) / std::sqrt(state.m2); - target = std::sqrt(n * (n - 1)) / (n - 2) * g1; + auto p = std::pow(variance, 3); + double div = std::sqrt(p); + double temp1 = std::sqrt(n * (n - 1)) / (n - 2); + target = temp1 * temp * + (state.sum_cub - 3 * state.sum_sqr * state.sum * temp + 2 * pow(state.sum, 3) * temp * temp) / div; if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("SKEW is out of range!"); } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp index bcb7594c2..55a67fb58 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp @@ -247,17 +247,8 @@ struct DecimalSumOperation : public OP { } }; -//! Binds the regular sum that sum_no_overflow is a specialized implementation of -pair> BindLogicalSum(ClientContext &context, - vector> &arguments) { - vector argument_types; - for (auto &argument : arguments) { - argument_types.push_back(argument->GetReturnType()); - } - auto &entry = Catalog::GetEntry( - context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); - FunctionBinder binder(context); - return binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); +unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { + throw BinderException("sum_no_overflow is for internal use only!"); } void SumNoOverflowSerialize(Serializer &serializer, const optional_ptr bind_data, @@ -270,10 +261,15 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo auto &return_type = deserializer.Get(); auto &children = deserializer.Get(); vector> arguments; + vector argument_types; for (auto &child : children) { arguments.push_back(child.get().Copy()); + argument_types.push_back(child.get().GetReturnType()); } - auto logical = BindLogicalSum(context, arguments); + auto &entry = Catalog::GetEntry( + context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); + FunctionBinder binder(context); + auto logical = binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); if (logical.second || (!return_type.IsAggregateState() && logical.first.GetReturnType() != return_type)) { throw SerializationException("Cannot reconstruct the logical sum signature"); } @@ -283,8 +279,6 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo return nullptr; } -unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input); - AggregateFunction GetSumAggregateNoOverflow(PhysicalType type) { switch (type) { case PhysicalType::INT32: { @@ -324,20 +318,6 @@ AggregateFunction GetSumAggregateNoOverflowDecimal() { return aggr; } -//! Binding sum_no_overflow by name (e.g. when re-binding an exported aggregate state) delegates to the regular sum, -//! with the no-overflow implementation swapped in as statistics propagation does -unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { - auto &function = input.GetBoundFunction(); - auto &arguments = input.GetArguments(); - const auto parameter_type = function.GetArguments()[0]; - auto logical = BindLogicalSum(input.GetClientContext(), arguments); - if (parameter_type.id() != LogicalTypeId::DECIMAL) { - logical.first.ReplaceImplementation(GetSumAggregateNoOverflow(parameter_type.InternalType())); - } - function = std::move(logical.first); - return std::move(logical.second); -} - unique_ptr SumPropagateStats(ClientContext &context, BoundAggregateExpression &expr, AggregateStatisticsInput &input) { if (!input.node_stats || !input.node_stats->has_max_cardinality) { diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp index b545e937a..008c81d94 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp @@ -1,4 +1,3 @@ -#include "duckdb/common/operator/add.hpp" #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/common/vector/list_vector.hpp" @@ -39,22 +38,6 @@ struct ApproxQuantileCoding { } return false; } - - template - static void Decode(const SAVE_TYPE &source, TARGET_TYPE &target, const LogicalType &type) { - Decode(source, target); - if (type.id() != LogicalTypeId::DECIMAL) { - return; - } - // The unscaled decimal range can be narrower than its physical integer range. - const auto limit = Hugeint::POWERS_OF_TEN[DecimalType::GetWidth(type)] - 1; - const auto value = Cast::Operation(target); - if (value > limit) { - target = Cast::Operation(limit); - } else if (value < -limit) { - target = Cast::Operation(-limit); - } - } }; template <> @@ -81,26 +64,6 @@ bool ApproxQuantileCoding::Decode(const double &source, dtime_tz_t &target) { return decoded; } -template <> -double ApproxQuantileCoding::Encode(const timestamp_t &input) { - return Encode(input.value); -} - -template <> -bool ApproxQuantileCoding::Decode(const double &source, timestamp_t &target) { - int64_t value; - const auto decoded = Decode(source, value); - // the infinities are the largest and smallest timestamps - do not return anything beyond them - if (value >= timestamp_t::infinity().value) { - target = timestamp_t::infinity(); - } else if (value <= timestamp_t::ninfinity().value) { - target = timestamp_t::ninfinity(); - } else { - target = timestamp_t(value); - } - return decoded; -} - struct ApproximateQuantileBindData : public FunctionData { ApproximateQuantileBindData() { } @@ -174,10 +137,8 @@ struct ApproxQuantileOperation { auto &allocator = aggr_input_data.allocator; target.h = allocator.Make(allocator, 100); } - if (!TryAddOperator::Operation(target.pos, source.pos, target.pos)) { - throw OutOfRangeException("Overflow in aggregate state combine"); - } target.h->merge(source.h); + target.pos += source.pos; } static bool IgnoreNull() { @@ -198,7 +159,7 @@ struct ApproxQuantileScalarOperation : public ApproxQuantileOperation { auto &bind_data = finalize_data.input.bind_data->template Cast(); D_ASSERT(bind_data.quantiles.size() == 1); const auto source = state.h->quantile(bind_data.quantiles[0]); - ApproxQuantileCoding::Decode(source, target, finalize_data.result.GetType()); + ApproxQuantileCoding::Decode(source, target); } }; @@ -348,10 +309,6 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ return AggregateFunction::UnaryAggregate(type, type); } - if (type.id() == LogicalTypeId::TIMESTAMP || type.id() == LogicalTypeId::TIMESTAMP_TZ) { - return AggregateFunction::UnaryAggregate(type, type); - } switch (type.InternalType()) { case PhysicalType::INT8: return AggregateFunction::UnaryAggregate( @@ -382,12 +339,15 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ AggregateFunction GetApproximateQuantileDecimalAggregateFunction(const LogicalType &type) { switch (type.InternalType()) { case PhysicalType::INT8: + return GetApproximateQuantileAggregateFunction(LogicalType::TINYINT); case PhysicalType::INT16: + return GetApproximateQuantileAggregateFunction(LogicalType::SMALLINT); case PhysicalType::INT32: + return GetApproximateQuantileAggregateFunction(LogicalType::INTEGER); case PhysicalType::INT64: + return GetApproximateQuantileAggregateFunction(LogicalType::BIGINT); case PhysicalType::INT128: - // keep the DECIMAL type as the input and return type so that the width and scale survive the bind - return GetApproximateQuantileAggregateFunction(type); + return GetApproximateQuantileAggregateFunction(LogicalType::HUGEINT); default: throw InternalException("Unimplemented quantile decimal aggregate"); } @@ -501,7 +461,7 @@ struct ApproxQuantileListOperation : public ApproxQuantileOperation { const auto &quantile = bind_data.quantiles[q]; const auto &source = state.h->quantile(quantile); auto &target = rdata[ridx + q]; - ApproxQuantileCoding::Decode(source, target, result.GetType()); + ApproxQuantileCoding::Decode(source, target); } ListVector::SetListSize(finalize_data.result, entry.offset + entry.length); @@ -538,11 +498,10 @@ AggregateFunction GetApproxQuantileListAggregateFunction(const LogicalType &type case LogicalTypeId::DATE: return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::BIGINT: - case LogicalTypeId::TIME: - return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::TIMESTAMP: case LogicalTypeId::TIMESTAMP_TZ: - return GetTypedApproxQuantileListAggregateFunction(type); + case LogicalTypeId::TIME: + return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::TIME_TZ: // Not binary comparable return GetTypedApproxQuantileListAggregateFunction(type); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp index 34dcb6f6d..719736066 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp @@ -9,7 +9,6 @@ #pragma once -#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" namespace duckdb { @@ -49,10 +48,8 @@ struct CovarOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - uint64_t count; - if (!TryAddOperator::Operation(target.count, source.count, count)) { - throw OutOfRangeException("Overflow in aggregate state combine"); - } + const auto count = target.count + source.count; + D_ASSERT(count >= target.count); // This is a check that we are not overflowing const auto target_count = static_cast(target.count); const auto source_count = static_cast(source.count); const auto total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp index 0068b906d..0150248c1 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp @@ -8,7 +8,6 @@ #pragma once -#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include #include @@ -58,10 +57,8 @@ struct STDDevBaseOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - uint64_t count; - if (!TryAddOperator::Operation(target.count, source.count, count)) { - throw OutOfRangeException("Overflow in aggregate state combine"); - } + const auto count = target.count + source.count; + D_ASSERT(count >= target.count); // This is a check that we are not overflowing const double target_count = static_cast(target.count); const double source_count = static_cast(source.count); const double total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp index dbe33743d..ac9836499 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp @@ -9,7 +9,6 @@ #pragma once -#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include "core_functions/aggregate/algebraic/covar.hpp" #include "core_functions/aggregate/algebraic/stddev.hpp" @@ -19,16 +18,11 @@ namespace duckdb { struct RegrCountFunction { template static void Combine(const STATE &source, STATE &target, AggregateInputData &) { - if (!TryAddOperator::Operation(target, source, target)) { - throw OutOfRangeException("Overflow in aggregate state combine"); - } + target += source; } template static void Finalize(STATE &state, T &target, AggregateFinalizeData &finalize_data) { - if (state > NumericLimits::Maximum()) { - throw OutOfRangeException("regr_count result %llu does not fit in the result type", state); - } target = static_cast(state); } static bool IgnoreNull() { diff --git a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp index 08e1a0abf..91105869d 100644 --- a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp +++ b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp @@ -95,8 +95,6 @@ ScalarFunction ArrayValueFun::GetFunction() { fun.SetResolveTypesCallback(ArrayValueResolveTypes); fun.GetSignature().AddArgs("args", LogicalType::ANY); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); - // casting the arguments to the common child type can fail, e.g. for arrays of different sizes - fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp index 5663e2957..3b8f8ae2e 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp @@ -440,19 +440,7 @@ struct DatePart { template static unique_ptr PropagateStatistics(ClientContext &context, FunctionStatisticsInput &input) { - auto result = PropagateDatePartStatistics(input.child_stats); - if (!result) { - return nullptr; - } - const auto min_year = NumericStats::GetMin(*result); - const auto max_year = NumericStats::GetMax(*result); - if (min_year > 0) { - return PropagateDatePartStatistics(input.child_stats); - } - // the week is subtracted for years before the common era, so only bound the result by the ISO year - NumericStats::SetMin(*result, Value::BIGINT(min_year * 100 - 53)); - NumericStats::SetMax(*result, Value::BIGINT(max_year * 100 + 53)); - return result; + return PropagateDatePartStatistics(input.child_stats); } }; diff --git a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp index 9998eb3d5..e6bd79669 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp @@ -494,16 +494,8 @@ unique_ptr DateTruncStatistics(vector &child_sta } // Infinite values are unmodified - TR min_part; - TR max_part; - try { - min_part = DateTrunc::UnaryFunction(min); - max_part = DateTrunc::UnaryFunction(max); - } catch (const ConversionException &) { - // the bounds are not in the range of the result type (e.g. a DATE beyond the TIMESTAMP range) - the rows - // holding them may not even reach the function, so do not propagate statistics - return nullptr; - } + auto min_part = DateTrunc::UnaryFunction(min); + auto max_part = DateTrunc::UnaryFunction(max); auto min_value = Value::CreateValue(min_part); auto max_value = Value::CreateValue(max_part); diff --git a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp index c1da6d140..48b0adbea 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp @@ -59,8 +59,6 @@ ScalarFunction CurrentSettingFun::GetFunction() { auto fun = ScalarFunction({}, LogicalType::ANY, CurrentSettingFunction, CurrentSettingBind); fun.GetSignature().AddParameter("setting_name", LogicalType::VARCHAR); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); - // the value of the setting can change between queries - fun.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp index 03fd01d40..685dd73b1 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp @@ -111,11 +111,7 @@ static unique_ptr BindMakeTypeFunctionExpression(FunctionBindExpress } auto type_name = args.front().second.GetValue(); - QualifiedName qualified_name; - string error; - if (!QualifiedName::TryParse(type_name, qualified_name, error)) { - throw ParserException(error); - } + auto qualified_name = QualifiedName::Parse(type_name); auto unbound_type = LogicalType::UNBOUND(make_uniq(std::move(qualified_name), std::move(type_args))); diff --git a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp index a2281bdca..5e498ea96 100644 --- a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp +++ b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp @@ -459,16 +459,6 @@ static unique_ptr ListSortUnbind(FunctionUnbindInput &input) { return make_uniq(QualifiedName("system", "main", "cast_to_type"), std::move(result)); } -static void ListSortResolveTypes(ResolveScalarFunctionTypesInput &input) { - auto list_type = input.GetArgumentType(0); - if (list_type.id() == LogicalTypeId::ARRAY) { - list_type = LogicalType::LIST(ArrayType::GetChildType(list_type)); - } - if (list_type.id() == LogicalTypeId::LIST) { - input.GetBoundFunction().SetReturnType(list_type); - } -} - static void SetListSortSerialization(ScalarFunction &function) { function.SetUnbindCallback(ListSortUnbind); function.SetSerializeCallback(ListSortBindData::Serialize); @@ -493,11 +483,8 @@ ScalarFunctionSet ListSortFun::GetFunctions() { .AddParameter("sort_order", LogicalType::VARCHAR) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort); - sort.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_order); - sort_order.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_orders); - sort_orders.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_sort; list_sort.AddFunction(sort); @@ -508,17 +495,17 @@ ScalarFunctionSet ListSortFun::GetFunctions() { ScalarFunctionSet ListGradeUpFun::GetFunctions() { // one parameter: list - ScalarFunction sort({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); + ScalarFunction sort({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); sort.GetSignature().AddParameter("list", LogicalType::LIST(LogicalType::ANY)); // two parameters: list, order - ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); sort_order.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR); // three parameters: list, order, null order - ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); sort_orders.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR) @@ -546,9 +533,7 @@ ScalarFunctionSet ListReverseSortFun::GetFunctions() { .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort_reverse); - sort_reverse.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_reverse_null_order); - sort_reverse_null_order.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_reverse_sort; list_reverse_sort.AddFunction(sort_reverse); diff --git a/src/duckdb/extension/core_functions/scalar/map/switch.cpp b/src/duckdb/extension/core_functions/scalar/map/switch.cpp index 1eec73243..c060299bd 100644 --- a/src/duckdb/extension/core_functions/scalar/map/switch.cpp +++ b/src/duckdb/extension/core_functions/scalar/map/switch.cpp @@ -122,8 +122,6 @@ ScalarFunctionSet SwitchFun::GetFunctions() { switch_expression.GetSignature().AddParameter(param.first, param.second); } switch_expression.SetBindExpressionCallback(SwitchBindExpression); - // switch is rewritten into a CASE expression, so NULL arguments do not make the result NULL - switch_expression.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); func_set.AddFunction(std::move(switch_expression)); } diff --git a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp index a69ceeede..f71b80a0e 100644 --- a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp +++ b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp @@ -1005,14 +1005,9 @@ struct RoundOperatorPrecision { double rounded_value; if (precision < 0) { double modifier = std::pow(10, -TA(precision)); - if (std::isinf(modifier)) { - // every finite value rounds to zero - return Value::IsFinite(input) ? 0 : input; - } rounded_value = ROUND_POLICY::Nearest(input / modifier) * modifier; if (std::isinf(rounded_value) || std::isnan(rounded_value)) { - // the input is too large to be rounded to this precision - return input; + return 0; } } else { double modifier = std::pow(10, TA(precision)); diff --git a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp index 4abc7d07c..a4c40b066 100644 --- a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp @@ -1,6 +1,5 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" -#include "duckdb/common/vector/vector_writer.hpp" #include "core_functions/scalar/string_functions.hpp" #include "duckdb/function/scalar/string_common.hpp" #include "duckdb/common/local_file_system.hpp" @@ -106,111 +105,126 @@ static idx_t SplitPath(string_t input, const string &sep, CB &&emit) { return list_idx; } -//! The optional separator and trim_extension arguments - NULL values use the default options -struct PathOptionArguments { - optional_ptr separator; - optional_ptr trim_extension; -}; - -static PathOptionArguments GetPathOptionArguments(DataChunk &args, const bool front_trim) { - PathOptionArguments result; +static void ReadOptionalArgs(DataChunk &args, Vector &sep, Vector &trim, const bool &front_trim) { switch (args.ColumnCount()) { - case 1: + case 1: { // use default values break; - case 2: - switch (args.data[1].GetType().id()) { - case LogicalTypeId::VARCHAR: - result.separator = args.data[1]; - break; - case LogicalTypeId::BOOLEAN: // parse_path and parse_dirname won't get in here - result.trim_extension = args.data[1]; - break; - default: - throw InvalidInputException("Invalid argument type"); + } + case 2: { + UnifiedVectorFormat sec_arg; + args.data[1].ToUnifiedFormat(sec_arg); + if (sec_arg.validity.RowIsValid(0)) { // if not NULL + switch (args.data[1].GetType().id()) { + case LogicalTypeId::VARCHAR: { + sep.Reinterpret(args.data[1]); + break; + } + case LogicalTypeId::BOOLEAN: { // parse_path and parse_driname won't get in here + trim.Reinterpret(args.data[1]); + break; + } + default: + throw InvalidInputException("Invalid argument type"); + } } break; - case 3: - if (front_trim) { + } + case 3: { + if (!front_trim) { + // set trim_extension + UnifiedVectorFormat sec_arg; + args.data[1].ToUnifiedFormat(sec_arg); + if (sec_arg.validity.RowIsValid(0)) { + trim.Reinterpret(args.data[1]); + } + UnifiedVectorFormat third_arg; + args.data[2].ToUnifiedFormat(third_arg); + if (third_arg.validity.RowIsValid(0)) { + sep.Reinterpret(args.data[2]); + } + } else { throw InvalidInputException("Invalid number of arguments"); } - result.trim_extension = args.data[1]; - result.separator = args.data[2]; break; + } default: throw InvalidInputException("Invalid number of arguments"); } - return result; -} - -template -static void ExecutePathFunction(DataChunk &args, Vector &result, const bool front_trim, OP &&op) { - const auto count = args.size(); - auto options = GetPathOptionArguments(args, front_trim); - Vector default_separator(string_t("default"), count_t(count)); - Vector default_trim_extension(Value::BOOLEAN(false), count_t(count)); - auto paths = args.data[0].Values(); - auto separators = (options.separator ? *options.separator : default_separator).Values(); - auto trim_extensions = (options.trim_extension ? *options.trim_extension : default_trim_extension).Values(); - - auto writer = FlatVector::Writer(result, count); - for (idx_t i = 0; i < count; i++) { - auto path = paths[i]; - if (!path.IsValid()) { - writer.WriteNull(); - continue; - } - auto separator = separators[i]; - auto sep = GetSeparator(separator.IsValid() ? separator.GetValue() : string_t("default")); - auto trim_extension = trim_extensions[i]; - writer.WriteValue(op(path.GetValue(), sep, trim_extension.IsValid() && trim_extension.GetValue())); - } } template static void TrimPathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - ExecutePathFunction(args, result, FRONT_TRIM, [&](const string_t &input, const string &sep, bool trim_extension) { - auto data = input.GetData(); - auto input_size = input.GetSize(); + // set default values + const Vector &path = args.data[0]; + Vector separator(string_t("default"), count_t(args.size())); + Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); + ReadOptionalArgs(args, separator, trim_extension, FRONT_TRIM); - // find the beginning idx and the size of the result string - idx_t begin = 0; - idx_t new_size = input_size; - if (FRONT_TRIM) { // left trim - auto pos = Find(data, input_size, sep); - if (pos == 0) { // path starts with separator - pos = 1; - } - new_size = (IsIdxValid(pos, input_size)) ? pos : 0; - } else { // right trim - auto idx_last_sep = FindLast(data, input_size, sep); - if (IsIdxValid(idx_last_sep, input_size)) { - begin = idx_last_sep + 1; - } - if (trim_extension) { - auto idx_extension_sep = FindLast(data, input_size, "."); - if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { - new_size = idx_extension_sep; - } - } - } - D_ASSERT(begin <= new_size); - return string_t(data + begin, UnsafeNumericCast(new_size - begin)); - }); + TernaryExecutor::Execute( + path, separator, trim_extension, result, [&](string_t &inputs, string_t input_sep, bool trim_extension) { + auto data = inputs.GetData(); + auto input_size = inputs.GetSize(); + auto sep = GetSeparator(input_sep.GetString()); + + // find the beginning idx and the size of the result string + idx_t begin = 0; + idx_t new_size = input_size; + if (FRONT_TRIM) { // left trim + auto pos = Find(data, input_size, sep); + if (pos == 0) { // path starts with separator + pos = 1; + } + new_size = (IsIdxValid(pos, input_size)) ? pos : 0; + } else { // right trim + auto idx_last_sep = FindLast(data, input_size, sep); + if (IsIdxValid(idx_last_sep, input_size)) { + begin = idx_last_sep + 1; + } + if (trim_extension) { + auto idx_extension_sep = FindLast(data, input_size, "."); + if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { + new_size = idx_extension_sep; + } + } + } + // copy the trimmed string + D_ASSERT(begin <= new_size); + auto target = StringVector::EmptyString(result, new_size - begin); + auto output = target.GetDataWriteable(); + memcpy(output, data + begin, new_size - begin); + + target.Finalize(); + return target; + }); } static void ParseDirpathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - ExecutePathFunction(args, result, true, [&](const string_t &input, const string &sep, bool) { - auto path = input.GetData(); - auto path_size = input.GetSize(); + // set default values + const Vector &path = args.data[0]; + Vector separator(string_t("default"), count_t(args.size())); + Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); + ReadOptionalArgs(args, separator, trim_extension, true); - auto last_sep = FindLast(path, path_size, sep); - if (last_sep == 0 && path_size == 1) { - last_sep = 1; - } - idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; - return string_t(path, UnsafeNumericCast(new_size)); - }); + auto &heap = StringVector::GetStringHeap(result); + BinaryExecutor::Execute( + path, separator, result, [&](string_t input_path, string_t input_sep) { + auto path = input_path.GetData(); + auto path_size = input_path.GetSize(); + auto sep = GetSeparator(input_sep.GetString()); + + auto last_sep = FindLast(path, path_size, sep); + if (last_sep == 0 && path_size == 1) { + last_sep = 1; + } + idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; + + auto target = heap.EmptyString(new_size); + auto output = target.GetDataWriteable(); + memcpy(output, path, new_size); + target.Finalize(); + return target; + }); } static void ParsePathFunction(DataChunk &args, ExpressionState &state, Vector &result) { diff --git a/src/duckdb/extension/core_functions/scalar/string/replace.cpp b/src/duckdb/extension/core_functions/scalar/string/replace.cpp index 91f72baf2..e2d70be15 100644 --- a/src/duckdb/extension/core_functions/scalar/string/replace.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/replace.cpp @@ -3,7 +3,6 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/common/vector_operations/ternary_executor.hpp" -#include "duckdb/function/scalar/string_common.hpp" #include #include @@ -14,11 +13,13 @@ namespace duckdb { static idx_t NextNeedle(const char *input_haystack, idx_t size_haystack, const char *input_needle, const idx_t size_needle) { // Needle needs something to proceed - if (size_needle > 0 && size_haystack > 0) { - auto position = FindStrInStr(const_uchar_ptr_cast(input_haystack), size_haystack, - const_uchar_ptr_cast(input_needle), size_needle); - if (position != DConstants::INVALID_INDEX) { - return position; + if (size_needle > 0) { + // Haystack should be bigger or equal size to the needle + for (idx_t string_position = 0; (size_haystack - string_position) >= size_needle; ++string_position) { + // Compare Needle to the Haystack + if ((memcmp(input_haystack + string_position, input_needle, size_needle) == 0)) { + return string_position; + } } } // Did not find the needle diff --git a/src/duckdb/extension/icu/collation/collation_collator.cpp b/src/duckdb/extension/icu/collation/collation_collator.cpp index d52e5e530..307eb7a3e 100644 --- a/src/duckdb/extension/icu/collation/collation_collator.cpp +++ b/src/duckdb/extension/icu/collation/collation_collator.cpp @@ -85,9 +85,6 @@ static collation_element_t MakeElement(uint32_t primary, uint32_t secondary, uin //! Returns the position of a Han character in radical/stroke order, or false if it is not a Han character static bool GetHanIndex(const CollationRoot &root, uint32_t codepoint, uint32_t &result) { // only the ranges of the block the code point is in have to be searched - if (codepoint >= 0x110000) { - return false; - } uint32_t lower = root.han_block_lower[codepoint >> HAN_BLOCK_SHIFT]; uint32_t upper = root.han_block_upper[codepoint >> HAN_BLOCK_SHIFT]; while (lower < upper) { @@ -127,9 +124,6 @@ static collation_element_t ImplicitElement(const CollationRoot &root, uint32_t c } static uint32_t LookupValue(const CollationTable &table, uint32_t codepoint) { - if (codepoint >= 0x110000) { - return 0; - } auto block = table.trie_stage1[codepoint >> COLLATION_TRIE_SHIFT]; return table.trie_stage2[block + (codepoint & COLLATION_TRIE_MASK)]; } diff --git a/src/duckdb/extension/icu/collation/collation_normalizer.cpp b/src/duckdb/extension/icu/collation/collation_normalizer.cpp index e354a96cd..8a9412909 100644 --- a/src/duckdb/extension/icu/collation/collation_normalizer.cpp +++ b/src/duckdb/extension/icu/collation/collation_normalizer.cpp @@ -66,7 +66,7 @@ uint32_t Normalizer::Decode(const char *data, idx_t size, vector &resu } codepoint = (codepoint << 6) | (trail & 0x3F); } - if (!valid || codepoint > 0x10FFFF) { + if (!valid) { result.push_back(REPLACEMENT_CHARACTER); pos++; continue; diff --git a/src/duckdb/extension/icu/collation/include/collation_collator.hpp b/src/duckdb/extension/icu/collation/include/collation_collator.hpp index 53ed16bc2..c3639dc58 100644 --- a/src/duckdb/extension/icu/collation/include/collation_collator.hpp +++ b/src/duckdb/extension/icu/collation/include/collation_collator.hpp @@ -48,19 +48,11 @@ struct TailoredBlocks { void Build(const uint32_t *codepoints, uint32_t count); bool Contains(uint32_t codepoint) const { - if (codepoint >= 0x110000) { - return false; - } auto block = codepoint >> BITMAP_SHIFT; return (bitmap[block / 64] & (1ULL << (block % 64))) != 0; } //! The part of the tailoring that can hold the code point void GetRange(uint32_t codepoint, uint32_t &lower, uint32_t &upper) const { - if (codepoint >= 0x110000) { - lower = 0; - upper = 0; - return; - } lower = bounds[codepoint >> BOUND_SHIFT]; upper = bounds[(codepoint >> BOUND_SHIFT) + 1]; } diff --git a/src/duckdb/extension/icu/icu-datefunc.cpp b/src/duckdb/extension/icu/icu-datefunc.cpp index ab10a86c7..d839b22db 100644 --- a/src/duckdb/extension/icu/icu-datefunc.cpp +++ b/src/duckdb/extension/icu/icu-datefunc.cpp @@ -78,17 +78,6 @@ Calendar *ICUDateFunc::CalendarCacheState::GetCalendar(const string_t &tz_id) { return entry.calendar.get(); } -Calendar *ICUDateFunc::CalendarCacheState::TryGetCalendar(const string_t &tz_id, string *error_message) { - auto &entry = GetEntry(tz_id); - if (!entry.calendar) { - if (error_message) { - *error_message = entry.error; - } - return nullptr; - } - return entry.calendar.get(); -} - void ICUDateFunc::CalendarCacheState::SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message) { auto &entry = GetEntry(tz_id); if (!entry.tz) { diff --git a/src/duckdb/extension/icu/icu-datepart.cpp b/src/duckdb/extension/icu/icu-datepart.cpp index 8ac85ecae..3b1caae00 100644 --- a/src/duckdb/extension/icu/icu-datepart.cpp +++ b/src/duckdb/extension/icu/icu-datepart.cpp @@ -677,8 +677,7 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { // register the individual operators // year/decade use CAL_YEAR (year-of-era, positive in both BC and AD), which is non-monotonic - // across the BC/AD flip; leave them unannotated. century/millennium are negated in era 0, which is the only era - // of some calendars, and yearweek decreases within negative years, so these are unannotated as well. + // across the BC/AD flip; leave them unannotated. era/century/millennium/isoyear are signed. // BIGINTs ICUDatePart::AddUnaryPartCodeFunctions("era", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); @@ -686,8 +685,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("month", loader); ICUDatePart::AddUnaryPartCodeFunctions("day", loader); ICUDatePart::AddUnaryPartCodeFunctions("decade", loader); - ICUDatePart::AddUnaryPartCodeFunctions("century", loader); - ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader); + ICUDatePart::AddUnaryPartCodeFunctions("century", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); + ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); ICUDatePart::AddUnaryPartCodeFunctions("microsecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("millisecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("second", loader); @@ -708,7 +707,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("julian", loader, LogicalType::DOUBLE); // register combinations - ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader); // ISO year and week + ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader, LogicalType::BIGINT, + ArgProperties().NonDecreasing()); // ISO year and week // register various aliases ICUDatePart::AddUnaryPartCodeFunctions("dayofmonth", loader); diff --git a/src/duckdb/extension/icu/icu-datesub.cpp b/src/duckdb/extension/icu/icu-datesub.cpp index 1c8d48541..d80be2ce8 100644 --- a/src/duckdb/extension/icu/icu-datesub.cpp +++ b/src/duckdb/extension/icu/icu-datesub.cpp @@ -209,20 +209,6 @@ ICUDateFunc::part_sub_t ICUDateFunc::SubtractFactory(DatePartSpecifier type) { // MS-SQL differences can be computed using ICU by truncating both arguments // to the desired part precision and then applying ICU subtraction/difference struct ICUCalendarDiff : public ICUDateFunc { - static unique_ptr Bind(BindScalarFunctionInput &input) { - auto part_value = input.TryGetConstant(0); - if (part_value && !part_value->IsNull()) { - DatePartSpecifier part; - if (TryGetDatePartSpecifier(part_value->GetValue(), part) && - (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || - part == DatePartSpecifier::MILLENNIUM)) { - // date_diff is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary - input.GetBoundFunction().SetArgProperties({}); - } - } - return ICUDateFunc::Bind(input); - } - static int64_t DifferenceEra(Calendar *calendar, timestamp_tz_t start_date, timestamp_tz_t end_date) { SetTime(calendar, start_date); const auto start_era = ExtractField(calendar, CAL_ERA); diff --git a/src/duckdb/extension/icu/icu-datetrunc.cpp b/src/duckdb/extension/icu/icu-datetrunc.cpp index 2e980012f..13562cc85 100644 --- a/src/duckdb/extension/icu/icu-datetrunc.cpp +++ b/src/duckdb/extension/icu/icu-datetrunc.cpp @@ -1,7 +1,6 @@ #include "include/icu-datetrunc.hpp" #include "include/icu-datefunc.hpp" -#include "duckdb/common/enums/date_part_specifier.hpp" #include "duckdb/common/vector_operations/binary_executor.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/main/extension/extension_loader.hpp" @@ -10,20 +9,6 @@ namespace duckdb { struct ICUDateTrunc : public ICUDateFunc { - static unique_ptr Bind(BindScalarFunctionInput &input) { - auto part_value = input.TryGetConstant(0); - if (part_value && !part_value->IsNull()) { - DatePartSpecifier part; - if (TryGetDatePartSpecifier(part_value->GetValue(), part) && - (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || - part == DatePartSpecifier::MILLENNIUM)) { - // date_trunc is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary - input.GetBoundFunction().SetArgProperties({}); - } - } - return ICUDateFunc::Bind(input); - } - static void PreserveOffsets(Calendar *calendar) { // We have to extract _everything_ before setting anything // Otherwise ICU will clear the fStamp fields diff --git a/src/duckdb/extension/icu/icu-makedate.cpp b/src/duckdb/extension/icu/icu-makedate.cpp index b41f54893..058355bc2 100644 --- a/src/duckdb/extension/icu/icu-makedate.cpp +++ b/src/duckdb/extension/icu/icu-makedate.cpp @@ -62,10 +62,8 @@ bool ICUMakeDate::CastNsToDate(Vector &source, Vector &result, idx_t count, Cast } // the date only needs the microsecond instant: floor the nanoseconds so no instant moves past midnight const auto nanos = input.value; - auto micros = nanos / Interval::NANOS_PER_MICRO; - if (nanos % Interval::NANOS_PER_MICRO < 0) { - micros--; - } + const auto micros = nanos >= 0 ? nanos / Interval::NANOS_PER_MICRO + : (nanos - (Interval::NANOS_PER_MICRO - 1)) / Interval::NANOS_PER_MICRO; return Operation(calendar.get(), timestamp_tz_t(micros)); }); return true; diff --git a/src/duckdb/extension/icu/icu-strptime.cpp b/src/duckdb/extension/icu/icu-strptime.cpp index e53514780..60c2b8b91 100644 --- a/src/duckdb/extension/icu/icu-strptime.cpp +++ b/src/duckdb/extension/icu/icu-strptime.cpp @@ -143,9 +143,12 @@ struct ICUStrptime : public ICUDateFunc { result = timestamp_tz_t(parsed.ToTimestamp()); return; } else { - // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone - auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); - result = GetTime(row_calendar, ToMicros(row_calendar, parsed, format)); + // Set TZ first, if any. + if (!parsed.tz.empty()) { + cache.SetTimeZone(calendar, parsed.tz); + } + + result = GetTime(calendar, ToMicros(calendar, parsed, format)); return; } } @@ -163,9 +166,11 @@ struct ICUStrptime : public ICUDateFunc { result.value = parsed.ToTimestamp().value; return; } else { - // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone - auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); - result = GetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format)); + // Set TZ first, if any. + if (!parsed.tz.empty()) { + cache.SetTimeZone(calendar, parsed.tz); + } + result = GetTimeNS(calendar, ToNanos(calendar, parsed, format)); return; } } @@ -202,9 +207,8 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else { - auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); - if (row_calendar && TryGetTime(row_calendar, ToMicros(row_calendar, parsed, format), result)) { + } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { + if (TryGetTime(calendar, ToMicros(calendar, parsed, format), result)) { return true; } } @@ -222,9 +226,8 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else { - auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); - if (row_calendar && TryGetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format), result)) { + } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { + if (TryGetTimeNS(calendar, ToNanos(calendar, parsed, format), result)) { return true; } } @@ -400,24 +403,18 @@ struct ICUStrptime : public ICUDateFunc { // Convert parts to a TZ (default or parsed) if no offset was provided auto calendar = cal.get(); - // Use the calendar of the time zone if one was provided - the shared calendar keeps the session zone + // Change TZ if one was provided. if (tz.GetSize()) { string error_msg; - calendar = cache.TryGetCalendar(tz, &error_msg); - if (!calendar) { + cache.SetTimeZone(calendar, tz, &error_msg); + if (!error_msg.empty()) { HandleCastError::AssignError(error_msg, parameters); return nullopt; } } // Now get the parts in the given time zone - timestamp_tz_t result; - string error_msg; - if (!TryFromNaive(calendar, us, result, error_msg)) { - HandleCastError::AssignError(error_msg, parameters); - return nullopt; - } - return result; + return FromNaive(calendar, us); } return timestamp_tz_t(us); diff --git a/src/duckdb/extension/icu/icu-timebucket.cpp b/src/duckdb/extension/icu/icu-timebucket.cpp index 495ead083..0a9586ab1 100644 --- a/src/duckdb/extension/icu/icu-timebucket.cpp +++ b/src/duckdb/extension/icu/icu-timebucket.cpp @@ -73,25 +73,6 @@ struct ICUTimeBucket : public ICUDateFunc { return Add(calendar, origin, interval_t {0, 0, result_micros}); } - //! Add resolves a local time that occurs twice (e.g. when the clocks are turned back) to its latter occurrence. - //! If that is after ts, ts lies between the two occurrences, and its bucket starts at the former occurrence. - static timestamp_tz_t FormerOccurrence(Calendar *calendar, const timestamp_tz_t bucket, const timestamp_tz_t ts) { - auto get_offset = [&](const timestamp_tz_t instant) { - SetTime(calendar, instant); - return int64_t(ExtractField(calendar, CAL_ZONE_OFFSET)) + ExtractField(calendar, CAL_DST_OFFSET); - }; - const auto ts_offset = get_offset(ts); - const auto bucket_offset = get_offset(bucket); - if (ts_offset <= bucket_offset) { - return bucket; - } - const timestamp_tz_t former(bucket.value - (ts_offset - bucket_offset) * Interval::MICROS_PER_MSEC); - if (ts < former || get_offset(former) != ts_offset) { - return bucket; - } - return former; - } - static inline timestamp_tz_t WidthConvertibleToDaysCommon(int32_t bucket_width_days, const timestamp_tz_t ts, const timestamp_tz_t origin, TZCalendar &calendar) { if (!bucket_width_days) { @@ -106,10 +87,9 @@ struct ICUTimeBucket : public ICUDateFunc { } auto bucket = Add(calendar, origin, interval_t {0, static_cast(result_days), 0}); if (ts < bucket) { - bucket = FormerOccurrence(calendar.GetCalendar(), bucket, ts); - } - while (ts < bucket) { + D_ASSERT(ts < origin); bucket = Add(calendar, bucket, interval_t {0, -bucket_width_days, 0}); + D_ASSERT(ts > bucket); } return bucket; @@ -143,14 +123,7 @@ struct ICUTimeBucket : public ICUDateFunc { SubtractOperatorOverflowCheck::Operation(result_months, bucket_width_months); } - auto bucket = Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); - if (ts < bucket) { - bucket = FormerOccurrence(calendar, bucket, ts); - } - while (ts < bucket) { - bucket = Add(calendar_p, bucket, interval_t {-bucket_width_months, 0, 0}); - } - return bucket; + return Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); } template diff --git a/src/duckdb/extension/icu/icu-timezone.cpp b/src/duckdb/extension/icu/icu-timezone.cpp index 23dd0195e..2caa2ebcb 100644 --- a/src/duckdb/extension/icu/icu-timezone.cpp +++ b/src/duckdb/extension/icu/icu-timezone.cpp @@ -708,10 +708,6 @@ timestamp_tz_t ICUDateFunc::FromNaive(Calendar *calendar, timestamp_t naive) { return ICUFromNaiveTimestamp::Operation(calendar, naive); } -bool ICUDateFunc::TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error) { - return ICUFromNaiveTimestamp::TryOperation(calendar, naive, result, error); -} - void RegisterICUTimeZoneFunctions(ExtensionLoader &loader) { // Table functions TableFunction tz_names("pg_timezone_names", {}, ICUTimeZoneFunction, ICUTimeZoneBind, ICUTimeZoneInit); diff --git a/src/duckdb/extension/icu/include/icu-datefunc.hpp b/src/duckdb/extension/icu/include/icu-datefunc.hpp index c47777f8e..04ad57f5b 100644 --- a/src/duckdb/extension/icu/include/icu-datefunc.hpp +++ b/src/duckdb/extension/icu/include/icu-datefunc.hpp @@ -62,8 +62,6 @@ struct ICUDateFunc { //! Returns a calendar for the time zone. Throws for unknown zones. Calendar *GetCalendar(const string_t &tz_id); - //! Returns a calendar for the time zone, or nullptr for unknown zones (filling error_message if provided). - Calendar *TryGetCalendar(const string_t &tz_id, string *error_message = nullptr); //! Sets the time zone for the calendar. //! For unknown zones, fills error_message if provided and throws otherwise. void SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message = nullptr); @@ -124,8 +122,6 @@ struct ICUDateFunc { //! constructs an ICU timestamp, and then converts that back to a DuckDB instant //! Adding offset doesn't really work around DST because the bin values are ambiguous static timestamp_tz_t FromNaive(Calendar *calendar, timestamp_t naive); - //! Non-throwing form of FromNaive - sets the error and returns false if the result is out of range - static bool TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error); //! Truncates the calendar time to the given part precision typedef void (*part_trunc_t)(Calendar *calendar, uint64_t µs); diff --git a/src/duckdb/extension/json/json_functions/json_create.cpp b/src/duckdb/extension/json/json_functions/json_create.cpp index 6117f0b89..4eb1fa82c 100644 --- a/src/duckdb/extension/json/json_functions/json_create.cpp +++ b/src/duckdb/extension/json/json_functions/json_create.cpp @@ -80,9 +80,9 @@ enum class JSONGeometryFormat : uint8_t { WKT, GEOJSON }; struct JSONCopyFormatOptions { JSONGeometryFormat geometry_format = JSONGeometryFormat::WKT; - mutable optional_ptr date_format; - mutable optional_ptr timestamp_format; - mutable optional_ptr context; + optional_ptr date_format; + optional_ptr timestamp_format; + optional_ptr context; optional_ptr timestamptz_format_expression; optional_ptr timestamptz_ns_format_expression; }; @@ -862,7 +862,7 @@ static void CreateValuesFormatted(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V static void CreateValuesDate(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.date_format), [&](Vector &string_vector) { - options.date_format->ConvertDateVector(value_v, string_vector); + options.date_format.get_mutable()->ConvertDateVector(value_v, string_vector); }); } @@ -875,14 +875,14 @@ static void CreateValuesTimestamp(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V return; } CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format->ConvertTimestampVector(value_v, string_vector); + options.timestamp_format.get_mutable()->ConvertTimestampVector(value_v, string_vector); }); } static void CreateValuesTimestampNS(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format->ConvertTimestampNSVector(value_v, string_vector); + options.timestamp_format.get_mutable()->ConvertTimestampNSVector(value_v, string_vector); }); } @@ -892,7 +892,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], // no COPY format was given - use the full cast set so that the session time zone is applied Vector string_vector(LogicalType::VARCHAR, count); if (options.context) { - VectorOperations::Cast(*options.context, value_v, string_vector, count, true); + VectorOperations::Cast(*options.context.get_mutable(), value_v, string_vector, count, true); } else { VectorOperations::DefaultCast(value_v, string_vector, count); } @@ -910,7 +910,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], input.InitializeEmpty({value_v.GetType()}); input.data[0].Reference(value_v); input.CheckCardinality(count); - ExpressionExecutor executor(*options.context, *format_expression); + ExpressionExecutor executor(*options.context.get_mutable(), *format_expression); executor.ExecuteExpression(input, string_vector); }); } @@ -1516,8 +1516,6 @@ ScalarFunction JSONFunctions::GetJSONCopyToJSONFunction() { fun.GetSignature().GetParameter(1).SetName("date_format"); fun.GetSignature().GetParameter(2).SetName("timestamp_format"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); - // formatting dates and timestamps can fail - fun.SetFallible(); fun.SetSerializeCallback(JSONCopyToJSONSerialize); fun.SetDeserializeCallback(JSONCopyToJSONDeserialize); return fun; @@ -1536,7 +1534,6 @@ ScalarFunction JSONFunctions::GetJSONCopyToGeoJSONFunction() { fun.GetSignature().GetParameter(4).SetName("id_column"); fun.GetSignature().GetParameter(5).SetName("write_bbox"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); - fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/json/json_functions/json_transform.cpp b/src/duckdb/extension/json/json_functions/json_transform.cpp index ffa43977e..4c08be33d 100644 --- a/src/duckdb/extension/json/json_functions/json_transform.cpp +++ b/src/duckdb/extension/json/json_functions/json_transform.cpp @@ -11,7 +11,6 @@ #include "duckdb/common/error_data.hpp" #include "duckdb/common/serializer/deserializer.hpp" #include "duckdb/common/types/geometry.hpp" -#include "duckdb/common/types/value_map.hpp" #include "json_geojson.hpp" #include "duckdb/common/serializer/serializer.hpp" #include "duckdb/common/types.hpp" @@ -45,8 +44,7 @@ static LogicalType StructureStringToTypeArray(yyjson_val *arr, ClientContext &co } static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context) { - // STRUCT field names are case-insensitive - identifier_set_t names; + unordered_set names; child_list_t child_types; size_t idx, max; yyjson_val *key, *val; @@ -56,9 +54,10 @@ static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context if (key_str.empty()) { throw BinderException("Empty key in object in JSON structure"); } - if (!names.insert(Identifier(key_str)).second) { + if (names.find(key_str) != names.end()) { JSONCommon::ThrowValFormatError("Duplicate keys in object in JSON structure: %s", val); } + names.insert(key_str); child_types.emplace_back(key_str, StructureStringToType(val, context)); } D_ASSERT(yyjson_obj_size(obj) == names.size()); @@ -923,38 +922,6 @@ static bool TransformObjectToMap(yyjson_val *objects[], yyjson_alc *alc, Vector success = false; } - // objects can have duplicate keys, and keys that fail to transform in non-strict mode are NULL - auto &map_keys = MapVector::GetKeys(result); - auto key_validity = map_keys.Validity(); - value_set_t unique_keys; - for (idx_t i = 0; i < count; i++) { - if (!list_validity.RowIsValid(i)) { - continue; - } - const auto &list_entry = list_entries[i]; - const char *invalid_reason = nullptr; - unique_keys.clear(); - for (idx_t key_idx = list_entry.offset; key_idx < list_entry.offset + list_entry.length; key_idx++) { - if (!key_validity.IsValid(key_idx)) { - invalid_reason = "Map keys can not be NULL"; - break; - } - if (!unique_keys.insert(map_keys.GetValue(key_idx)).second) { - invalid_reason = "Map keys must be unique"; - break; - } - } - if (!invalid_reason) { - continue; - } - list_validity.SetInvalid(i); - if (success && options.strict_cast) { - options.error_message = invalid_reason; - options.object_index = i; - success = false; - } - } - if (!options.delay_error && !success) { throw InvalidInputException(options.error_message); } diff --git a/src/duckdb/extension/json/json_reader.cpp b/src/duckdb/extension/json/json_reader.cpp index 1ecd229b1..8e32310db 100644 --- a/src/duckdb/extension/json/json_reader.cpp +++ b/src/duckdb/extension/json/json_reader.cpp @@ -771,15 +771,7 @@ bool JSONReader::ParseJSON(JSONReaderScanState &scan_state, char *const json_sta // If we return strings, we cannot parse INSITU. If we ignore errors, we cannot parse INSITU either: a malformed // document could extend into (and modify) the next one const bool ignorable_errors = options.ignore_errors && GetFormat() == JSONFormat::NEWLINE_DELIMITED; - // A number that does not fit is read as a raw string, which INSITU parsing null-terminates in place - at the root - // that overwrites the delimiter that follows the document - idx_t first_char = 0; - while (first_char < json_size && StringUtil::CharacterIsSpace(json_start[first_char])) { - first_char++; - } - const bool number_root = first_char < json_size && - (StringUtil::CharacterIsDigit(json_start[first_char]) || json_start[first_char] == '-'); - if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors || number_root) { + if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors) { doc = JSONCommon::ReadDocumentUnsafe(json_start, json_size, JSONCommon::READ_STOP_FLAG, scan_state.allocator.GetYYAlc(), &err); } else { diff --git a/src/duckdb/extension/parquet/column_reader.cpp b/src/duckdb/extension/parquet/column_reader.cpp index 54519dfc2..453be1314 100644 --- a/src/duckdb/extension/parquet/column_reader.cpp +++ b/src/duckdb/extension/parquet/column_reader.cpp @@ -250,9 +250,6 @@ void ColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_row chunk = &columns[ColumnIndex()]; protocol = &protocol_p; D_ASSERT(chunk); - if (chunk->__isset.crypto_metadata && !reader.metadata->crypto_metadata) { - throw InvalidInputException("File is encrypted but no file crypto metadata is set"); - } D_ASSERT(chunk->__isset.meta_data); if (chunk->__isset.file_path) { @@ -519,6 +516,9 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { uint32_t compressed_page_size = page_hdr.compressed_page_size; if (chunk->__isset.crypto_metadata) { + if (!reader.metadata->crypto_metadata) { + throw InvalidInputException("File is encrypted but no file crypto metadata is set"); + } auto const file_aad = reader.GetUniqueFileIdentifier(reader.metadata->crypto_metadata->encryption_algorithm); if (!file_aad.empty()) { // If there is a file aad (identifier), this means that the Encrypted file is written by Arrow @@ -550,50 +550,6 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { page_hdr.uncompressed_page_size); } -static bool TryDecompressLZ4Hadoop(const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { - // Hadoop framing stores a big-endian uncompressed block size, followed by one or more - // chunks, each prefixed by its big-endian compressed size. - while (src_size > 0) { - if (src_size < sizeof(uint32_t)) { - return false; - } - auto block_size = BSwapIfLE(Load(src)); - src += sizeof(uint32_t); - src_size -= sizeof(uint32_t); - if (block_size > dst_size) { - return false; - } - if (block_size == 0) { - // Hadoop uses a zero-sized block to terminate an empty stream. - return src_size == 0 && dst_size == 0; - } - idx_t remaining = block_size; - while (remaining > 0) { - if (src_size < sizeof(uint32_t)) { - return false; - } - auto compressed_size = BSwapIfLE(Load(src)); - src += sizeof(uint32_t); - src_size -= sizeof(uint32_t); - if (compressed_size == 0 || compressed_size > src_size) { - return false; - } - auto decompressed_size = - duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), - NumericCast(compressed_size), NumericCast(remaining)); - if (decompressed_size <= 0) { - return false; - } - src += compressed_size; - src_size -= compressed_size; - dst += decompressed_size; - dst_size -= decompressed_size; - remaining -= decompressed_size; - } - } - return dst_size == 0; -} - void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { switch (codec) { @@ -604,12 +560,6 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p s.Decompress(const_char_ptr_cast(src), src_size, char_ptr_cast(dst), dst_size); break; } - case CompressionCodec::LZ4: - if (TryDecompressLZ4Hadoop(src, src_size, dst, dst_size)) { - break; - } - // Early parquet-cpp writers used raw LZ4 under the legacy codec identifier. - DUCKDB_EXPLICIT_FALLTHROUGH; case CompressionCodec::LZ4_RAW: { auto res = duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), @@ -669,7 +619,7 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p duckdb::stringstream codec_name; codec_name << codec; throw InvalidInputException("Failed to read file \"%s\": Unsupported compression codec \"%s\". Supported " - "options are uncompressed, brotli, gzip, lz4, lz4_raw, snappy or zstd", + "options are uncompressed, brotli, gzip, lz4_raw, snappy or zstd", Reader().GetFileName(), codec_name.str()); } // LCOV_EXCL_STOP } diff --git a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp index 74da3df82..8c54b9243 100644 --- a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp +++ b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp @@ -45,6 +45,35 @@ bool IsContinuationByte(uint8_t b) { return (b & 0xC0) == 0x80; } +//! Whether a string starts or ends in the middle of a multi-byte character. These are the only strings that can be +//! invalid on their own when the batch they are part of is valid UTF-8 +bool CutsUtf8Character(const char *data, uint32_t len) { + if (len == 0) { + return false; + } + const auto bytes = const_data_ptr_cast(data); + if (IsContinuationByte(bytes[0])) { + return true; + } + // find the first byte of the last character - a character is at most 4 bytes + idx_t last_start = len - 1; + while (last_start > 0 && len - last_start < 4 && IsContinuationByte(bytes[last_start])) { + last_start--; + } + const auto first_byte = bytes[last_start]; + idx_t char_length; + if (first_byte < 0x80) { + char_length = 1; + } else if (first_byte >= 0xF0) { + char_length = 4; + } else if (first_byte >= 0xE0) { + char_length = 3; + } else { + char_length = 2; + } + return len - last_start != char_length; +} + void DeltaLengthByteArrayDecoder::Read(shared_ptr &block_ref, uint8_t *defines, idx_t read_count, Vector &result, idx_t result_offset) { if (defines) { @@ -80,7 +109,6 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo StringColumnReader::Utf8ValidationOption::STRICT_UTF8; const auto start_ptr = block.ptr; - auto current_ptr = start_ptr; auto result_data = FlatVector::Writer(result, read_count, result_offset); for (idx_t row_idx = 0; row_idx < read_count; row_idx++) { @@ -97,23 +125,20 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo length_idx, byte_array_count); } } - const auto str_len = length_data[length_idx++]; + const auto &str_len = length_data[length_idx++]; if (needs_full_individual_validation) { - auto verified = string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); + auto verified = string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); result_data.WriteValue(verified); } else { - // Batch validation below catches incomplete trailing characters. Within a valid batch, a character - // can only cross a string boundary if the next nonempty string starts with a continuation byte. - if (str_len > 0 && IsContinuationByte(current_ptr[0])) { - string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); + if (CutsUtf8Character(char_ptr_cast(block.ptr), str_len)) { + string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); } - result_data.WriteStringRef(string_t(char_ptr_cast(current_ptr), str_len)); + result_data.WriteStringRef(string_t(char_ptr_cast(block.ptr), str_len)); } - current_ptr += str_len; + block.unsafe_inc(str_len); } - block.unsafe_inc(current_ptr - start_ptr); if (!needs_full_individual_validation) { string_column_reader.VerifyString(char_ptr_cast(start_ptr), NumericCast(block.ptr - start_ptr)); } diff --git a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp index 6f71072e3..775160215 100644 --- a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp +++ b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp @@ -27,7 +27,7 @@ class DbpDecoder { miniblock_index(number_of_miniblocks_per_block - 1), list_of_bitwidths_of_miniblocks(nullptr), miniblock_offset(number_of_values_in_a_miniblock), unpacked_data_offset(BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE) { - if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && number_of_values_in_a_miniblock > 0 && + if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && number_of_values_in_a_miniblock % BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE == 0)) { throw InvalidInputException("Parquet file has invalid block sizes for DELTA_BINARY_PACKED"); } @@ -63,9 +63,6 @@ class DbpDecoder { if (miniblock_offset == number_of_values_in_a_miniblock) { return; } - if (miniblock_offset > number_of_values_in_a_miniblock) { - throw InvalidInputException("Parquet file has invalid miniblocks for DELTA_BINARY_PACKED"); - } auto data = make_unsafe_uniq_array(number_of_values_in_a_miniblock); GetBatchInternal(data_ptr_cast(data.get()), number_of_values_in_a_miniblock - miniblock_offset); } diff --git a/src/duckdb/extension/parquet/include/parquet_reader.hpp b/src/duckdb/extension/parquet/include/parquet_reader.hpp index b7af194e4..479ce7fb6 100644 --- a/src/duckdb/extension/parquet/include/parquet_reader.hpp +++ b/src/duckdb/extension/parquet/include/parquet_reader.hpp @@ -475,9 +475,6 @@ class ParquetReader : public BaseFileReader { idx_t GetGroupOffset(ParquetReaderScanState &state); //! Group span is the distance between the min page offset and the max page offset plus the max page compressed size uint64_t GetGroupSpan(ParquetReaderScanState &state); - FilterPropagateResult CheckRowGroupFilterStatistics(ClientContext &context, ParquetReaderScanState &state, - ColumnReader &column_reader, const TableFilter &filter); - bool DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state); void PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t out_col_idx); //! Whole-group prefetch strategy. ParquetPrefetchStrategy WholeGroupPrefetch(ParquetReaderScanState &state, ThriftFileTransport &trans, diff --git a/src/duckdb/extension/parquet/parquet_extension.cpp b/src/duckdb/extension/parquet/parquet_extension.cpp index fc01e0def..f14943469 100644 --- a/src/duckdb/extension/parquet/parquet_extension.cpp +++ b/src/duckdb/extension/parquet/parquet_extension.cpp @@ -283,9 +283,6 @@ static unique_ptr ParquetWriteBind(ClientContext &context, CopyFun for (idx_t i = 0; i < values.size(); i++) { auto &value = values[i]; auto key = StructType::GetChildName(kv_struct_type, i); - if (value.IsNull()) { - throw BinderException("kv_metadata value for key %s cannot be NULL", key); - } // If the value is a blob, write the raw blob bytes // otherwise, cast to string if (value.type().id() == LogicalTypeId::BLOB) { diff --git a/src/duckdb/extension/parquet/parquet_metadata.cpp b/src/duckdb/extension/parquet/parquet_metadata.cpp index 3d8b00473..cb1da7910 100644 --- a/src/duckdb/extension/parquet/parquet_metadata.cpp +++ b/src/duckdb/extension/parquet/parquet_metadata.cpp @@ -566,12 +566,8 @@ void ParquetRowGroupMetadataProcessor::ReadRow(vector> &output class ParquetSchemaProcessor : public ParquetMetadataFileProcessor { public: - void InitializeInternal(ClientContext &context, ParquetReader &reader) override; idx_t TotalRowCount(ParquetReader &reader) override; void ReadRow(vector> &output, idx_t row_idx, ParquetReader &reader) override; - -private: - vector element_types; }; template <> @@ -672,25 +668,6 @@ static Value ParquetLogicalTypeToString(const duckdb_parquet::LogicalType &type, return Value(); } -static void CollectElementTypes(const ParquetColumnSchema &schema, vector &element_types) { - // a group collapsed into its only child has the type of that child - for (auto &index : {schema.parent_schema_index, schema.schema_index}) { - if (index.IsValid() && index.GetIndex() < element_types.size() && element_types[index.GetIndex()].IsNull()) { - element_types[index.GetIndex()] = Value(schema.type.ToString()); - } - } - for (auto &child : schema.children) { - CollectElementTypes(child, element_types); - } -} - -void ParquetSchemaProcessor::InitializeInternal(ClientContext &context, ParquetReader &reader) { - element_types.assign(reader.GetFileMetadata()->schema.size(), Value()); - for (auto &column : reader.root_schema->children) { - CollectElementTypes(column, element_types); - } -} - idx_t ParquetSchemaProcessor::TotalRowCount(ParquetReader &reader) { return reader.GetFileMetadata()->schema.size(); } @@ -722,7 +699,12 @@ void ParquetSchemaProcessor::ReadRow(vector> &output, idx_t ro // logical_type output[10].get().Append(ParquetLogicalTypeToString(column.logicalType, column.__isset.logicalType)); // duckdb_type - output[11].get().Append(element_types[row_idx]); + ParquetColumnSchema column_schema; + Value duckdb_type; + if (column.__isset.type) { + duckdb_type = reader.DeriveLogicalType(column, column_schema).ToString(); + } + output[11].get().Append(duckdb_type); // column_id output[12].get().Append(Value::BIGINT(UnsafeNumericCast(row_idx))); } diff --git a/src/duckdb/extension/parquet/parquet_reader.cpp b/src/duckdb/extension/parquet/parquet_reader.cpp index ef68bc96d..808bce83c 100644 --- a/src/duckdb/extension/parquet/parquet_reader.cpp +++ b/src/duckdb/extension/parquet/parquet_reader.cpp @@ -593,10 +593,6 @@ LogicalType ParquetReader::DeriveLogicalType(const SchemaElement &s_ele, const P throw IOException("TIME_MICROS converted type can only be set for value of Type::INT64"); } case ConvertedType::INTERVAL: - if (s_ele.type != Type::FIXED_LEN_BYTE_ARRAY || s_ele.type_length != 12) { - throw DataCorruptionException( - "INTERVAL converted type can only be set for value of Type::FIXED_LEN_BYTE_ARRAY of length 12"); - } return LogicalType::INTERVAL; case ConvertedType::JSON: return LogicalType::JSON(); @@ -1045,20 +1041,11 @@ static bool IsVariantType(const SchemaElement &root, const vector MAX_PARQUET_SCHEMA_DEPTH) { - throw InvalidInputException( - "Malformed Parquet schema in file \"%s\": the schema is nested more than %llu levels " - "deep", - file.path, MAX_PARQUET_SCHEMA_DEPTH); - } if (next_schema_idx >= file_meta_data->schema.size()) { throw InvalidInputException("Malformed Parquet schema in file \"%s\": invalid schema index %d", file.path, next_schema_idx); @@ -1158,24 +1145,6 @@ ParquetColumnSchema ParquetReader::ParseSchemaRecursive(idx_t depth, idx_t max_d if (!is_variant && parquet_options.variant_legacy_encoding && IsVariantType(s_ele, child_schemas)) { is_variant = true; } - if (is_variant) { - // the VARIANT reader reads the 'metadata' and 'value' columns as binary - for (auto &child_schema : child_schemas) { - if (child_schema.name != "metadata" && child_schema.name != "value") { - continue; - } - // a logical type annotation (e.g. GEOMETRY) would make the column reader produce another type - const auto is_binary = - child_schema.type.id() == LogicalTypeId::BLOB || child_schema.type.id() == LogicalTypeId::VARCHAR; - if (!child_schema.children.empty() || child_schema.parquet_type != duckdb_parquet::Type::BYTE_ARRAY || - !is_binary || child_schema.schema_type != ParquetColumnSchemaType::COLUMN) { - throw DataCorruptionException( - "Failed to read Parquet file \"%s\": the '%s' column of VARIANT column \"%s\" " - "must be a binary BYTE_ARRAY column", - file.path, child_schema.name, s_ele.name); - } - } - } if (!is_map_kv && this_idx > 0) { // check if the parent node of this is a map @@ -1801,55 +1770,6 @@ ColumnReader &ParquetReaderScanState::GetColumnReader(idx_t i) { return *column_readers[i]; } -FilterPropagateResult ParquetReader::CheckRowGroupFilterStatistics(ClientContext &context, - ParquetReaderScanState &state, - ColumnReader &column_reader, - const TableFilter &filter) { - auto &group = GetGroup(state); - auto schema_column_index = column_reader.ColumnIndex(); - auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; - bool is_generated_column = schema_column_index >= group.columns.size(); - bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; - auto stats = column_reader.Stats(state.group_index, group.columns); - if (stats) { - bool has_min_max = false; - if (!is_generated_column) { - has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && - group.columns[schema_column_index].meta_data.statistics.__isset.max_value; - } - if (!is_expression && !is_generated_column && has_min_max && - (column_reader.Type().id() == LogicalTypeId::FLOAT || column_reader.Type().id() == LogicalTypeId::DOUBLE) && - ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, - parquet_options.can_have_nan)) { - // floating point columns can have NaN values in addition to the min/max bounds defined in the file - // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning - // based on nan - prune_result = CheckParquetFloatFilter(context, column_reader, - group.columns[schema_column_index].meta_data.statistics, filter); - } else { - auto &expr_filter = - ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::CheckRowGroupFilterStatistics"); - prune_result = expr_filter.CheckStatistics(context, *stats); - } - } - return prune_result; -} - -bool ParquetReader::DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state) { - for (auto &scan_filter : state.scan_filters) { - if (!ExpressionFilter::GetOptionalDynamicFilterData(scan_filter.filter)) { - continue; - } - auto &column_reader = state.GetColumnReader(MultiFileLocalIndex(scan_filter.filter_idx)); - auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, scan_filter.filter); - if (prune_result == FilterPropagateResult::FILTER_ALWAYS_FALSE || - prune_result == FilterPropagateResult::FILTER_FALSE_OR_NULL) { - return true; - } - } - return false; -} - void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t i) { auto &group = GetGroup(state); auto col_idx = MultiFileLocalIndex(i); @@ -1874,7 +1794,33 @@ void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderS if (filter_entry) { auto &filter = *filter_entry; - auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, filter); + auto schema_column_index = column_reader.ColumnIndex(); + auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; + bool is_generated_column = schema_column_index >= group.columns.size(); + bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; + auto stats = column_reader.Stats(state.group_index, group.columns); + if (stats) { + bool has_min_max = false; + if (!is_generated_column) { + has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && + group.columns[schema_column_index].meta_data.statistics.__isset.max_value; + } + if (!is_expression && !is_generated_column && has_min_max && + (column_reader.Type().id() == LogicalTypeId::FLOAT || + column_reader.Type().id() == LogicalTypeId::DOUBLE) && + ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, + parquet_options.can_have_nan)) { + // floating point columns can have NaN values in addition to the min/max bounds defined in the file + // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning + // based on nan + prune_result = CheckParquetFloatFilter( + context, column_reader, group.columns[schema_column_index].meta_data.statistics, filter); + } else { + auto &expr_filter = + ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::PrepareRowGroupBuffer"); + prune_result = expr_filter.CheckStatistics(context, *stats); + } + } // check the bloom filter if present auto bloom_source = TryGetBloomFilterSource(column_reader); if (bloom_source) { @@ -2499,11 +2445,6 @@ AsyncResult ParquetReader::Process(ClientContext &context, ParquetReaderScanStat const bool log_prefetch = Logger::Get(context).ShouldLog(ParquetPrefetchLogType::NAME, ParquetPrefetchLogType::LEVEL); const idx_t group_num_rows = GetGroup(state).num_rows; - // Queued row groups may predate the current dynamic filter. Do not revisit a partially decoded chunk. - if (state.offset_in_group == 0 && !state.resuming_payload && DynamicFiltersExcludeRowGroup(context, state)) { - rows_read += group_num_rows; - state.offset_in_group = group_num_rows; - } auto scan_count = MinValue(STANDARD_VECTOR_SIZE, group_num_rows - state.offset_in_group); if (!state.resuming_payload) { result.SetChildCardinality(scan_count); diff --git a/src/duckdb/extension/parquet/parquet_shredding.cpp b/src/duckdb/extension/parquet/parquet_shredding.cpp index 903a03020..aacbacab7 100644 --- a/src/duckdb/extension/parquet/parquet_shredding.cpp +++ b/src/duckdb/extension/parquet/parquet_shredding.cpp @@ -72,11 +72,6 @@ static ShreddingType ConvertShreddingTypeRecursive(const LogicalType &type) { return ShreddingType(LogicalType(LogicalTypeId::ANY)); } if (!type.IsNested()) { - // the values are written without further conversion or validation, so types with an alias (e.g., JSON) and - // types without a Parquet typed_value representation cannot be shredded on - if (type.HasAlias() || type.id() == LogicalTypeId::BIT || type.id() == LogicalTypeId::BIGNUM) { - throw BinderException("VARIANT cannot be shredded on type %s in Parquet", type.ToString()); - } return ShreddingType(type); } diff --git a/src/duckdb/extension/parquet/read_single_parquet_file.cpp b/src/duckdb/extension/parquet/read_single_parquet_file.cpp index d88450a5c..2739185de 100644 --- a/src/duckdb/extension/parquet/read_single_parquet_file.cpp +++ b/src/duckdb/extension/parquet/read_single_parquet_file.cpp @@ -37,20 +37,6 @@ struct ReadSingleParquetFileData : public TableFunctionData { } } - unique_ptr Copy() const override { - // the copy does not take over the reader the bind opened - its scans open the file themselves - auto result = make_uniq(); - result->file = file; - result->options = options; - result->parquet_names = parquet_names; - result->parquet_types = parquet_types; - result->columns = columns; - result->metadata = metadata; - result->cardinality = cardinality; - result->projection_expressions = projection_expressions; - return std::move(result); - } - //! The reader the bind opened for this file, handed to the first scan of this bind data so that the file does //! not have to be opened again. Only set when the bind data is used to read the file shared_ptr TakeBindReader() const { @@ -411,12 +397,6 @@ static virtual_column_map_t ReadSingleParquetFileVirtualColumns(ClientContext &c return result; } -//! The row number within the file identifies a row of a single file -static vector ReadSingleParquetFileRowIdColumns(ClientContext &context, - optional_ptr bind_data) { - return {MultiFileReader::COLUMN_IDENTIFIER_FILE_ROW_NUMBER}; -} - //! The statistics of a column of this file, read from its metadata static unique_ptr ReadSingleParquetFileStatistics(ClientContext &context, const FunctionData *bind_data_p, column_t column_index) { @@ -550,7 +530,6 @@ TableFunction ParquetScanFunction::GetSingleFileFunction() { read_parquet.get_metrics = ReadSingleParquetFileGetMetrics; read_parquet.projection_pushdown = true; read_parquet.late_materialization = true; - read_parquet.get_row_id_columns = ReadSingleParquetFileRowIdColumns; read_parquet.filter_pushdown = true; read_parquet.filter_prune = true; ParquetScanFunction::AddNamedParameters(read_parquet); diff --git a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp index e7579b4ff..3679ee03b 100644 --- a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp @@ -31,15 +31,6 @@ RowNumberColumnReader::RowNumberColumnReader(const ParquetReader &reader, const void RowNumberColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_rows, const vector &columns, TProtocol &protocol_p) { - // the row numbers are produced from the row count without reading a column chunk - every column chunk has a value - // (or NULL) per row, so it cannot have fewer values than the row group has rows - for (auto &column : columns) { - if (column.__isset.meta_data && column.meta_data.num_values < NumericCast(row_group_num_rows)) { - throw DataCorruptionException( - "Failed to read file \"%s\": metadata is corrupt. Column has %lld values but row group has %llu rows", - reader.GetFileName(), column.meta_data.num_values, row_group_num_rows); - } - } row_group_offset = 0; auto &row_groups = reader.GetFileMetadata()->row_groups; for (idx_t i = 0; i < row_group_idx_p; i++) { diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index b2956eb82..1654161a0 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48256"; + descriptor->api_version = "v2.0.0-alpha47887"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48256"; + descriptor->api_version = "v2.0.0-alpha47887"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48256"; + descriptor->api_version = "v2.0.0-alpha47887"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48256"; + descriptor->api_version = "v2.0.0-alpha47887"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp index a3233f0e5..fbccd7aea 100644 --- a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp @@ -281,13 +281,6 @@ unique_ptr DuckTableEntry::GetSample() { return storage->GetSample(); } -//! The index of the column name in a column reference of a table expression - the names before it qualify the table, -//! the names after it are struct fields -static idx_t ColumnNameIndex(const ColumnRefExpression &colref, const Identifier &table_name) { - auto &names = colref.ColumnNames(); - return names.size() > 1 && names[0] == table_name ? 1 : 0; -} - unique_ptr DuckTableEntry::AlterEntry(CatalogTransaction transaction, AlterInfo &info) { if (transaction.HasContext()) { return AlterEntry(transaction.GetContext(), info); @@ -336,19 +329,6 @@ unique_ptr DuckTableEntry::AlterEntry(ClientContext &context, Alte auto &rename_info = table_info.Cast(); auto copied_table = Copy(context); copied_table->name = rename_info.new_table_name; - // CHECK constraints can qualify columns with the table name - for (auto &constraint : copied_table->Cast().constraints) { - if (constraint->type != ConstraintType::CHECK) { - continue; - } - auto &check = constraint->Cast(); - ParsedExpressionIterator::VisitExpressionMutable( - *check.expression, [&](ColumnRefExpression &colref) { - if (ColumnNameIndex(colref, name) == 1) { - colref.ColumnNamesMutable()[0] = rename_info.new_table_name; - } - }); - } return copied_table; } case AlterTableType::ADD_COLUMN: { @@ -423,11 +403,10 @@ void DuckTableEntry::UndoAlter(ClientContext &context, AlterInfo &info) { } } -static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info, const Identifier &table_name) { +static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info) { ParsedExpressionIterator::VisitExpressionMutable(root_expr, [&](ColumnRefExpression &colref) { - auto column_idx = ColumnNameIndex(colref, table_name); - if (colref.ColumnNames()[column_idx] == info.old_name) { - colref.ColumnNamesMutable()[column_idx] = info.new_name; + if (colref.ColumnNames().back() == info.old_name) { + colref.ColumnNamesMutable().back() = info.new_name; } }); } @@ -470,7 +449,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re copy.SetName(info.new_name); } if (col.Generated() && column_dependency_manager.IsDependencyOf(col.Logical(), rename_idx)) { - RenameExpression(copy.GeneratedExpressionMutable(), info, name); + RenameExpression(copy.GeneratedExpressionMutable(), info); } create_info->columns.AddColumn(std::move(copy)); } @@ -483,7 +462,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re case ConstraintType::CHECK: { // CHECK constraint: need to rename column references that refer to the renamed column auto &check = copy->Cast(); - RenameExpression(*check.expression, info, name); + RenameExpression(*check.expression, info); break; } case ConstraintType::UNIQUE: { @@ -881,9 +860,12 @@ unique_ptr DuckTableEntry::RemoveColumn(ClientContext &context, Re if (!removed_columns.empty() && !info.cascade) { throw CatalogException("Cannot drop column: column is a dependency of 1 or more generated column(s)"); } - bool dropped_column_is_generated = columns.GetColumn(LogicalIndex(removed_index)).Generated(); + bool dropped_column_is_generated = false; for (auto &col : columns.Logical()) { if (col.Logical() == removed_index || removed_columns.count(col.Logical())) { + if (col.Generated()) { + dropped_column_is_generated = true; + } continue; } create_info->columns.AddColumn(col.Copy()); diff --git a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp index 95c870b57..fec16d5d2 100644 --- a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp @@ -6,7 +6,6 @@ #include "duckdb/parser/parsed_data/create_sequence_info.hpp" #include "duckdb/catalog/dependency_manager.hpp" #include "duckdb/common/operator/add.hpp" -#include "duckdb/common/operator/subtract.hpp" #include "duckdb/transaction/duck_transaction.hpp" #include @@ -114,22 +113,6 @@ void SequenceCatalogEntry::ReplayValue(uint64_t v_usage_count, int64_t v_counter } } -void SequenceCatalogEntry::DeriveLastValue() { - lock_guard seqlock(lock); - if (data.last_value || data.usage_count == 0) { - return; - } - int64_t last_value; - if (!TrySubtractOperator::Operation(data.counter, data.increment, last_value)) { - return; - } - // a counter that wrapped around in a cycle does not tell us the last value - if (last_value < data.min_value || last_value > data.max_value) { - return; - } - data.last_value = last_value; -} - unique_ptr SequenceCatalogEntry::GetInfo() const { auto seq_data = GetData(); diff --git a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp index c146cfb41..74e25516b 100644 --- a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp +++ b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp @@ -75,23 +75,6 @@ optional_idx Allocator::DecayDelay() { return NumericCast(DUCKDB_JEMALLOC_DECAY); } -optional_idx Allocator::GetMappedMemory() const { - if (allocate_function != DefaultAllocate || free_function != DefaultFree || - reallocate_function != DefaultReallocate) { - return optional_idx(); - } - uint64_t epoch = 1; - if (duckdb_je_mallctl("epoch", nullptr, nullptr, &epoch, sizeof(epoch)) != 0) { - return optional_idx(); - } - size_t mapped = 0; - size_t mapped_size = sizeof(mapped); - if (duckdb_je_mallctl("stats.mapped", &mapped, &mapped_size, nullptr, 0) != 0) { - return optional_idx(); - } - return optional_idx(mapped); -} - bool Allocator::SupportsFlush() { return true; } diff --git a/src/duckdb/src/common/allocator/allocator_standard.cpp b/src/duckdb/src/common/allocator/allocator_standard.cpp index d74816ed1..da6a959c8 100644 --- a/src/duckdb/src/common/allocator/allocator_standard.cpp +++ b/src/duckdb/src/common/allocator/allocator_standard.cpp @@ -37,10 +37,6 @@ optional_idx Allocator::DecayDelay() { return optional_idx(); } -optional_idx Allocator::GetMappedMemory() const { - return optional_idx(); -} - bool Allocator::SupportsFlush() { #if defined(__GLIBC__) return true; diff --git a/src/duckdb/src/common/bind_helpers.cpp b/src/duckdb/src/common/bind_helpers.cpp index 74bfde8da..c20b8bbe2 100644 --- a/src/duckdb/src/common/bind_helpers.cpp +++ b/src/duckdb/src/common/bind_helpers.cpp @@ -9,7 +9,6 @@ #include "duckdb/planner/binder.hpp" #include "duckdb/planner/bound_result_modifier.hpp" #include "duckdb/planner/expression_iterator.hpp" -#include "duckdb/planner/expression_binder/relation_binder.hpp" #include "duckdb/planner/expression/bound_reference_expression.hpp" #include "duckdb/main/config.hpp" @@ -140,12 +139,10 @@ vector BindOrderByNodes(Binder &binder, TableIndex table_index vector &orders) { auto child_binder = Binder::CreateBinder(binder.context, &binder); child_binder->bind_context.AddGenericBinding(table_index, alias, names, types); - RelationBinder expr_binder(*child_binder, binder.context, "ORDER_BY"); + ExpressionBinder expr_binder(*child_binder, binder.context); vector bound_orders; for (auto &order : orders) { - auto expression = expr_binder.Bind(order.expression); - ExpressionBinder::PushCollation(binder.context, expression, expression->GetReturnType()); - bound_orders.emplace_back(order.type, order.null_order, std::move(expression)); + bound_orders.emplace_back(order.type, order.null_order, expr_binder.Bind(order.expression)); } return bound_orders; } diff --git a/src/duckdb/src/common/enum_util.cpp b/src/duckdb/src/common/enum_util.cpp index 3c57cc018..588815540 100644 --- a/src/duckdb/src/common/enum_util.cpp +++ b/src/duckdb/src/common/enum_util.cpp @@ -163,7 +163,6 @@ #include "duckdb/main/extension.hpp" #include "duckdb/main/extension_helper.hpp" #include "duckdb/main/extension_install_info.hpp" -#include "duckdb/main/http/http_retry_budget.hpp" #include "duckdb/main/http/http_util.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/secret/secret.hpp" @@ -2280,20 +2279,19 @@ const StringUtil::EnumStringLiteral *GetExpressionTailTypeValues() { { static_cast(ExpressionTailType::IS_TEST), "IS_TEST" }, { static_cast(ExpressionTailType::DISTINCT), "DISTINCT" }, { static_cast(ExpressionTailType::COMPARISON), "COMPARISON" }, - { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" }, - { static_cast(ExpressionTailType::CAST), "CAST" } + { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" } }; return values; } template<> const char* EnumUtil::ToChars(ExpressionTailType value) { - return StringUtil::EnumToString(GetExpressionTailTypeValues(), 5, "ExpressionTailType", static_cast(value)); + return StringUtil::EnumToString(GetExpressionTailTypeValues(), 4, "ExpressionTailType", static_cast(value)); } template<> ExpressionTailType EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 5, "ExpressionTailType", value)); + return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 4, "ExpressionTailType", value)); } const StringUtil::EnumStringLiteral *GetExpressionTypeValues() { @@ -2783,20 +2781,19 @@ const StringUtil::EnumStringLiteral *GetFunctionCollationHandlingValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(FunctionCollationHandling::PROPAGATE_COLLATIONS), "PROPAGATE_COLLATIONS" }, { static_cast(FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS), "PUSH_COMBINABLE_COLLATIONS" }, - { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" }, - { static_cast(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS), "PUSH_DISTINCT_COLLATIONS" } + { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" } }; return values; } template<> const char* EnumUtil::ToChars(FunctionCollationHandling value) { - return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", static_cast(value)); + return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", static_cast(value)); } template<> FunctionCollationHandling EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", value)); + return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", value)); } const StringUtil::EnumStringLiteral *GetFunctionErrorsValues() { @@ -3040,24 +3037,6 @@ HTTPClientCachePolicy EnumUtil::FromString(const char *va return static_cast(StringUtil::StringToEnum(GetHTTPClientCachePolicyValues(), 2, "HTTPClientCachePolicy", value)); } -const StringUtil::EnumStringLiteral *GetHTTPRetryOutcomeOwnerValues() { - static constexpr StringUtil::EnumStringLiteral values[] { - { static_cast(HTTPRetryOutcomeOwner::STATUS), "STATUS" }, - { static_cast(HTTPRetryOutcomeOwner::COORDINATOR), "COORDINATOR" } - }; - return values; -} - -template<> -const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value) { - return StringUtil::EnumToString(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", static_cast(value)); -} - -template<> -HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", value)); -} - const StringUtil::EnumStringLiteral *GetHTTPStatusCodeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(HTTPStatusCode::INVALID), "INVALID" }, @@ -6513,20 +6492,19 @@ const StringUtil::EnumStringLiteral *GetSuggestionStateValues() { { static_cast(SuggestionState::SUGGEST_TABLE_FUNCTION_NAME), "SUGGEST_TABLE_FUNCTION_NAME" }, { static_cast(SuggestionState::SUGGEST_PRAGMA_NAME), "SUGGEST_PRAGMA_NAME" }, { static_cast(SuggestionState::SUGGEST_SETTING_NAME), "SUGGEST_SETTING_NAME" }, - { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" }, - { static_cast(SuggestionState::SUGGEST_BARE_LABEL), "SUGGEST_BARE_LABEL" } + { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" } }; return values; } template<> const char* EnumUtil::ToChars(SuggestionState value) { - return StringUtil::EnumToString(GetSuggestionStateValues(), 15, "SuggestionState", static_cast(value)); + return StringUtil::EnumToString(GetSuggestionStateValues(), 14, "SuggestionState", static_cast(value)); } template<> SuggestionState EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 15, "SuggestionState", value)); + return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 14, "SuggestionState", value)); } const StringUtil::EnumStringLiteral *GetTableColumnTypeValues() { diff --git a/src/duckdb/src/common/exception.cpp b/src/duckdb/src/common/exception.cpp index 3ca99bdfb..185bfe4f3 100644 --- a/src/duckdb/src/common/exception.cpp +++ b/src/duckdb/src/common/exception.cpp @@ -74,6 +74,7 @@ bool Exception::InvalidatesTransaction(ExceptionType exception_type) { bool Exception::InvalidatesDatabase(ExceptionType exception_type) { switch (exception_type) { + case ExceptionType::DATA_CORRUPTION: case ExceptionType::FATAL: return true; default: diff --git a/src/duckdb/src/common/file_buffer.cpp b/src/duckdb/src/common/file_buffer.cpp index 642adcb5a..f86e3fb40 100644 --- a/src/duckdb/src/common/file_buffer.cpp +++ b/src/duckdb/src/common/file_buffer.cpp @@ -113,12 +113,6 @@ void FileBuffer::Resize(BlockManager &block_manager) { void FileBuffer::Read(QueryContext context, FileHandle &handle, uint64_t location) { D_ASSERT(type != FileBufferType::TINY_BUFFER); - if (handle.file_system.IsLocalFileSystem()) { - // Pre-fault local read buffers before the kernel fills them. - for (idx_t offset = 0; offset < internal_size; offset += 4096) { - internal_buffer[offset] = 0; - } - } handle.Read(context, internal_buffer, internal_size, location); } diff --git a/src/duckdb/src/common/gzip_file_system.cpp b/src/duckdb/src/common/gzip_file_system.cpp index a154be2a1..cfd6eae5b 100644 --- a/src/duckdb/src/common/gzip_file_system.cpp +++ b/src/duckdb/src/common/gzip_file_system.cpp @@ -238,8 +238,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { } state_bytes_remaining = NumericCast((uint8_t)gzip_xlen[0] | (uint8_t)gzip_xlen[1] << 8); if (gzip_header_size + state_bytes_remaining >= GZIP_HEADER_MAXSIZE) { - throw DataCorruptionException("Extra field resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw InternalException("Extra field resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } read_state = GZipReadState::EXTRA_DATA; break; @@ -265,8 +265,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { auto c = *sd.in_buff_start++; gzip_header_size++; if (gzip_header_size >= GZIP_HEADER_MAXSIZE) { - throw DataCorruptionException("Filename resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw InternalException("Filename resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } if (c == '\0') { read_state = GZipReadState::DEFLATE; diff --git a/src/duckdb/src/common/hive_partitioning.cpp b/src/duckdb/src/common/hive_partitioning.cpp index c103ab2e9..b52f1ddf6 100644 --- a/src/duckdb/src/common/hive_partitioning.cpp +++ b/src/duckdb/src/common/hive_partitioning.cpp @@ -29,7 +29,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f auto &column_map = filter_info.column_map; if (filter_info.filename_enabled) { - auto lookup_column_id = column_map.find(filter_info.filename_column); + auto lookup_column_id = column_map.find("filename"); if (lookup_column_id != column_map.end()) { result.insert(make_pair(lookup_column_id->second, PartitioningColumnValue(filename))); } @@ -38,7 +38,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f if (filter_info.hive_enabled) { auto partitions = HivePartitioning::Parse(filename); for (auto &partition : partitions) { - auto lookup_column_id = column_map.find(Identifier(partition.first)); + auto lookup_column_id = column_map.find(partition.first); if (lookup_column_id != column_map.end()) { result.insert( make_pair(lookup_column_id->second, PartitioningColumnValue(partition.first, partition.second))); diff --git a/src/duckdb/src/common/local_file_system.cpp b/src/duckdb/src/common/local_file_system.cpp index 664659e76..87ba23c52 100644 --- a/src/duckdb/src/common/local_file_system.cpp +++ b/src/duckdb/src/common/local_file_system.cpp @@ -1285,12 +1285,7 @@ unique_ptr LocalFileSystem::OpenFile(const string &path_p, FileOpenF HANDLE hFile = CreateFileW(unicode_path.c_str(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL); if (hFile == INVALID_HANDLE_VALUE) { - auto last_error = GetLastError(); - if (flags.ReturnNullIfNotExists() && last_error == ERROR_FILE_NOT_FOUND) { - return nullptr; - } - // Match POSIX EEXIST handling for exclusive creation. - if (flags.ReturnNullIfExists() && (last_error == ERROR_FILE_EXISTS || last_error == ERROR_ALREADY_EXISTS)) { + if (flags.ReturnNullIfNotExists() && GetLastError() == ERROR_FILE_NOT_FOUND) { return nullptr; } auto error = LocalFileSystem::GetLastErrorAsString(); diff --git a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp index af5b3c272..589923fcd 100644 --- a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp @@ -934,51 +934,16 @@ static unique_ptr CreateStructExtractExpression(unique_ptrsecond->index.GetIndex() != i || - !(source_children[i].first == target_children[i].first) || - !MappedCastIsInvertible(*entry->second, source_children[i].second, target_children[i].second)) { - return false; - } - } - return true; -} - -static bool TryCastConstant(Value &constant, const MultiFileIndexMapping &mapping, const LogicalType &target_type) { - // this filter replaces the global one, so both casts must preserve which rows match - if (!BoundCastExpression::CastIsInvertible(constant.type(), target_type) || - !MappedCastIsInvertible(mapping, target_type, constant.type())) { +static bool TryCastConstant(Value &constant, const LogicalType &target_type) { + // this filter replaces the global one in the reader, so a constant that does not survive the cast + // exactly would silently change which rows match + if (!BoundCastExpression::CastIsInvertible(constant.type(), target_type)) { return false; } auto cast = constant.DefaultTryCastAs(target_type); if (!cast) { return false; } - auto roundtrip = cast->DefaultTryCastAs(constant.type()); - if (!roundtrip || *roundtrip != constant) { - return false; - } constant = std::move(*cast); return true; } @@ -1027,14 +992,13 @@ static RewrittenMappedExpression RewriteMappedValueExpression(const Expression & } static unique_ptr RewriteDynamicFilterExpression(const shared_ptr &filter_data, - const MultiFileIndexMapping &mapping, const LogicalType &target_type) { if (!filter_data || !filter_data->initialized) { return nullptr; } - annotated_shared_lock lock(filter_data->lock); + lock_guard lock(filter_data->lock); auto new_constant = filter_data->constant; - if (!TryCastConstant(new_constant, mapping, target_type)) { + if (!TryCastConstant(new_constant, target_type)) { return nullptr; } return BoundComparisonExpression::Create(filter_data->comparison_type, CreateReferenceExpression(target_type), @@ -1067,7 +1031,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto &data = func.BindInfo()->Cast(); - return RewriteDynamicFilterExpression(data.filter_data, mapping, target_type); + return RewriteDynamicFilterExpression(data.filter_data, target_type); } if (!BoundComparisonExpression::IsComparison(expr)) { return nullptr; @@ -1083,7 +1047,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = right.Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.type)) { return nullptr; } return BoundComparisonExpression::Create(comparison.GetExpressionType(), std::move(lhs.expr), @@ -1132,7 +1096,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = op.GetChildren()[i]->Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.type)) { return nullptr; } result->GetChildrenMutable().push_back(make_uniq(std::move(constant))); diff --git a/src/duckdb/src/common/multi_file/multi_file_list.cpp b/src/duckdb/src/common/multi_file/multi_file_list.cpp index 38ecf87cb..cbabe9939 100644 --- a/src/duckdb/src/common/multi_file/multi_file_list.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_list.cpp @@ -30,11 +30,10 @@ bool PushdownInternal(ClientContext &context, const MultiFileOptions &options, M if (IsVirtualColumn(info.column_ids[i])) { continue; } - filter_info.column_map.insert({info.column_names[info.column_ids[i]], i}); + filter_info.column_map.insert({info.column_names[info.column_ids[i]].GetIdentifierName(), i}); } filter_info.hive_enabled = options.hive_partitioning; filter_info.filename_enabled = options.filename; - filter_info.filename_column = Identifier(options.filename_column); auto start_files = expanded_files.size(); HivePartitioning::ApplyFiltersToFileList(context, expanded_files, filters, filter_info, info); diff --git a/src/duckdb/src/common/opener_file_system.cpp b/src/duckdb/src/common/opener_file_system.cpp index 617947828..5c3508a52 100644 --- a/src/duckdb/src/common/opener_file_system.cpp +++ b/src/duckdb/src/common/opener_file_system.cpp @@ -45,7 +45,8 @@ void OpenerFileSystem::VerifyCanAccessFileInternal(const string &path, FileType return; } auto &config = db->config; - if (!config.CanAccessFile(path, type, opener)) { + auto canonical_path = config.file_system->CanonicalizePath(path, opener); + if (!config.CanAccessFile(canonical_path, type)) { throw PermissionException("Cannot access %s \"%s\" - file system operations are disabled by configuration", type == FileType::FILE_TYPE_DIR ? "directory" : "file", path); } diff --git a/src/duckdb/src/common/operator/cast_operators.cpp b/src/duckdb/src/common/operator/cast_operators.cpp index 95d7828e9..c4282dc0c 100644 --- a/src/duckdb/src/common/operator/cast_operators.cpp +++ b/src/duckdb/src/common/operator/cast_operators.cpp @@ -2187,9 +2187,9 @@ struct HugeIntCastData { idx_t digits; ResultType decimal; - idx_t decimal_total_digits; + uint16_t decimal_total_digits; ResultType decimal_intermediate; - idx_t decimal_intermediate_digits; + uint16_t decimal_intermediate_digits; bool Flush() { if (digits == 0 && intermediate == 0) { @@ -2319,7 +2319,7 @@ struct HugeIntegerCastOperation { remainder = negate_result; } state.decimal = remainder; - state.decimal_total_digits = static_cast(-e); + state.decimal_total_digits = static_cast(-e); state.decimal_intermediate = 0; state.decimal_intermediate_digits = 0; return Finalize(state); @@ -2338,19 +2338,16 @@ struct HugeIntegerCastOperation { return Finalize(state); } - const auto shift = static_cast(exponent) - static_cast(state.decimal_total_digits); - if (shift < 0) { - if (shift < -38) { - // all of decimal is fractional - remainder = state.decimal; - state.decimal = 0; - } else { - state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-shift], remainder); + e = exponent - state.decimal_total_digits; + if (e < 0) { + if (e < -38) { + return false; } - state.decimal_total_digits -= static_cast(exponent); + state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-e], remainder); + state.decimal_total_digits -= (exponent); } else { - if (shift > 38 || - !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[shift], state.decimal)) { + if (e > 38 || + !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[e], state.decimal)) { return false; } } @@ -2371,11 +2368,6 @@ struct HugeIntegerCastOperation { if (!state.Flush()) { return false; } - // digits past this position cannot affect the result for any int16_t exponent. Ignore them! - static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); - if (DUCKDB_UNLIKELY(state.decimal_total_digits + state.decimal_intermediate_digits == MAX_DECIMAL_DIGITS)) { - return true; - } if (DUCKDB_UNLIKELY(state.decimal_intermediate > (NumericLimits::Maximum() - digit) / 10)) { if (!state.FlushDecimal()) { return false; @@ -2403,7 +2395,7 @@ struct HugeIntegerCastOperation { state.decimal /= T::Operation::POWERS_OF_TEN[MAX_DIGITS]; state.decimal_total_digits -= MAX_DIGITS; } - D_ASSERT(state.decimal_total_digits >= 1 && state.decimal_total_digits - 1 <= MAX_DIGITS); + D_ASSERT((state.decimal_total_digits - 1) >= 0 && (state.decimal_total_digits - 1) <= MAX_DIGITS); state.decimal /= T::Operation::POWERS_OF_TEN[state.decimal_total_digits - 1]; if (state.decimal >= 5) { @@ -3323,7 +3315,6 @@ static void FillDecimalDigits(SRC input, duckdb_fast_float::decimal &decimal, bo } static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decimal, bool &negative) { - negative = false; if (input == 0) { return; } @@ -3331,6 +3322,8 @@ static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decim if (input < 0) { negative = true; Hugeint::NegateInPlace(input); + } else { + negative = false; } // a hugeint can have one more digit than the maximum decimal width @@ -3353,7 +3346,7 @@ bool TryCastDecimalToFloatingPoint(SRC input, DST &result, uint8_t width, uint8_ } duckdb_fast_float::decimal decimal; - bool negative = false; + bool negative; FillDecimalDigits(input, decimal, negative); decimal.decimal_point = UnsafeNumericCast(decimal.num_digits) - UnsafeNumericCast(scale); while (decimal.num_digits > 0 && decimal.digits[decimal.num_digits - 1] == 0) { diff --git a/src/duckdb/src/common/sort/sort.cpp b/src/duckdb/src/common/sort/sort.cpp index 5bb566837..234e0d94f 100644 --- a/src/duckdb/src/common/sort/sort.cpp +++ b/src/duckdb/src/common/sort/sort.cpp @@ -164,7 +164,7 @@ class SortGlobalSinkState : public GlobalSinkState { } // Double until it fits - auto required = num_threads * lstate.sorted_run->SizeInBytesForSorting(); + auto required = num_threads * lstate.sorted_run->SizeInBytes(); if (is_index_sort) { required *= 4; // Index creation is pretty intense, so we are very conservative here } @@ -225,8 +225,8 @@ unique_ptr Sort::GetGlobalSinkState(ClientContext &context) con //! Returns true if the Sink call is done (either because run size is small or because run was finalized) static bool TryFinishSink(SortGlobalSinkState &gstate, SortLocalSinkState &lstate, unique_lock &guard) { - // Check if we exceed the limit (sorting the run needs a merge buffer on top of the run itself) - const auto sorted_run_size = lstate.sorted_run->SizeInBytesForSorting(); + // Check if we exceed the limit + const auto sorted_run_size = lstate.sorted_run->SizeInBytes(); if (sorted_run_size < lstate.maximum_run_size) { return true; // Sink is done } diff --git a/src/duckdb/src/common/sort/sorted_run.cpp b/src/duckdb/src/common/sort/sorted_run.cpp index f7eab31cf..d72cc9328 100644 --- a/src/duckdb/src/common/sort/sorted_run.cpp +++ b/src/duckdb/src/common/sort/sorted_run.cpp @@ -8,7 +8,6 @@ #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/common/sorting/sort.hpp" #include "duckdb/common/sorting/sort_key.hpp" -#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "vergesort.h" @@ -248,16 +247,10 @@ static void TemplatedSort(ClientContext &context, TupleDataCollection &key_data, auto ska_extract_key = SkaExtractKey(requires_next_sort, ska_sort_width, sort_skippable_bytes, context.interrupt_state); - // vergesort merges the sorted runs it finds with a temporary buffer of up to half the keys - allocate that - // buffer through the buffer allocator instead of letting std::inplace_merge take it from operator new. The same - // buffer serves the merges of the nested vergesort that ska_sort runs over the buckets its radix passes cannot - // order, so that no merge anywhere in this sort allocates on its own (BufferedMerge never does) - SortMergeBuffer merge_buffer(BufferAllocator::Get(context), key_data.Count(), sizeof(SORT_KEY)); - const BufferedMergePolicy merge(merge_buffer); - const auto fallback = [&ska_extract_key, &merge](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { - duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key, merge); + const auto fallback = [ska_extract_key](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { + duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key); }; - duckdb_vergesort::vergesort(begin, end, std::less(), fallback, merge); + duckdb_vergesort::vergesort(begin, end, std::less(), fallback); context.InterruptCheck(); } @@ -454,9 +447,4 @@ idx_t SortedRun::SizeInBytes() const { return size; } -idx_t SortedRun::SizeInBytesForSorting() const { - // the fixed-size sort keys are the rows of the key layout, so the row width is the key size the merge buffer uses - return SizeInBytes() + SortMergeBuffer::MaximumSize(key_data->Count(), key_data->GetLayout().GetRowWidth()); -} - } // namespace duckdb diff --git a/src/duckdb/src/common/sort/sorted_run_merger.cpp b/src/duckdb/src/common/sort/sorted_run_merger.cpp index 1ddd7c6f0..2baecd162 100644 --- a/src/duckdb/src/common/sort/sorted_run_merger.cpp +++ b/src/duckdb/src/common/sort/sorted_run_merger.cpp @@ -6,7 +6,6 @@ #include "duckdb/common/thread_annotation.hpp" #include "duckdb/common/sorting/sorted_run.hpp" #include "duckdb/common/sorting/sort_key.hpp" -#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/parallel/parallel_destroy_task.hpp" @@ -656,12 +655,8 @@ void SortedRunMergerLocalState::TemplatedMergePartition(SortedRunMergerGlobalSta static const auto fallback = [](SORT_KEY *begin, SORT_KEY *end) { duckdb_pdqsort::pdqsort_branchless(begin, end); }; - // the run merges need a scratch buffer of up to half the keys - take it from the buffer allocator so that it is - // counted against the memory limit instead of coming from operator new inside std::inplace_merge - SortMergeBuffer merge_buffer(BufferAllocator::Get(gstate.context), merged_partition_count, sizeof(SORT_KEY)); - const BufferedMergePolicy merge(merge_buffer); duckdb_vergesort::vergesort(merged_partition_keys, merged_partition_keys + merged_partition_count, - std::less(), fallback, merge); + std::less(), fallback); } void SortedRunMergerLocalState::ScanPartition(SortedRunMergerGlobalState &gstate, DataChunk &chunk) { diff --git a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp index b8909899a..f60ab52bd 100644 --- a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp +++ b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp @@ -300,7 +300,7 @@ void HTMLTreeRenderer::RenderProfiler(const QueryProfiler &profiler, BaseTreeRen query_sql = HighlightSQL(FormatSQL(profiler.GetQuerySQL())); query_timings.clear(); for (auto &entry : qm.GetMetricTimings()) { - query_timings.emplace_back(entry.first, static_cast(entry.second.duration) / 1e9); + query_timings.emplace_back(entry.first, static_cast(entry.second) / 1e9); } has_query_metrics = true; profiler.RenderProfilingNodeTree(*this, ss); diff --git a/src/duckdb/src/common/types/bignum.cpp b/src/duckdb/src/common/types/bignum.cpp index 862e959ae..9f4da3623 100644 --- a/src/duckdb/src/common/types/bignum.cpp +++ b/src/duckdb/src/common/types/bignum.cpp @@ -101,8 +101,29 @@ char Bignum::DigitToChar(int digit) { } static bool ShouldRoundDecimalDigits(const char *data, idx_t decimal_start, idx_t decimal_end) { - // the fraction is at least 0.5 exactly when its first digit is at least 5 - return decimal_start < decimal_end && data[decimal_start] >= '5'; + if (decimal_start >= decimal_end) { + return false; + } + uint64_t decimal = 0; + uint16_t decimal_digits = 0; + for (idx_t pos = decimal_start; pos < decimal_end; pos++) { + auto digit = UnsafeNumericCast(data[pos] - '0'); + if (decimal > (NumericLimits::Maximum() - digit) / 10) { + for (; pos < decimal_end; pos++) { + if (data[pos] != '0') { + return true; + } + } + break; + } + decimal_digits++; + decimal = decimal * 10 + digit; + } + while (decimal > 10) { + decimal /= 10; + decimal_digits--; + } + return decimal_digits == 1 && decimal >= 5; } static void IncrementDecimalString(string &digits) { @@ -537,8 +558,11 @@ bool Bignum::BignumToDouble(const bignum_t &blob, double &result, bool &strict) if (is_negative) { result *= -1; } - // the value does not fit in a double - return std::isfinite(result); + if (!std::isfinite(result)) { + // We throw an error + throw ConversionException("Could not convert bignum '%s' to Double", BignumToVarchar(blob)); + } + return true; } } // namespace duckdb diff --git a/src/duckdb/src/common/types/geometry.cpp b/src/duckdb/src/common/types/geometry.cpp index 2e7539f77..6c366b727 100644 --- a/src/duckdb/src/common/types/geometry.cpp +++ b/src/duckdb/src/common/types/geometry.cpp @@ -1018,9 +1018,6 @@ WKBAnalysis AnalyzeWKB(BlobReader &reader) { const auto meta = reader.Read(le); const auto type_id = (meta & 0x0000FFFF) % 1000; const auto flag_id = (meta & 0x0000FFFF) / 1000; - if (flag_id > 3) { - throw InvalidInputException("Unsupported geometry flag %d in WKB", flag_id); - } // Extended WKB detection const auto has_extz = (meta & 0x80000000) != 0; diff --git a/src/duckdb/src/common/types/hugeint.cpp b/src/duckdb/src/common/types/hugeint.cpp index b98611ddb..79a353a6e 100644 --- a/src/duckdb/src/common/types/hugeint.cpp +++ b/src/duckdb/src/common/types/hugeint.cpp @@ -89,6 +89,42 @@ hugeint_t Hugeint::Abs(hugeint_t n) { // Divide //===--------------------------------------------------------------------===// +static uint8_t PositiveHugeintHighestBit(hugeint_t bits) { + uint8_t out = 0; + if (bits.upper) { + out = 64; + uint64_t up = static_cast(bits.upper); + while (up) { + up >>= 1; + out++; + } + } else { + uint64_t low = bits.lower; + while (low) { + low >>= 1; + out++; + } + } + return out; +} + +static bool PositiveHugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { + if (bit_position < 64) { + return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); + } else { + return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); + } +} + +static hugeint_t PositiveHugeintLeftShift(hugeint_t lhs, uint32_t amount) { + D_ASSERT(amount > 0 && amount < 64); + hugeint_t result; + result.lower = lhs.lower << amount; + result.upper = + UnsafeNumericCast((UnsafeNumericCast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); + return result; +} + hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remainder) { D_ASSERT(lhs.upper >= 0); // DivMod code adapted from: @@ -100,14 +136,14 @@ hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remaind div_result.upper = 0; remainder = 0; - uint8_t highest_bit_set = HugeintHighestBit(lhs); + uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); // now iterate over the amount of bits that are set in the LHS for (uint8_t x = highest_bit_set; x > 0; x--) { // left-shift the current result and remainder by 1 - div_result = HugeintLeftShift(div_result, 1); + div_result = PositiveHugeintLeftShift(div_result, 1); remainder <<= 1; // we get the value of the bit at position X, where position 0 is the least-significant bit - if (HugeintIsBitSet(lhs, x - 1)) { + if (PositiveHugeintIsBitSet(lhs, x - 1)) { // increment the remainder remainder++; } @@ -267,18 +303,172 @@ bool Hugeint::TryMultiply(hugeint_t lhs, hugeint_t rhs, hugeint_t &result) { // Multiply without overflow check template <> hugeint_t Hugeint::Multiply(hugeint_t lhs, hugeint_t rhs) { - return HugeintMultiplyUnchecked(lhs, rhs); + hugeint_t result; + bool lhs_negative = lhs.upper < 0; + bool rhs_negative = rhs.upper < 0; + if (lhs_negative) { + NegateInPlace(lhs); + } + if (rhs_negative) { + NegateInPlace(rhs); + } + +#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) + __uint128_t left = __uint128_t(lhs.lower) + (__uint128_t(lhs.upper) << 64); + __uint128_t right = __uint128_t(rhs.lower) + (__uint128_t(rhs.upper) << 64); + __uint128_t result_i128; + result_i128 = left * right; + uint64_t upper = uint64_t(result_i128 >> 64); + result.upper = int64_t(upper); + result.lower = uint64_t(result_i128 & 0xffffffffffffffff); +#else + // Multiply code adapted from: + // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp + + // split values into 4 32-bit parts + uint64_t top[4] = {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, + lhs.lower & 0xffffffff}; + uint64_t bottom[4] = {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, + rhs.lower & 0xffffffff}; + uint64_t products[4][4]; + + // multiply each component of the values + for (auto x = 0; x < 4; x++) { + for (auto y = 0; y < 4; y++) { + products[x][y] = top[x] * bottom[y]; + } + } + + // first row + uint64_t fourth32 = (products[3][3] & 0xffffffff); + uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); + uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); + uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); + + // second row + third32 += (products[2][3] & 0xffffffff); + second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); + first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); + + // third row + second32 += (products[1][3] & 0xffffffff); + first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); + + // fourth row + first32 += (products[0][3] & 0xffffffff); + + // move carry to next digit + third32 += fourth32 >> 32; + second32 += third32 >> 32; + first32 += second32 >> 32; + + // remove carry from current digit + fourth32 &= 0xffffffff; + third32 &= 0xffffffff; + second32 &= 0xffffffff; + first32 &= 0xffffffff; + + // combine components + result.lower = (third32 << 32) | fourth32; + result.upper = (first32 << 32) | second32; +#endif + if (lhs_negative ^ rhs_negative) { + NegateInPlace(result); + } + return result; } //===--------------------------------------------------------------------===// // Divide //===--------------------------------------------------------------------===// +int Sign(hugeint_t n) { + return ((n > 0) - (n < 0)); +} + +hugeint_t Abs(hugeint_t n) { + D_ASSERT(n != NumericLimits::Minimum()); + return (n * Sign(n)); +} + +static hugeint_t DivModMinimum(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { + D_ASSERT(lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()); + if (rhs == NumericLimits::Minimum()) { + if (lhs == NumericLimits::Minimum()) { + remainder = 0; + return 1; + } + remainder = lhs; + return 0; + } + + // Add 1 to minimum and run through DivMod again + hugeint_t result = Hugeint::DivMod(NumericLimits::Minimum() + 1, rhs, remainder); + + // If the 1 mattered we need to adjust the result, otherwise the remainder + if (Abs(remainder) + 1 == Abs(rhs)) { + result -= Sign(rhs); + remainder = 0; + } else { + remainder -= 1; + } + return result; +} + // No overflow checks hugeint_t Hugeint::DivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { - auto result = HugeintDivMod(lhs, rhs); - remainder = result.remainder; - return result.quotient; + if (rhs == 0) { + remainder = lhs; + return hugeint_t(0); + } + + // Check if one of the sides is hugeint_t minimum, as that can't be negated. + if (lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()) { + return DivModMinimum(lhs, rhs, remainder); + } + + bool lhs_negative = lhs.upper < 0; + bool rhs_negative = rhs.upper < 0; + if (lhs_negative) { + Hugeint::NegateInPlace(lhs); + } + if (rhs_negative) { + Hugeint::NegateInPlace(rhs); + } + // DivMod code adapted from: + // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp + + // initialize the result and remainder to 0 + hugeint_t div_result; + div_result.lower = 0; + div_result.upper = 0; + remainder.lower = 0; + remainder.upper = 0; + + uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); + // now iterate over the amount of bits that are set in the LHS + for (uint8_t x = highest_bit_set; x > 0; x--) { + // left-shift the current result and remainder by 1 + div_result = PositiveHugeintLeftShift(div_result, 1); + remainder = PositiveHugeintLeftShift(remainder, 1); + + // we get the value of the bit at position X, where position 0 is the least-significant bit + if (PositiveHugeintIsBitSet(lhs, x - 1)) { + remainder += 1; + } + if (Hugeint::GreaterThanEquals(remainder, rhs)) { + // the remainder has passed the division multiplier: add one to the divide result + remainder -= rhs; + div_result += 1; + } + } + if (lhs_negative ^ rhs_negative) { + Hugeint::NegateInPlace(div_result); + } + if (lhs_negative) { + Hugeint::NegateInPlace(remainder); + } + return div_result; } bool Hugeint::TryDivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &result, hugeint_t &remainder) { @@ -304,7 +494,9 @@ hugeint_t Hugeint::Divide(hugeint_t lhs, hugeint_t rhs) { template <> hugeint_t Hugeint::Modulo(hugeint_t lhs, hugeint_t rhs) { - return HugeintDivMod(lhs, rhs).remainder; + hugeint_t remainder; + (void)Hugeint::DivMod(lhs, rhs, remainder); + return remainder; } //===--------------------------------------------------------------------===// @@ -581,11 +773,62 @@ bool Hugeint::TryConvert(long double value, hugeint_t &result) { //===--------------------------------------------------------------------===// // hugeint_t operators //===--------------------------------------------------------------------===// +hugeint_t::hugeint_t(int64_t value) { + auto result = Hugeint::Convert(value); + this->lower = result.lower; + this->upper = result.upper; +} + +bool hugeint_t::operator==(const hugeint_t &rhs) const { + return Hugeint::Equals(*this, rhs); +} + +bool hugeint_t::operator!=(const hugeint_t &rhs) const { + return Hugeint::NotEquals(*this, rhs); +} + +bool hugeint_t::operator<(const hugeint_t &rhs) const { + return Hugeint::LessThan(*this, rhs); +} + +bool hugeint_t::operator<=(const hugeint_t &rhs) const { + return Hugeint::LessThanEquals(*this, rhs); +} + +bool hugeint_t::operator>(const hugeint_t &rhs) const { + return Hugeint::GreaterThan(*this, rhs); +} + +bool hugeint_t::operator>=(const hugeint_t &rhs) const { + return Hugeint::GreaterThanEquals(*this, rhs); +} + +hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { + return hugeint_t(upper + rhs.upper + ((lower + rhs.lower) < lower), lower + rhs.lower); +} + +hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { + return hugeint_t(upper - rhs.upper - ((lower - rhs.lower) > lower), lower - rhs.lower); +} + +hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { + hugeint_t result = *this; + result *= rhs; + return result; +} + +hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { + return Hugeint::Divide(*this, rhs); +} hugeint_t hugeint_t::operator%(const hugeint_t &rhs) const { return Hugeint::Modulo(*this, rhs); } +hugeint_t hugeint_t::operator-() const { + return Hugeint::Negate(*this); +} + hugeint_t hugeint_t::operator>>(const hugeint_t &rhs) const { hugeint_t result; uint64_t shift = rhs.lower; diff --git a/src/duckdb/src/common/types/interval.cpp b/src/duckdb/src/common/types/interval.cpp index f208bf137..be943a4de 100644 --- a/src/duckdb/src/common/types/interval.cpp +++ b/src/duckdb/src/common/types/interval.cpp @@ -167,10 +167,10 @@ interval_parse_time : { // parse the remainder of the time as a Time type dtime_t time; idx_t pos; - if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time, false, nullptr, negative)) { + if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time)) { return false; } - if (!IntervalTryAddition(result.micros, time.value, 1, error_message)) { + if (!IntervalTryAddition(result.micros, negative ? -time.value : time.value, 1, error_message)) { return false; } found_any = true; diff --git a/src/duckdb/src/common/types/row/tuple_data_collection.cpp b/src/duckdb/src/common/types/row/tuple_data_collection.cpp index 19a682334..3a34bcf12 100644 --- a/src/duckdb/src/common/types/row/tuple_data_collection.cpp +++ b/src/duckdb/src/common/types/row/tuple_data_collection.cpp @@ -757,14 +757,7 @@ void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state, TupleDa } void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state) { - if (segments.empty()) { - // The collection was Reset() while an append state still referred to it, which happens when an exception - // (e.g. out-of-memory) unwinds a Repartition after it moved this partition's rows and before the owner - // re-initialised its append states. There is no segment left to store the handles in, so release them. - pin_state.row_handles.clear(); - pin_state.heap_handles.clear(); - return; - } + D_ASSERT(!segments.empty()); FinalizePinState(pin_state, *segments.back()); } diff --git a/src/duckdb/src/common/types/time.cpp b/src/duckdb/src/common/types/time.cpp index 04396b7a1..34ebec740 100644 --- a/src/duckdb/src/common/types/time.cpp +++ b/src/duckdb/src/common/types/time.cpp @@ -5,9 +5,7 @@ #include "duckdb/common/types/date.hpp" #include "duckdb/common/types/interval.hpp" #include "duckdb/common/types/timestamp.hpp" -#include "duckdb/common/operator/add.hpp" #include "duckdb/common/operator/multiply.hpp" -#include "duckdb/common/operator/subtract.hpp" #include "duckdb/common/exception/conversion_exception.hpp" #include @@ -21,9 +19,8 @@ static_assert(sizeof(dtime_t) == sizeof(int64_t), "dtime_t was padded"); // ISO 8601 bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos, bool negative) { - int64_t hour = -1; - int32_t min = -1, sec = -1, micros = -1; + optional_ptr nanos) { + int32_t hour = -1, min = -1, sec = -1, micros = -1; pos = 0; if (len == 0) { @@ -45,9 +42,9 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r return false; } - // Allow up to 10 digit interval hours + // Allow up to 9 digit hours to support intervals hour = 0; - for (int32_t digits = 10; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { + for (int32_t digits = 9; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { if (digits-- > 0) { hour = hour * 10 + (buf[pos] - '0'); } else { @@ -136,25 +133,13 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r } } - // a negative interval accumulates below zero, so its minimum fits - int64_t time_micros; - auto sub_hour_micros = Time::FromTime(0, min, sec, micros).value; - if (negative) { - if (!TryMultiplyOperator::Operation(hour, -Interval::MICROS_PER_HOUR, time_micros) || - !TrySubtractOperator::Operation(time_micros, sub_hour_micros, time_micros)) { - return false; - } - } else if (!TryMultiplyOperator::Operation(hour, Interval::MICROS_PER_HOUR, time_micros) || - !TryAddOperator::Operation(time_micros, sub_hour_micros, time_micros)) { - return false; - } - result = dtime_t(time_micros); + result = Time::FromTime(hour, min, sec, micros); return true; } bool Time::TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos, bool negative) { - return Time::TryConvertInternal(buf, len, pos, result, strict, nanos, negative); + optional_ptr nanos) { + return Time::TryConvertInternal(buf, len, pos, result, strict, nanos); } bool Time::TryConvertTime(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, diff --git a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp index d85fc87dd..7a372334d 100644 --- a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp +++ b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp @@ -623,13 +623,6 @@ ParquetArrayIterator ParquetVariantNode::GetArrayChildren() const { void ParquetObjectIterator::Finalize() { std::sort(ordered_entries.begin(), ordered_entries.end(), [](const ParquetObjectEntry &a, const ParquetObjectEntry &b) { return a.key < b.key; }); - // the keys of a Variant object have to be unique - for (idx_t i = 1; i < ordered_entries.size(); i++) { - if (ordered_entries[i].key == ordered_entries[i - 1].key) { - throw InvalidInputException("Invalid VARIANT: object contains the key \"%s\" more than once", - ordered_entries[i].key.GetString()); - } - } } ParquetObjectIterator::ParquetObjectIterator(const ParquetVariantIterator &state, const ShreddedGroupView &view, diff --git a/src/duckdb/src/common/types/vector_buffer.cpp b/src/duckdb/src/common/types/vector_buffer.cpp index 751027350..957712872 100644 --- a/src/duckdb/src/common/types/vector_buffer.cpp +++ b/src/duckdb/src/common/types/vector_buffer.cpp @@ -338,17 +338,18 @@ void VectorBuffer::Copy(const Vector &source_p, const SelectionVector &source_se finished = true; break; default: { - // for exotic types we flatten followed by copying - only the copied rows are flattened, as e.g. list - // functions copy the children of every list at a different offset, and the selection vector might only - // cover the copied rows - D_ASSERT(source_count >= source_offset + copy_count); - SelectionVector range_sel(copy_count); - for (idx_t i = 0; i < copy_count; i++) { + // for exotic types we flatten followed by copying - only the copied range [source_offset, source_count) + // is flattened, as e.g. list functions copy the children of every list at a different offset + D_ASSERT(source_count >= source_offset); + const auto range_count = source_count - source_offset; + SelectionVector range_sel(range_count); + for (idx_t i = 0; i < range_count; i++) { range_sel.set_index(i, sel.get_index(source_offset + i)); } Vector flattened_vector(Vector::Ref(source)); - flattened_vector.Flatten(range_sel, copy_count); - Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), copy_count, 0, target_offset, copy_count); + flattened_vector.Flatten(range_sel, range_count); + Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), range_count, 0, target_offset, + copy_count); return; } } diff --git a/src/duckdb/src/common/vector/constant_vector.cpp b/src/duckdb/src/common/vector/constant_vector.cpp index 706d3830e..1343fde9d 100644 --- a/src/duckdb/src/common/vector/constant_vector.cpp +++ b/src/duckdb/src/common/vector/constant_vector.cpp @@ -34,60 +34,22 @@ void ConstantVector::Reference(Vector &vector, const Value &value, count_t count vector.SetBuffer(CreateConstantBuffer(value, count)); } -static bool IsReusableBufferType(VectorBufferType buffer_type) { - switch (buffer_type) { - case VectorBufferType::STANDARD_BUFFER: - case VectorBufferType::STRUCT_BUFFER: - case VectorBufferType::ARRAY_BUFFER: - case VectorBufferType::LIST_BUFFER: - case VectorBufferType::STRING_BUFFER: - return true; - default: - return false; - } -} - -//! Whether the buffer of the vector can be turned into a NULL constant in place: it must not be shared with other -//! vectors (e.g. a referenced payload), and the children that are nulled as well must be flat or constant -static bool CanSetNullInPlace(const Vector &vector) { - auto &buffer_ref = vector.GetBufferRef(); - if (!buffer_ref || buffer_ref.use_count() > 1) { - return false; - } - switch (buffer_ref->GetBufferType()) { - case VectorBufferType::STANDARD_BUFFER: - case VectorBufferType::LIST_BUFFER: - case VectorBufferType::STRING_BUFFER: - return true; - case VectorBufferType::STRUCT_BUFFER: - for (auto &entry : StructVector::GetEntries(vector)) { - if (!CanSetNullInPlace(entry)) { - return false; - } - } - return true; - case VectorBufferType::ARRAY_BUFFER: { - auto child_type = ArrayVector::GetChild(vector).GetVectorType(); - return child_type == VectorType::FLAT_VECTOR || child_type == VectorType::CONSTANT_VECTOR; - } - default: - return false; - } -} - void ConstantVector::SetNull(Vector &vector, count_t count) { + // try to re-use the buffer if possible auto &buffer_ref = vector.GetBufferRef(); - if (!buffer_ref || !IsReusableBufferType(buffer_ref->GetBufferType())) { + bool needs_new_buffer = !buffer_ref; + if (!needs_new_buffer) { + auto buffer_type = buffer_ref->GetBufferType(); + needs_new_buffer = + (buffer_type != VectorBufferType::STANDARD_BUFFER && buffer_type != VectorBufferType::STRUCT_BUFFER && + buffer_type != VectorBufferType::ARRAY_BUFFER && buffer_type != VectorBufferType::LIST_BUFFER && + buffer_type != VectorBufferType::STRING_BUFFER); + } + if (needs_new_buffer) { // we cannot re-use the buffer - refer a null-value which has code necessary to create a new buffer Reference(vector, Value(vector.GetType()), count); return; } - if (!CanSetNullInPlace(vector)) { - // the buffer is shared with other vectors (or has children that cannot be nulled) - use a fresh buffer - auto capacity = MaxValue(buffer_ref->Capacity(), 1); - vector.Initialize(VectorDataInitialization::UNINITIALIZED, capacity); - } - // re-use the buffer, so that e.g. the reserved capacity of a list child is kept vector.SetVectorType(VectorType::CONSTANT_VECTOR); FlatVector::SetSize(vector, count); SetNull(vector, true); diff --git a/src/duckdb/src/common/vector/list_vector.cpp b/src/duckdb/src/common/vector/list_vector.cpp index 124911264..127125e54 100644 --- a/src/duckdb/src/common/vector/list_vector.cpp +++ b/src/duckdb/src/common/vector/list_vector.cpp @@ -329,7 +329,7 @@ void ListVector::SetListSize(Vector &vec, idx_t size) { if (child_buffer) { auto vtype = child_buffer->GetVectorType(); if ((vtype == VectorType::FLAT_VECTOR || vtype == VectorType::CONSTANT_VECTOR) && - size > child_buffer->Capacity() && size > child_buffer->Size()) { + size > child_buffer->Capacity()) { list_buffer.Reserve(size); } } diff --git a/src/duckdb/src/execution/expression_executor/execute_function.cpp b/src/duckdb/src/execution/expression_executor/execute_function.cpp index ef1fe0bc2..7481fe125 100644 --- a/src/duckdb/src/execution/expression_executor/execute_function.cpp +++ b/src/duckdb/src/execution/expression_executor/execute_function.cpp @@ -186,29 +186,6 @@ static idx_t SelectBooleanResult(Vector &result, const SelectionVector *sel, idx result, sel, count, [](bool value) { return value; }, true_sel, false_sel); } -static void SetConstantArgumentCardinality(DataChunk &arguments, vector> &buffers) { - buffers.clear(); - for (auto &arg : arguments.data) { - buffers.push_back(arg.GetBufferRef()); - } - arguments.SetChildCardinality(1ULL); -} - -static void RestoreConstantArgumentCardinality(DataChunk &arguments, vector> &buffers, - idx_t count) { - for (auto &arg : arguments.data) { - arg.SetVectorType(VectorType::CONSTANT_VECTOR); - } - arguments.SetChildCardinality(count); - // the function can have replaced an argument's buffer (e.g. by flattening it) - for (auto &buffer : buffers) { - if (buffer) { - buffer->SetVectorSize(count); - } - } - buffers.clear(); -} - static void ExecuteConstantSelectFunction(const BoundFunctionExpression &expr, DataChunk &args, ExpressionState &state, Vector &result) { D_ASSERT(args.size() == 1); @@ -245,15 +222,15 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } } - auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant temporarily set the child cardinality to 1 - SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); + arguments.SetChildCardinality(1ULL); } else { arguments.SetChildCardinality(count); } arguments.Verify(context); + auto &execute_function_state = state->Cast(); auto dictionary_executed = expr.Function().HasFunctionCallback() && !all_constant && execute_function_state.TryExecuteDictionaryExpression(expr, arguments, *state, result); if (!dictionary_executed) { @@ -266,7 +243,11 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } if (all_constant) { - RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); + // restore the input cardinality + for (auto &arg : arguments.data) { + arg.SetVectorType(VectorType::CONSTANT_VECTOR); + } + arguments.SetChildCardinality(count); // ensure the result type is constant result.FlattenAndSetConstant(); } @@ -300,10 +281,9 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression return SelectBooleanResult(result, sel, count, true_sel, false_sel); } } - auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant we only need to run the function on one value - SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); + arguments.SetChildCardinality(1ULL); } else { arguments.SetChildCardinality(count); } @@ -316,9 +296,14 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression } else { ExecuteConstantSelectFunction(expr, arguments, *state, result); } - RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); + // restore the input cardinality + for (auto &arg : arguments.data) { + arg.SetVectorType(VectorType::CONSTANT_VECTOR); + } + arguments.SetChildCardinality(count); return SelectBooleanResult(result, sel, count, true_sel, false_sel); } + auto &execute_function_state = state->Cast(); if (expr.Function().HasFunctionCallback()) { Vector result(LogicalType::BOOLEAN); auto dictionary_executed = diff --git a/src/duckdb/src/execution/index/fixed_size_buffer.cpp b/src/duckdb/src/execution/index/fixed_size_buffer.cpp index 457413209..a1f4483ea 100644 --- a/src/duckdb/src/execution/index/fixed_size_buffer.cpp +++ b/src/duckdb/src/execution/index/fixed_size_buffer.cpp @@ -77,11 +77,7 @@ FixedSizeBuffer::~FixedSizeBuffer() { } if (OnDisk()) { // marking a block as modified decreases the reference count of multi-use blocks - // this must not throw out of a destructor, e.g., if the block ids of a corrupt file overlap - try { - block_manager.MarkBlockAsModified(block_pointer.block_id); - } catch (...) { // NOLINT - } + block_manager.MarkBlockAsModified(block_pointer.block_id); } } diff --git a/src/duckdb/src/execution/join_hashtable.cpp b/src/duckdb/src/execution/join_hashtable.cpp index 493e0f084..bd90a7982 100644 --- a/src/duckdb/src/execution/join_hashtable.cpp +++ b/src/duckdb/src/execution/join_hashtable.cpp @@ -174,9 +174,9 @@ void JoinHashTable::Merge(JoinHashTable &other) { } } - // every thread-local table shares this operator's filter - a different one here would mean its rows - // never reached the filter that gets published - D_ASSERT(!other.bloom_filter || bloom_filter.get() == other.bloom_filter.get()); + if (bloom_filter.IsInitialized() && other.bloom_filter.IsInitialized()) { + bloom_filter.Merge(other.bloom_filter); + } sink_collection->Combine(*other.sink_collection); @@ -715,8 +715,8 @@ void JoinHashTable::Build(PartitionedTupleDataAppendState &append_state, DataChu // hash the keys and obtain an entry in the list // note that we only hash the keys used in the equality comparison Hash(keys, *current_sel, added_count, hash_values); - if (bloom_filter) { - bloom_filter->InsertHashes(hash_values, *current_sel, added_count); + if (bloom_filter.IsInitialized()) { + bloom_filter.InsertHashes(hash_values); } // Re-reference and ToUnifiedFormat the hash column after computing it @@ -1003,9 +1003,9 @@ static void InsertHashesLoop(unsafe_optional_ptr> entries, Ve void JoinHashTable::InsertHashes(Vector &hashes_v, TupleDataChunkState &chunk_state, InsertState &insert_state, bool parallel) { - // a sealed filter was accumulated during Sink and is already complete; only a finalize-built one fills here - if (bloom_filter && !bloom_filter->IsSealed()) { - bloom_filter->InsertHashes(hashes_v); + // Insert Hashes into the BF + if (bloom_filter.IsInitialized()) { + bloom_filter.InsertHashes(hashes_v); } auto atomic_entries = GetAtomicEntries(); auto &row_locations = chunk_state.row_locations; @@ -1072,10 +1072,9 @@ void JoinHashTable::AllocatePointerTable() { throw InternalException("Hashtable capacity exceeds 48-bit limit (2^48 - 1)"); } - if (should_build_bloom_filter && (!bloom_filter || bloom_filter->IsSealed())) { - // (re)create the finalize-built filter now that the actual build count is known - bloom_filter = make_shared_ptr(); - bloom_filter->Initialize(context, MaxValue(Count(), 1)); + if (should_build_bloom_filter && !bloom_filter.IsInitialized()) { + bloom_filter_init_count = MaxValue(Count(), 1); + bloom_filter.Initialize(context, bloom_filter_init_count); } if (hash_map.get()) { @@ -1101,6 +1100,28 @@ void JoinHashTable::AllocatePointerTable() { {"size", to_string(data_collection->SizeInBytes() + hash_map.GetSize())}}); } +void JoinHashTable::PrepareBloomFilterForFinalize() { + if (!should_build_bloom_filter) { + return; + } + + // Finalize scans every build tuple and inserts its hash into the bloom filter. + // Make sure any existing filter has enough sectors for the actual build count. + const auto build_count = Count(); + const auto actual_init_count = MaxValue(build_count, 1); + if (bloom_filter.IsInitialized()) { + const auto current_sectors = BloomFilter::GetNumberOfSectors(bloom_filter_init_count); + const auto required_sectors = BloomFilter::GetNumberOfSectors(actual_init_count); + if (current_sectors >= required_sectors) { + return; + } + } + + bloom_filter.Reset(); + bloom_filter_init_count = actual_init_count; + bloom_filter.Initialize(context, bloom_filter_init_count); +} + void JoinHashTable::InitializePointerTable(idx_t entry_idx_from, idx_t entry_idx_to) { // initialize HT with all-zero entries auto entries = GetEntries(); @@ -2578,8 +2599,9 @@ void JoinHashTable::ResetForNewIterationSinglePartition() { chains_longer_than_one.store(false, std::memory_order_relaxed); total_probe_matches = 0; load_factor = DEFAULT_LOAD_FACTOR; - bloom_filter.reset(); should_build_bloom_filter = false; + bloom_filter.Reset(); + bloom_filter_init_count = 0; prefix_range_filter.reset(); should_build_prefix_range_filter = false; ResetMarkJoinInfo(*this); diff --git a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp index 687f3fb43..3edca9ab2 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp @@ -518,9 +518,7 @@ class WindowGlobalSourceState : public GlobalSourceState { return !stopped && finished < total_tasks; } //! Get the next task given the current state - bool TryNextTask(TaskPtr &task, Task &task_local) DUCKDB_EXCLUDES(lock); - //! Select a task with the lock held, after the previous task has been retired. - bool TryNextTaskLocked(TaskPtr &task, Task &task_local) DUCKDB_REQUIRES(lock); + bool TryNextTask(TaskPtr &task, Task &task_local); //! Context for executing computations ClientContext &client; @@ -1051,10 +1049,7 @@ bool WindowGlobalSourceState::TryNextTask(TaskPtr &task, Task &task_local) { WindowHashGroupPtr finished_group; annotated_lock_guard guard(lock); finished_group = FinishTask(task); - return TryNextTaskLocked(task, task_local); -} -bool WindowGlobalSourceState::TryNextTaskLocked(TaskPtr &task, Task &task_local) { if (!HasMoreTasks()) { task = nullptr; return false; @@ -1299,15 +1294,11 @@ SourceResultType PhysicalWindow::GetDataInternal(ExecutionContext &context, Data // no more tasks - exit gsource.UnblockTasks(); break; + } else { + // there are more tasks available, but we can't execute them yet + // block the source + return gsource.BlockSource(source.interrupt_state); } - // TryAssignTask released the lock: another thread may have prepared the next stage and unblocked the - // blocked tasks in between, so look again before blocking, or no one would ever unblock this task. - if (gsource.TryNextTaskLocked(lsource.task, lsource.task_local)) { - continue; - } - // there are more tasks available, but we can't execute them yet - // block the source - return gsource.BlockSource(source.interrupt_state); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp index 96773a018..dfec932ad 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp @@ -673,7 +673,7 @@ void StringValueResult::AddValue(StringValueResult &result, const idx_t buffer_p AddQuotedValue(result, buffer_pos - result.extra_delimiter_bytes); } else if (result.escaped) { AddPossiblyEscapedValue(result, buffer_pos, result.buffer_ptr + result.last_position.buffer_pos, - result.HandleMultiDelimiter(buffer_pos), false); + buffer_pos - result.last_position.buffer_pos, false); } else { result.AddValueToVector(result.buffer_ptr + result.last_position.buffer_pos, result.HandleMultiDelimiter(buffer_pos)); @@ -1536,22 +1536,12 @@ void StringValueScanner::ProcessOverBufferValue() { } if (!skip_value) { string_t value; - const auto &delimiter = result.state_machine.dialect_options.state_machine_options.delimiter.GetValue(); - const idx_t extra_delimiter_bytes = states.IsDelimiter() && !delimiter.empty() ? delimiter.size() - 1 : 0; - bool removed_extra_delimiter_bytes = false; if (result.quoted && !result.comment) { idx_t length = over_buffer_string.size() - 1 - result.quoted_position; while (length > 0 && result.ignore_empty_values && over_buffer_string.c_str()[result.quoted_position + length] == ' ') { length--; } - // remove the extra delimiter bytes before removing escapes, which can change the length of the value - if (extra_delimiter_bytes > length) { - throw InternalException( - "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); - } - length -= extra_delimiter_bytes; - removed_extra_delimiter_bytes = true; value = string_t(over_buffer_string.c_str() + result.quoted_position, UnsafeNumericCast(length)); if (result.escaped) { if (result.cur_col_id >= result.number_of_columns && @@ -1576,11 +1566,12 @@ void StringValueScanner::ProcessOverBufferValue() { } return; } - value = RemoveEscape( - str_ptr, length, state_machine->dialect_options.state_machine_options.escape.GetValue(), - state_machine->dialect_options.state_machine_options.quote.GetValue(), - result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), - result.parse_chunk.data[result.chunk_col_id]); + value = + RemoveEscape(str_ptr, over_buffer_string.size() - 2, + state_machine->dialect_options.state_machine_options.escape.GetValue(), + state_machine->dialect_options.state_machine_options.quote.GetValue(), + result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), + result.parse_chunk.data[result.chunk_col_id]); } } } else { @@ -1620,7 +1611,10 @@ void StringValueScanner::ProcessOverBufferValue() { result.EmptyLine(result, iterator.pos.buffer_pos); } else if (!states.IsNotSet() && (!result.comment || !value.Empty())) { idx_t value_size = value.GetSize(); - if (!removed_extra_delimiter_bytes) { + if (states.IsDelimiter() && + !result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().empty()) { + idx_t extra_delimiter_bytes = + result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().size() - 1; if (extra_delimiter_bytes > value_size) { throw InternalException( "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); @@ -1764,16 +1758,12 @@ bool StringValueScanner::FirstValueEndsOnQuote(CSVIterator iterator) const { CSVStates current_state; current_state.Initialize(CSVState::STANDARD); const idx_t to_pos = iterator.GetEndPos(); - const auto quote = state_machine->dialect_options.state_machine_options.quote.GetValue(); - const auto start_pos = iterator.pos.buffer_pos; while (iterator.pos.buffer_pos < to_pos) { state_machine->Transition(current_state, buffer_handle_ptr[iterator.pos.buffer_pos++]); if (current_state.IsState(CSVState::DELIMITER) || current_state.IsState(CSVState::CARRIAGE_RETURN) || current_state.IsState(CSVState::RECORD_SEPARATOR)) { - // a value that also starts with a quote is a regular quoted value - const auto value_end = iterator.pos.buffer_pos - 2; - return value_end > start_pos && buffer_handle_ptr[value_end] == quote && - buffer_handle_ptr[start_pos] != quote; + return buffer_handle_ptr[iterator.pos.buffer_pos - 2] == + state_machine->dialect_options.state_machine_options.quote.GetValue(); } } return false; diff --git a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp index 907cb14a6..9330822ee 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp @@ -123,9 +123,6 @@ static unique_ptr ReadSingleCSVFileBind(ClientContext &context, Ta throw BinderException("read_single_csv_file requires a non-NULL file name"); } result->file = TableFunctionFileBindInput::GetFile(input); - if (result->file.path.empty()) { - throw BinderException("read_single_csv_file requires a non-empty file name"); - } // the options of the scan this file is part of steer the sniffer - the file list is this single file MultiFileOptions file_options; diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp index 160f15832..02de6895c 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp @@ -680,9 +680,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::STRUCT) { throw BinderException("read_csv columns requires a struct as input"); } - if (val.IsNull()) { - throw BinderException("read_csv columns cannot be NULL"); - } auto &struct_children = StructValue::GetChildren(val); D_ASSERT(StructType::GetChildCount(child_type) == struct_children.size()); @@ -697,9 +694,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv requires a type specification as string"); } - if (val.IsNull()) { - throw BinderException("read_csv columns cannot have a NULL type"); - } parsed_types_per_column[name] = i; parsed_types.emplace_back(TransformStringToLogicalType(StringValue::Get(val), context)); } @@ -725,9 +719,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::LIST) { throw BinderException("read_csv auto_types requires a list as input"); } - if (val.IsNull()) { - throw BinderException("auto_type_candidates cannot be NULL"); - } auto &list_children = ListValue::GetChildren(val); if (list_children.empty()) { throw BinderException("auto_type_candidates requires at least one type"); @@ -736,9 +727,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("auto_type_candidates requires a type specification as string"); } - if (child.IsNull()) { - throw BinderException("auto_type_candidates cannot contain NULL types"); - } auto candidate_type = TransformStringToLogicalType(StringValue::Get(child), context); candidate_types[GetCandidateSpecificity(candidate_type)] = candidate_type; } @@ -784,9 +772,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (!columns_set && !sql_type_list.empty()) { throw BinderException("read_csv column_types/types/dtypes can only be supplied once"); } - if (val.IsNull()) { - throw BinderException("read_csv %s cannot be NULL", key); - } // Parse into temporary lists first vector sql_type_names; @@ -800,9 +785,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv %s requires a type specification as string", key); } - if (val.IsNull()) { - throw BinderException("read_csv %s cannot have a NULL type", key); - } sql_type_names.push_back(StringValue::Get(val)); parsed_types_per_column[name] = i; } @@ -813,9 +795,6 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key } auto &children = ListValue::GetChildren(val); for (auto &child : children) { - if (child.IsNull()) { - throw BinderException("read_csv %s cannot have a NULL type", key); - } sql_type_names.push_back(StringValue::Get(child)); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp index f2f27d572..09c2ea4c2 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp @@ -30,7 +30,7 @@ void ThreadLines::Verify() const { continue; } if (last_end_pos + error_margin < line_info.second.start_pos || - line_info.second.start_pos + error_margin < last_end_pos) { + line_info.second.start_pos < last_end_pos - error_margin) { std::ostringstream error; error << "The Parallel CSV Reader currently does not support a full read on this file." << '\n'; error << "To correctly parse this file, please run with the single threaded error (i.e., parallel = " diff --git a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp index 286cc8b16..81df617a6 100644 --- a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp @@ -1089,16 +1089,14 @@ AsOfProbeBuffer::AsOfProbeBuffer(ClientContext &client, const PhysicalAsOfJoin & prefix_types.emplace_back(type); SortKeyPrefixComparisonColumn col; col.size = DConstants::INVALID_INDEX; - switch (type.InternalType()) { - case PhysicalType::VARCHAR: - // only the sort keys of VARCHAR are terminated by the first null byte - the sort keys of other string - // types (e.g., BLOB or GEOMETRY) can contain null bytes, so we have to decode them to measure their width - col.type = type.id() == LogicalTypeId::VARCHAR ? SortKeyPrefixComparisonType::VARCHAR - : SortKeyPrefixComparisonType::NESTED; + switch (type.id()) { + case LogicalTypeId::VARCHAR: + case LogicalTypeId::BLOB: + col.type = SortKeyPrefixComparisonType::VARCHAR; break; - case PhysicalType::STRUCT: - case PhysicalType::LIST: - case PhysicalType::ARRAY: + case LogicalTypeId::STRUCT: + case LogicalTypeId::LIST: + case LogicalTypeId::ARRAY: col.type = SortKeyPrefixComparisonType::NESTED; break; default: diff --git a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp index 81ff1e45d..d0db40702 100644 --- a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp @@ -350,60 +350,6 @@ struct LayoutGate { static idx_t GetBuildSpaceRequirement(ClientContext &context, const JoinHashTable &ht, idx_t num_threads); -//! Building the bloom filter is costly on the build side to make probing faster, -//! so only use it if there are less build tuples than probing tuples -static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; -//! If we have a build side without a filter, only build the bloom filter if the ratio/size is small enough -static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; -static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; -//! A join is expected to stay in memory while its estimated build side is below this share of the memory budget -static constexpr idx_t LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION = 4; - -//! Whether the build side is expected to exceed the memory budget and spill -static bool JoinIsLikelyExternal(ClientContext &context, const PhysicalHashJoin &op) { - if (Settings::Get(context)) { - return true; - } - idx_t row_width = sizeof(data_ptr_t) + sizeof(hash_t); // per-row pointer table and hash overhead - for (auto &type : op.children[1].get().GetTypes()) { - row_width += GetTypeIdSize(type.InternalType()); - } - const auto build_size = op.children[1].get().estimated_cardinality * row_width; - const auto memory_budget = BufferManager::GetBufferManager(context).GetMaxMemory(); - return build_size > memory_budget / LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION; -} - -//! Whether to accumulate a bloom filter during Sink. Only an external join needs that: its finalize only ever -//! materializes some partitions, so a filter built there would be incomplete, while every row passes through -//! Sink. An in-memory join builds its filter during finalize instead, where the actual build count and the -//! competing filter choices are known. The estimate-based checks mirror CanUseBloomFilter, which decides -//! publication once the actual build count is known. -static bool ShouldBuildBloomFilterInSink(ClientContext &context, const PhysicalHashJoin &op) { - if (!op.filter_pushdown || op.filter_pushdown->probe_info.empty()) { - return false; - } - idx_t equality_count = 0; - for (auto &cond : op.conditions) { - const auto cmp = cond.GetComparisonType(); - if (cmp == ExpressionType::COMPARE_EQUAL || cmp == ExpressionType::COMPARE_NOT_DISTINCT_FROM) { - equality_count++; - } - } - if (equality_count != 1) { - return false; - } - const auto build_estimate = static_cast(op.children[1].get().estimated_cardinality); - const auto probe_estimate = static_cast(op.children[0].get().estimated_cardinality); - if (build_estimate > BUILD_TO_PROBE_RATIO_THRESHOLD * probe_estimate) { - return false; - } - if (!op.filter_pushdown->build_side_has_filter && build_estimate > NON_FILTERING_RATIO_THRESHOLD * probe_estimate && - build_estimate > NON_FILTERING_BUILD_SIDE_THRESHOLD) { - return false; - } - return JoinIsLikelyExternal(context, op); -} - class HashJoinGlobalSinkState : public GlobalSinkState { public: HashJoinGlobalSinkState(const PhysicalHashJoin &op_p, ClientContext &context_p) @@ -438,19 +384,6 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } - InitializeBloomFilter(); - } - - //! Creates the bloom filter that every thread-local hash table inserts into during Sink - void InitializeBloomFilter() { - // a perfect hash join publishes its own exact filter, so a bloom filter would never leave the sink - if (skip_filter_pushdown || can_use_perfect_hash || !ShouldBuildBloomFilterInSink(context, op)) { - hash_table->SetBloomFilter(nullptr); - return; - } - auto filter = make_shared_ptr(); - filter->Initialize(context, MaxValue(op.children[1].get().estimated_cardinality, 1)); - hash_table->SetBloomFilter(std::move(filter)); } ~HashJoinGlobalSinkState() override { @@ -498,7 +431,6 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } - InitializeBloomFilter(); // The next iteration's first chunk publishes the layout afresh: the rerun build may deliver a different // dictionary (or none), so neither a narrowed slot nor the dictionary it indexes into can be carried over. // ResetForNewIterationSinglePartition returned the global JHT to the unpublished state accordingly. @@ -525,12 +457,6 @@ class HashJoinGlobalSinkState : public GlobalSinkState { //! The number of active local states atomic active_local_states; - //! The memory available to the hash table: the reservation minus what partitioning the probe side requires - idx_t GetMaxExternalHTSize() const { - const auto reservation = temporary_memory_state->GetReservation(); - return reservation > probe_side_requirement ? reservation - probe_side_requirement : 0; - } - //! Whether we are doing an external + some sizes bool external; idx_t total_size; @@ -588,8 +514,6 @@ class HashJoinLocalSinkState : public LocalSinkState { } hash_table = op.InitializeHashTable(context, gstate.hash_table->GetRadixBits()); - // every thread inserts into this one filter - InsertOne is an atomic fetch_or, so it needs no lock - hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); // sink_collection exists only after the layout is published on the first build chunk, so // InitializeAppendState runs lazily inside Sink. keep_hash_table = gstate.keep_local_hash_tables; @@ -637,9 +561,6 @@ class HashJoinLocalSinkState : public LocalSinkState { hash_table = op.InitializeHashTable(context.client, gstate.hash_table->GetRadixBits()); append_state_initialised = false; } - // the global state resets before any local one, so the filter this picks up is always a fresh one - D_ASSERT(!gstate.hash_table->GetBloomFilter() || !gstate.hash_table->GetBloomFilter()->IsSealed()); - hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); keep_hash_table = gstate.keep_local_hash_tables; gstate.active_local_states++; if (op.filter_pushdown) { @@ -1257,9 +1178,6 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { } if (sink.global_filter_state) { - // a finalize-built bloom filter is complete once the tasks are done; a sink-built one is - // already sealed and this is a no-op - sink.hash_table->SealBloomFilter(); PublishDeferredRuntimeFilters(sink.context, *sink.hash_table, *sink.global_filter_state); } sink.hash_table->finalized = true; @@ -1311,24 +1229,6 @@ void HashJoinGlobalSinkState::InitializeProbeSpill() { } } -//! Computes and registers the runtime filters of an external join. Both external paths call this exactly once, -//! after the whole build side has been merged into the global hash table and before the finalize event is -//! scheduled. Every input is accumulated during Sink, so it does not matter which partitions are materialized. -static void FinalizeExternalRuntimeFilters(ClientContext &context, const PhysicalHashJoin &op, - HashJoinGlobalSinkState &sink) { - if (!op.filter_pushdown || sink.skip_filter_pushdown) { - return; - } - auto &ht = *sink.hash_table; - if (ht.GetSinkCollection().Count() == 0) { - return; - } - auto filter_min_max = op.filter_pushdown->FinalizeMinMax(*sink.global_filter_state); - // prefix-range filters are built from the materialized hash table, which only ever holds one round - op.filter_pushdown->FinalizeFilters(context, op, std::move(filter_min_max), &ht, true, false, - sink.global_filter_state.get()); -} - class HashJoinRepartitionTask : public ExecutorTask { public: HashJoinRepartitionTask(shared_ptr event_p, ClientContext &context, JoinHashTable &global_ht, @@ -1423,9 +1323,9 @@ class HashJoinRepartitionEvent : public BasePipelineEvent { sink.probe_side_requirement); sink.temporary_memory_state->UpdateReservation(executor.context); - FinalizeExternalRuntimeFilters(sink.context, op, sink); - - sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); + D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); + sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - + sink.probe_side_requirement); sink.ScheduleFinalize(*pipeline, *this); } }; @@ -1461,6 +1361,9 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i return false; } + // building the bloom filter is costly on the build to make probing faster, + // so only use it if there are less build tuples than probing tuples + static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; auto build_count = ht->Count(); if (build_count == 0) { build_count = ht->GetSinkCollection().Count(); @@ -1475,6 +1378,8 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i } // if we have a build side without a filter, only build the bloom filter if the ratio/size is small enough + static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; + static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; if (!build_side_has_filter && build_to_probe_ratio > NON_FILTERING_RATIO_THRESHOLD && build_count > NON_FILTERING_BUILD_SIDE_THRESHOLD) { return false; @@ -1626,15 +1531,13 @@ static unique_ptr CreateRuntimeFilterExpression(ClientContext &conte unique_ptr filter_expr; switch (deferred.type) { case DeferredRuntimeFilterType::BLOOM_FILTER: { - // a filter that is not sealed does not describe the complete build side - publishing it would drop - // rows - and a filter that saturated is not worth the hashing it costs the scan - if (!ht.HasPublishableBloomFilter()) { + D_ASSERT(ht.GetBloomFilter().IsInitialized()); + if (!ht.GetBloomFilter().IsInitialized()) { return nullptr; } - auto bloom_filter = ht.GetBloomFilter(); filter_expr = make_uniq( BoundScalarFunction(BloomFilterScalarFun::GetFunction(filter_input_type)), std::move(children), - make_uniq(std::move(bloom_filter), filters_null_values, key_name, key_type, + make_uniq(ht.GetBloomFilter(), filters_null_values, key_name, key_type, selectivity_threshold, n_vectors_to_check)); break; } @@ -1948,17 +1851,9 @@ unique_ptr JoinFilterPushdownInfo::FinalizeFilters(ClientContext &con } if (allow_bloom_filters && can_emit_runtime_filters && ht && gstate && CanUseBloomFilter(info, context, op, cmp, ht)) { - if (ht->HasPublishableBloomFilter()) { - // a complete filter was accumulated during Sink (external joins build it there) - DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, - filter_col_idx, *gstate); - } else if (allow_prefix_range_filters) { - // like a prefix-range filter, a bloom filter may be built during finalize when the - // finalize materializes the whole build side - an external finalize does not - ht->SetBuildBloomFilter(true); - DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, - filter_col_idx, *gstate); - } + ht->SetBuildBloomFilter(true); + DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, + filter_col_idx, *gstate); } } } @@ -1978,9 +1873,6 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl auto &sink = input.global_state.Cast(); auto &ht = *sink.hash_table; - // the filter covers the whole build side once Sink is done - ht.SealBloomFilter(); - sink.temporary_memory_state->UpdateReservation(context); sink.external = sink.temporary_memory_state->GetReservation() < sink.total_size; if (sink.external) { @@ -2021,7 +1913,8 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl (max_partition_ht_size + sink.probe_side_requirement) > sink.temporary_memory_state->GetReservation()) { // We have to repartition const auto radix_bits_before = ht.GetRadixBits(); - ht.SetRepartitionRadixBits(sink.GetMaxExternalHTSize(), sink.max_partition_size, sink.max_partition_count); + ht.SetRepartitionRadixBits(sink.temporary_memory_state->GetReservation(), sink.max_partition_size, + sink.max_partition_count); DUCKDB_LOG(context, PhysicalOperatorLogType, *this, "PhysicalHashJoin", "Repartition", {{"partitions_before", to_string(RadixPartitioning::NumberOfPartitions(radix_bits_before))}, {"partitions_after", to_string(RadixPartitioning::NumberOfPartitions(ht.GetRadixBits()))}}); @@ -2034,8 +1927,15 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl } sink.local_hash_tables.clear(); sink.owned_local_hash_tables.clear(); - FinalizeExternalRuntimeFilters(context, *this, sink); - sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); + if (filter_pushdown && !sink.skip_filter_pushdown && ht.GetSinkCollection().Count() > 0) { + auto filter_min_max = filter_pushdown->FinalizeMinMax(*sink.global_filter_state); + filter_pushdown->FinalizeFilters(context, *this, std::move(filter_min_max), &ht, false, false, + sink.global_filter_state.get()); + } + ht.PrepareBloomFilterForFinalize(); + D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); + sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - + sink.probe_side_requirement); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; @@ -2089,6 +1989,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl // In case of a large build side or duplicates, use regular hash join if (!use_perfect_hash) { + ht.PrepareBloomFilterForFinalize(); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; @@ -2535,7 +2436,9 @@ void HashJoinGlobalSourceState::PrepareBuild(HashJoinGlobalSinkState &sink) { sink.probe_side_requirement); // Try to put the next partitions in the block collection of the HT - if (!sink.external || !ht.PrepareExternalFinalize(sink.GetMaxExternalHTSize())) { + D_ASSERT(!sink.external || sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); + if (!sink.external || + !ht.PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - sink.probe_side_requirement)) { global_stage = HashJoinSourceStage::DONE; sink.temporary_memory_state->SetZero(); return; diff --git a/src/duckdb/src/execution/operator/order/physical_top_n.cpp b/src/duckdb/src/execution/operator/order/physical_top_n.cpp index 5dd70b9db..41cb651eb 100644 --- a/src/duckdb/src/execution/operator/order/physical_top_n.cpp +++ b/src/duckdb/src/execution/operator/order/physical_top_n.cpp @@ -57,13 +57,9 @@ struct TopNBoundaryValue { Vector boundary_vector; OrderModifiers boundary_modifiers; - bool TryGetBoundaryValue(string ¤t_boundary) { - unique_lock l(lock, std::try_to_lock); - if (!l.owns_lock() || boundary_value == current_boundary) { - return false; - } - current_boundary = boundary_value; - return true; + string GetBoundaryValue() { + lock_guard l(lock); + return boundary_value; } void UpdateValue(string_t boundary_val) { @@ -277,14 +273,15 @@ void TopNHeap::AddLargeHeap(DataChunk &input, const Vector &sort_keys_vec) { } bool TopNHeap::CheckBoundaryValues(DataChunk &sort_chunk, DataChunk &payload, TopNBoundaryValue &global_boundary) { - // An older local boundary remains valid if refreshing it would block. - const auto boundary_updated = global_boundary.TryGetBoundaryValue(boundary_val); - if (boundary_val.empty()) { + // get the global boundary value + auto current_boundary_val = global_boundary.GetBoundaryValue(); + if (current_boundary_val.empty()) { // no boundary value (yet) - don't do anything return true; } - if (boundary_updated) { + if (current_boundary_val != boundary_val) { // new boundary value - decode + boundary_val = std::move(current_boundary_val); boundary_values.Reset(); CreateSortKeyHelpers::DecodeSortKey(string_t(boundary_val), boundary_values, 0, modifiers); for (auto &col : boundary_values.data) { diff --git a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp index af65686ff..ef35aa085 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp @@ -335,12 +335,12 @@ unique_ptr PhysicalBatchInsert::GetGlobalSinkState(ClientContex // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info); + auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info); table = &created_table->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table; + table = insert_table.get_mutable(); } // heuristic - we start off by allocating 4MB of cache space per column static constexpr const idx_t MINIMUM_MEMORY_PER_COLUMN = 4ULL * 1024ULL * 1024ULL; diff --git a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp index 7487684a4..ebbb8a93c 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp @@ -634,7 +634,7 @@ class PartitionWriteLease { PartitionWriteInfo &Get() const { D_ASSERT(write_info); - return *write_info; + return *write_info.get_mutable(); } PartitionWriteInfo &operator*() const { @@ -649,7 +649,7 @@ class PartitionWriteLease { private: optional_ptr manager; - mutable optional_ptr write_info; + optional_ptr write_info; }; class PartitionWriteManager { @@ -766,21 +766,8 @@ struct PartitionedCopyFlushAction { struct PartitionedCopyBatchState { private: - //! Partition values can be NULL, so they are compared with IS NOT DISTINCT FROM semantics - static bool PartitionValuesEqual(const vector &left, const vector &right) { - if (left.size() != right.size()) { - return false; - } - for (idx_t i = 0; i < left.size(); i++) { - if (!Value::NotDistinctFrom(left[i], right[i])) { - return false; - } - } - return true; - } - void SetValues(vector values_p) { - D_ASSERT(!values || PartitionValuesEqual(*values, values_p)); + D_ASSERT(!values || *values == values_p); if (!values) { values = std::move(values_p); } diff --git a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp index c671c63db..c8c9c1635 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp @@ -117,11 +117,12 @@ unique_ptr PhysicalInsert::GetGlobalSinkState(ClientContext &co // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info)->Cast(); + table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info) + ->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table; + table = insert_table.get_mutable(); } auto result = make_uniq(context, GetTypes(), *table); return std::move(result); diff --git a/src/duckdb/src/execution/operator/projection/physical_projection.cpp b/src/duckdb/src/execution/operator/projection/physical_projection.cpp index 5dd02daa8..fcc5b7ba6 100644 --- a/src/duckdb/src/execution/operator/projection/physical_projection.cpp +++ b/src/duckdb/src/execution/operator/projection/physical_projection.cpp @@ -31,16 +31,6 @@ PhysicalProjection::PhysicalProjection(PhysicalPlan &physical_plan, vectorGetExpressionClass() != ExpressionClass::BOUND_REF) { - PhysicalOperator::InitializeOutputChunk(allocator, chunk); - return; - } - } - chunk.InitializeEmpty(types); -} - OperatorResultType PhysicalProjection::Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state_p) const { auto &state = state_p.Cast(); diff --git a/src/duckdb/src/execution/operator/set/physical_union.cpp b/src/duckdb/src/execution/operator/set/physical_union.cpp index 78551361b..3be025eff 100644 --- a/src/duckdb/src/execution/operator/set/physical_union.cpp +++ b/src/duckdb/src/execution/operator/set/physical_union.cpp @@ -83,15 +83,10 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin auto dependency_type = order_matters ? PipelineDependencyType::REQUIRED : PipelineDependencyType::OPTIONAL_DEPENDENCY; dependencies = meta_pipeline.AddDependenciesFrom(union_pipeline, union_pipeline, false, dependency_type); - if (!allow_out_of_order) { - // CreateUnionPipeline already orders this pipeline after current. Include current when - // propagating that ordering to the child sink pipelines as well. - dependencies.push_back(current.shared_from_this()); - } // we also add dependencies if the LHS child can saturate all available threads // in that case, we recursively make all RHS children depend on the LHS. // This prevents breadth-first plan evaluation - if (!allow_out_of_order || can_saturate_threads) { + if (can_saturate_threads) { last_child_ptr = meta_pipeline.GetLastChild(); } } @@ -101,10 +96,8 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin children[i].get().BuildPipelines(union_pipeline, meta_pipeline); if (last_child_ptr) { - // Explicitly ordered unions also order their children's sink pipelines. - auto dependency_mode = - !allow_out_of_order ? RecursiveDependencyMode::FORCE : RecursiveDependencyMode::RESPECT_PARALLELISM; - meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr, dependency_mode); + // the pointer was set, set up the dependencies + meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr); } } } diff --git a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp index f2b2dbbcc..c22e2cf4f 100644 --- a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp @@ -144,7 +144,6 @@ optional_ptr PhysicalPlanGenerator::PlanAsOfLoopJoin(LogicalCo case ExpressionType::COMPARE_EQUAL: case ExpressionType::COMPARE_NOTEQUAL: case ExpressionType::COMPARE_DISTINCT_FROM: - case ExpressionType::COMPARE_NOT_DISTINCT_FROM: break; default: // Unsupported NLJ comparison @@ -361,11 +360,6 @@ PhysicalOperator &PhysicalPlanGenerator::PlanAsOfJoin(LogicalComparisonJoin &op) if (result) { return *result; } - if (has_predicate) { - // PhysicalAsOfJoin only evaluates the comparisons - throw NotImplementedException("Unsupported ASOF JOIN: the join predicate cannot be evaluated together " - "with these join conditions"); - } } // Check to see if we can leverage partitioning diff --git a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp index 3a3267568..4431083a1 100644 --- a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp @@ -247,7 +247,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi return join; } - // range joins apply both projection maps, the nested loop join only applies the left one of a MARK join + D_ASSERT(op.left_projection_map.empty()); idx_t nested_loop_join_threshold = Settings::Get(context); if (left.estimated_cardinality < nested_loop_join_threshold || right.estimated_cardinality < nested_loop_join_threshold) { @@ -273,7 +273,6 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi } if (PhysicalNestedLoopJoin::IsSupported(op.conditions, op.join_type)) { // inequality join: use nested loop - D_ASSERT(op.left_projection_map.empty() || op.join_type == JoinType::MARK); return Make(op, left, right, std::move(op.conditions), op.join_type, op.estimated_cardinality, std::move(op.filter_pushdown)); } @@ -283,7 +282,6 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi RewriteJoinCondition(cond.RightReference(), left.types.size()); } } - D_ASSERT(op.left_projection_map.empty()); auto condition = JoinCondition::CreateExpression(std::move(op.conditions)); return Make(op, left, right, std::move(condition), op.join_type, op.estimated_cardinality); } diff --git a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp index 078f2b426..6e83fd173 100644 --- a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp @@ -39,24 +39,6 @@ static JoinCondition CreateNotDistinctComparison(ClientContext &context, const L return cond; } -//! The types to compare the columns of EXCEPT / INTERSECT with - a collation of either side applies (as for UNION) -static vector GetComparisonTypes(ClientContext &context, const LogicalSetOperation &op, - const vector &left_types, - const vector &right_types) { - // the types only differ in their collations here, but a collation can be missing from a child (e.g. when the - // projection applying a COLLATE was removed) or from the operator (it takes the types of the left child) - vector types; - for (idx_t i = 0; i < left_types.size(); i++) { - auto type = LogicalType::ForceMaxLogicalType(context, left_types[i], right_types[i]); - // the operator types can be outdated (e.g. compressed children), only take a collation from them - if (i < op.types.size() && op.types[i].id() == type.id()) { - type = LogicalType::ForceMaxLogicalType(context, op.types[i], type); - } - types.push_back(std::move(type)); - } - return types; -} - PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { ArenaLinkedList> children(physical_plan->ArenaRef()); for (auto &child : op.children) { @@ -81,7 +63,7 @@ PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { } auto &left = children[0]; auto &right = children[1]; - auto types = GetComparisonTypes(context, op, left.get().GetTypes(), right.get().GetTypes()); + auto &types = left.get().GetTypes(); vector conditions; // create equality condition for all columns for (idx_t i = 0; i < types.size(); i++) { diff --git a/src/duckdb/src/function/aggregate/distributive/count.cpp b/src/duckdb/src/function/aggregate/distributive/count.cpp index f62027b09..69360d5aa 100644 --- a/src/duckdb/src/function/aggregate/distributive/count.cpp +++ b/src/duckdb/src/function/aggregate/distributive/count.cpp @@ -296,7 +296,6 @@ AggregateFunction CountFunctionBase::GetFunction() { fun.GetSignature().AddParameter("arg", LogicalTypeId::ANY); fun.SetName("count"); fun.SetOrderDependent(AggregateOrderDependent::NOT_ORDER_DEPENDENT); - fun.SetCollationHandling(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS); fun.SetStructStateExport(GetCountStateType); fun.SetStatisticsCallback(CountPropagateStats); return fun; diff --git a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp index 318702a0b..c36df8b1d 100644 --- a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp +++ b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp @@ -63,22 +63,12 @@ struct SortedAggregateBindData : public FunctionData { using BindInfoPtr = unique_ptr; using OrderBys = vector; - //! fixed_layout: the buffer struct and per-key columns to use instead of mapping the keys onto the arguments - SortedAggregateBindData( - ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, BindInfoPtr &bind_info, - OrderBys &order_bys, - optional_ptr>> fixed_layout = nullptr) + SortedAggregateBindData(ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, + BindInfoPtr &bind_info, OrderBys &order_bys) : context(context), function(aggregate), bind_info(std::move(bind_info)), threshold(Settings::Get(context)) { vector order_spec; - if (fixed_layout) { - for (auto &child : StructType::GetChildTypes(fixed_layout->first)) { - buffered_types.emplace_back(child.second); - } - order_spec = fixed_layout->second; - } else { - buffered_types = MapSortedColumns(children, order_bys, order_spec); - } + buffered_types = MapSortedColumns(children, order_bys, order_spec); const idx_t argument_count = children.size(); // the arguments are the leading buffered columns (referencing their input slot); appended sort keys follow buffered_cols.resize(buffered_types.size()); @@ -96,11 +86,9 @@ struct SortedAggregateBindData : public FunctionData { BuildSort(order_spec); } - SortedAggregateBindData( - ClientContext &context, BoundAggregateExpression &expr, - optional_ptr>> fixed_layout = nullptr) + SortedAggregateBindData(ClientContext &context, BoundAggregateExpression &expr) : SortedAggregateBindData(context, expr.GetChildrenMutable(), expr.FunctionMutable(), expr.BindInfoMutable(), - expr.GetOrderBysMutable()->orders, fixed_layout) { + expr.GetOrderBysMutable()->orders) { } SortedAggregateBindData(ClientContext &context, BoundWindowExpression &expr) @@ -628,34 +616,29 @@ void FunctionBinder::BindSortedAggregate(ClientContext &context, BoundAggregateE auto &children = expr.GetChildrenMutable(); auto &order_bys = *expr.GetOrderBysMutable(); - // the layout of an exported state was fixed at bind time and is part of its type - keep it, as the optimizer may - // have changed the expressions since (e.g. so that an ORDER BY key no longer matches the argument it matched) - pair> export_layout; if (state_export) { - export_layout.first = ListType::GetChildType(expr.GetReturnType()); - auto &logical_fields = StructType::GetChildTypes(export_layout.first); - ExportAggregateFunction::GetSortedStateOrders(expr.GetReturnType(), export_layout.second); - if (export_layout.second.size() != order_bys.orders.size() || children.size() > logical_fields.size()) { - throw InternalException("Exported state layout of ordered aggregate \"%s\" does not match its ORDER BY", - bound_function.GetName()); - } // the statistics optimizer may have narrowed the buffered column types since the exported type was fixed at // bind time (e.g. a small-range group column INTEGER->TINYINT) - cast them back so the buffer matches its - // declared type. The widening preserves sort order. + // declared type. The widening preserves sort order; casting reused columns to the same type keeps matching. + LogicalType plan_struct; + vector order_columns; + idx_t argument_count; + GetSortedAggregateStateLayout(expr, plan_struct, order_columns, argument_count); + auto &logical_fields = StructType::GetChildTypes(ListType::GetChildType(expr.GetReturnType())); auto cast_to = [&](unique_ptr &e, const LogicalType &type) { if (e->GetReturnType() != type) { e = BoundCastExpression::AddCastToType(context, std::move(e), type); } }; - for (idx_t i = 0; i < children.size(); i++) { + for (idx_t i = 0; i < children.size() && i < logical_fields.size(); i++) { cast_to(children[i], logical_fields[i].second); } - for (idx_t o = 0; o < order_bys.orders.size(); o++) { - cast_to(order_bys.orders[o].expression, logical_fields[export_layout.second[o].column].second); + for (idx_t o = 0; o < order_bys.orders.size() && o < order_columns.size(); o++) { + cast_to(order_bys.orders[o].expression, logical_fields[order_columns[o].column].second); } } - auto sorted_bind = make_uniq(context, expr, state_export ? &export_layout : nullptr); + auto sorted_bind = make_uniq(context, expr); if (!sorted_bind->sorted_on_args) { // The arguments are the children plus the sort columns. diff --git a/src/duckdb/src/function/cast/array_casts.cpp b/src/duckdb/src/function/cast/array_casts.cpp index 118dd5690..6e373fb6b 100644 --- a/src/duckdb/src/function/cast/array_casts.cpp +++ b/src/duckdb/src/function/cast/array_casts.cpp @@ -52,9 +52,11 @@ static bool ArrayToArrayCast(Vector &source, Vector &result, idx_t count, CastPa auto msg = StringUtil::Format("Cannot cast array of size %u to array of size %u", source_array_size, target_array_size); HandleCastError::AssignError(msg, parameters); - // every row fails, so just return null - ConstantVector::SetNull(result, count_t(count)); - return false; + if (!parameters.strict) { + // if this was a TRY_CAST, we know every row will fail, so just return null + ConstantVector::SetNull(result, count_t(count)); + return false; + } } auto &cast_data = parameters.cast_data->Cast(); diff --git a/src/duckdb/src/function/cast/bignum_casts.cpp b/src/duckdb/src/function/cast/bignum_casts.cpp index 11b6e0ec3..489a94270 100644 --- a/src/duckdb/src/function/cast/bignum_casts.cpp +++ b/src/duckdb/src/function/cast/bignum_casts.cpp @@ -276,18 +276,6 @@ BoundCastInfo Bignum::NumericToBignumCastSwitch(const LogicalType &source) { } } -struct BignumToDoubleErrorCast { - template - static inline bool Operation(SRC input, DST &result, CastParameters ¶meters) { - if (Bignum::BignumToDouble(input, result, parameters.strict)) { - return true; - } - HandleCastError::AssignError( - StringUtil::Format("Could not convert bignum '%s' to Double", Bignum::BignumToVarchar(input)), parameters); - return false; - } -}; - BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const LogicalType &source, const LogicalType &target) { D_ASSERT(source.id() == LogicalTypeId::BIGNUM); @@ -325,7 +313,7 @@ BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const Logical case LogicalTypeId::VARCHAR: return BoundCastInfo(&VectorCastHelpers::StringCast); case LogicalTypeId::DOUBLE: - return BoundCastInfo(&VectorCastHelpers::TryCastErrorLoop); + return BoundCastInfo(&VectorCastHelpers::TryCastLoop); default: return TryVectorNullCast; } diff --git a/src/duckdb/src/function/cast/cast_statistics.cpp b/src/duckdb/src/function/cast/cast_statistics.cpp index baebb777a..b382f1633 100644 --- a/src/duckdb/src/function/cast/cast_statistics.cpp +++ b/src/duckdb/src/function/cast/cast_statistics.cpp @@ -153,10 +153,6 @@ static unique_ptr StatisticsPropagateVariant(const BaseStatistic // only try this for non-nested return nullptr; } - if (input.GetStatsType() != StatisticsType::VARIANT_STATS) { - // e.g. the unknown statistics of a prepared statement parameter - return nullptr; - } if (!VariantStats::IsShredded(input)) { // not shredded return nullptr; diff --git a/src/duckdb/src/function/cast/string_cast.cpp b/src/duckdb/src/function/cast/string_cast.cpp index ce5893030..1a9d2b44f 100644 --- a/src/duckdb/src/function/cast/string_cast.cpp +++ b/src/duckdb/src/function/cast/string_cast.cpp @@ -4,7 +4,6 @@ #include "duckdb/common/vector/list_vector.hpp" #include "duckdb/common/vector/map_vector.hpp" #include "duckdb/common/vector/struct_vector.hpp" -#include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/function/cast/default_casts.hpp" #include "duckdb/function/cast/vector_cast_helpers.hpp" #include "duckdb/common/exception/conversion_exception.hpp" @@ -324,10 +323,8 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, // set the list size after the child casts, since the casts may have replaced the child buffers ListVector::SetListSize(result, total); - if (!vector_cast_data.all_converted || parameters.error_message) { - // maps with a NULL key - from a failed key cast, or keys of type NULL - fail to convert - // the key cast does not necessarily produce a flat vector (e.g. when casting to NULL keys) - auto key_validity = result_key_child.Validity(); + if (!vector_cast_data.all_converted) { + auto &key_validity = FlatVector::ValidityMutable(result_key_child); for (idx_t row_idx = 0; row_idx < count; row_idx++) { if (!result_mask.RowIsValid(row_idx)) { continue; @@ -335,30 +332,13 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, auto list = list_data[row_idx]; for (idx_t list_idx = 0; list_idx < list.length; list_idx++) { auto idx = list.offset + list_idx; - if (!key_validity.IsValid(idx)) { - HandleVectorCastError::Operation("Map keys can not be NULL.", result_mask, row_idx, - vector_cast_data); - break; + if (!key_validity.RowIsValid(idx)) { + result_mask.SetInvalid(row_idx); } } } } - if (!parameters.error_message) { - MapVector::MapConversionVerify(result, count); - return vector_cast_data.all_converted; - } - // TRY_CAST: nullify maps with duplicate keys instead of throwing - SelectionVector row_sel(1); - for (idx_t row_idx = 0; row_idx < count; row_idx++) { - if (!result_mask.RowIsValid(row_idx)) { - continue; - } - row_sel.set_index(0, row_idx); - if (MapVector::CheckMapValidity(result, 1, row_sel) != MapInvalidReason::VALID) { - HandleVectorCastError::Operation("Map keys must be unique.", result_mask, row_idx, - vector_cast_data); - } - } + MapVector::MapConversionVerify(result, count); return vector_cast_data.all_converted; } diff --git a/src/duckdb/src/function/cast/union_casts.cpp b/src/duckdb/src/function/cast/union_casts.cpp index b1ad5230f..e4630e317 100644 --- a/src/duckdb/src/function/cast/union_casts.cpp +++ b/src/duckdb/src/function/cast/union_casts.cpp @@ -359,11 +359,6 @@ BoundCastInfo DefaultCasts::ImplicitToUnionCast(BindCastInput &input, const Logi } static bool UnionToVarcharCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { - if (count == 0) { - // the intermediate union vector would not have any storage - FlatVector::SetSize(result, count_t(0)); - return true; - } // first cast all union members to varchar auto &cast_data = parameters.cast_data->Cast(); Vector varchar_union(cast_data.type, count); diff --git a/src/duckdb/src/function/cast/variant/from_variant.cpp b/src/duckdb/src/function/cast/variant/from_variant.cpp index bb910a1bb..a38820cbd 100644 --- a/src/duckdb/src/function/cast/variant/from_variant.cpp +++ b/src/duckdb/src/function/cast/variant/from_variant.cpp @@ -594,18 +594,8 @@ static optional TryCastVariantValueToMap(FromVariantConversionData &conve if (entries->IsNull()) { return Value(target_type); } - auto &children = ListValue::GetChildren(*entries); - for (auto &child : children) { - if (child.IsNull()) { - // a NULL entry has no key - if (conversion_data.error.empty()) { - conversion_data.error = "Map keys can not be NULL."; - } - return optional(); - } - } try { - return Value::MAP(entry_type, children); + return Value::MAP(entry_type, ListValue::GetChildren(*entries)); } catch (const InvalidInputException &ex) { // the entries do not form a valid map (e.g. duplicate keys) if (conversion_data.error.empty()) { @@ -690,13 +680,6 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu // Set the value to NULL and mark as failed FlatVector::SetNull(result, offset + i, true); all_valid = false; - } catch (const ConversionException &ex) { - // strict casts of the converted value (e.g. VARCHAR to an ARRAY of a different size) can throw - if (error.empty()) { - error = ErrorData(ex).RawMessage(); - } - FlatVector::SetNull(result, offset + i, true); - all_valid = false; } } if (target_type.id() == LogicalTypeId::MAP) { diff --git a/src/duckdb/src/function/function_binder.cpp b/src/duckdb/src/function/function_binder.cpp index 2d10779e9..25f9fc9fd 100644 --- a/src/duckdb/src/function/function_binder.cpp +++ b/src/duckdb/src/function/function_binder.cpp @@ -1125,8 +1125,6 @@ static string PushArgumentCollations(ClientContext &context, BoundSimpleFunction return string(); case FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS: return PushCollations(context, bound_function, children, CollationType::COMBINABLE_COLLATIONS); - case FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS: - throw InternalException("PUSH_DISTINCT_COLLATIONS is only supported for aggregate functions"); default: throw InternalException("Unrecognized collation handling"); } @@ -1624,10 +1622,6 @@ unique_ptr FunctionBinder::BindScalarFunction(shared_ptr(std::move(bound_function), std::move(children), std::move(bind_info), is_operator); - if (binder && result_func->Function().GetStability() == FunctionStability::CONSISTENT_WITHIN_QUERY) { - // the result depends on the query it is bound in, also when the bind_expression callback folds it - binder->SetAlwaysRequireRebind(); - } if (result_func->Function().HasBindExpressionCallback()) { // if a bind_expression callback is registered - call it and emit the resulting expression FunctionBindExpressionInput input(context, result_func->FunctionMutable(), result_func->BindInfoMutable().get(), @@ -1704,15 +1698,6 @@ FunctionBinder::BindAggregateFunction(shared_ptr functi vector> children, vector>> keyword_args, unique_ptr filter, AggregateType aggr_type) { - if (aggr_type == AggregateType::DISTINCT && - function->GetCollationHandling() == FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { - for (auto &child : children) { - ExpressionBinder::PushCollation(context, child, child->GetReturnType()); - } - for (auto &argument : keyword_args) { - ExpressionBinder::PushCollation(context, argument.second, argument.second->GetReturnType()); - } - } auto [bound_function, bind_info] = ResolveFunction(std::move(function), children, keyword_args); return make_uniq(std::move(bound_function), std::move(children), std::move(filter), diff --git a/src/duckdb/src/function/pragma/pragma_functions.cpp b/src/duckdb/src/function/pragma/pragma_functions.cpp index 4ddce15a7..c86c04875 100644 --- a/src/duckdb/src/function/pragma/pragma_functions.cpp +++ b/src/duckdb/src/function/pragma/pragma_functions.cpp @@ -19,9 +19,9 @@ namespace duckdb { -static string PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { +static void PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { auto &config = ClientConfig::GetConfig(context); - return StringUtil::Format("SET enable_profiling=%s", SQLString(config.profiler_print_format)); + config.enable_profiler = true; } void RegisterEnableProfiling(BuiltinFunctions &set) { @@ -33,25 +33,24 @@ void RegisterEnableProfiling(BuiltinFunctions &set) { } static void PragmaDisableProfiling(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); auto &config = ClientConfig::GetConfig(context); config.enable_profiler = false; } -static string PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - return "SET enable_progress_bar=true"; +static void PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + ClientConfig::GetConfig(context).enable_progress_bar = true; } -static string PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - return "SET enable_progress_bar=false"; +static void PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + ClientConfig::GetConfig(context).enable_progress_bar = false; } -static string PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - return "SET enable_progress_bar_print=true"; +static void PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + ClientConfig::GetConfig(context).print_progress_bar = true; } -static string PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - return "SET enable_progress_bar_print=false"; +static void PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + ClientConfig::GetConfig(context).print_progress_bar = false; } //! Warn that a pragma that no longer does anything is on its way out @@ -72,17 +71,14 @@ static void PragmaDisableVerification(ClientContext &context, const FunctionPara } static void PragmaEnableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = true; } static void PragmaForceCheckpoint(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock("force_checkpoint"); DBConfig::GetConfig(context).options.force_checkpoint = true; } static void PragmaDisableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = false; } @@ -95,21 +91,19 @@ static void PragmaDisableObjectCache(ClientContext &context, const FunctionParam } static void PragmaEnableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = true; } static void PragmaDisableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { - DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = false; } -static string PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - return "SET SESSION enable_optimizer=true"; +static void PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(true)); } -static string PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - return "SET SESSION enable_optimizer=false"; +static void PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(false)); } void PragmaFunctions::RegisterFunction(BuiltinFunctions &set) { diff --git a/src/duckdb/src/function/scalar/generic/getvariable.cpp b/src/duckdb/src/function/scalar/generic/getvariable.cpp index 4eca4fa03..fe1fbb2a9 100644 --- a/src/duckdb/src/function/scalar/generic/getvariable.cpp +++ b/src/duckdb/src/function/scalar/generic/getvariable.cpp @@ -57,8 +57,6 @@ ScalarFunction GetVariableFun::GetFunction() { ScalarFunction getvar("getvariable", {}, LogicalType::ANY, nullptr, GetVariableBind, nullptr); getvar.GetSignature().AddParameter("variable_name", LogicalType::VARCHAR); getvar.SetBindExpressionCallback(BindGetVariableExpression); - // the value of the variable can change between queries - getvar.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return getvar; } diff --git a/src/duckdb/src/function/scalar/map/map_contains.cpp b/src/duckdb/src/function/scalar/map/map_contains.cpp index 5565d0ebd..95624c3a5 100644 --- a/src/duckdb/src/function/scalar/map/map_contains.cpp +++ b/src/duckdb/src/function/scalar/map/map_contains.cpp @@ -1,5 +1,4 @@ #include "duckdb/common/vector/map_vector.hpp" -#include "duckdb/function/function_binder.hpp" #include "duckdb/function/scalar/list/contains_or_position.hpp" #include "duckdb/planner/expression/bound_cast_expression.hpp" #include "duckdb/function/scalar/map_functions.hpp" @@ -16,52 +15,12 @@ static void MapContainsFunction(DataChunk &input, ExpressionState &state, Vector ListSearchOp(map_vec, key_vec, arg_vec, result, count); } -static bool TypeHasCollation(const LogicalType &type) { - switch (type.id()) { - case LogicalTypeId::VARCHAR: - return !type.HasAlias() && !StringType::GetCollation(type).empty(); - case LogicalTypeId::LIST: - return TypeHasCollation(ListType::GetChildType(type)); - case LogicalTypeId::ARRAY: - return TypeHasCollation(ArrayType::GetChildType(type)); - default: - return false; - } -} - -static unique_ptr BindMapContainsExpression(FunctionBindExpressionInput &input) { - const auto &key_type = input.children[1]->GetReturnType(); - const auto &map_key_type = MapType::KeyType(input.children[0]->GetReturnType()); - if (!TypeHasCollation(key_type) && !TypeHasCollation(map_key_type)) { - return nullptr; - } - - FunctionBinder function_binder(input.context); - ErrorData error; - vector> children; - children.push_back(std::move(input.children[0])); - auto keys = function_binder.BindScalarFunction(Identifier::DefaultSchema(), "map_keys", std::move(children), error); - if (!keys) { - error.Throw(); - } - children.clear(); - children.push_back(std::move(keys)); - children.push_back(std::move(input.children[1])); - auto result = - function_binder.BindScalarFunction(Identifier::DefaultSchema(), "list_contains", std::move(children), error); - if (!result) { - error.Throw(); - } - return result; -} - ScalarFunction MapContainsFun::GetFunction() { auto key_type = LogicalType::TEMPLATE("K"); auto val_type = LogicalType::TEMPLATE("V"); ScalarFunction fun("map_contains", {}, LogicalType::BOOLEAN, MapContainsFunction); fun.GetSignature().AddParameter("map", LogicalType::MAP(key_type, val_type)).AddParameter("key", key_type); - fun.SetBindExpressionCallback(BindMapContainsExpression); return fun; } diff --git a/src/duckdb/src/function/scalar/operator/arithmetic.cpp b/src/duckdb/src/function/scalar/operator/arithmetic.cpp index bdc3e8e01..ca3983d28 100644 --- a/src/duckdb/src/function/scalar/operator/arithmetic.cpp +++ b/src/duckdb/src/function/scalar/operator/arithmetic.cpp @@ -472,16 +472,14 @@ static void BignumAdd(DataChunk &args, ExpressionState &state, Vector &result) { static void BignumSubtract(DataChunk &args, ExpressionState &state, Vector &result) { auto &heap = StringVector::GetStringHeap(result); - // negate a copy of the right operand - the input must not be modified, it can e.g. also be the left operand - vector negated_rhs; BinaryExecutor::Execute( args.data[0], args.data[1], result, [&](bignum_t a, bignum_t b) { const BignumIntermediate lhs(a); - negated_rhs.resize(b.data.GetSize()); - memcpy(negated_rhs.data(), b.data.GetData(), b.data.GetSize()); - BignumIntermediate rhs(negated_rhs.data(), negated_rhs.size()); + BignumIntermediate rhs(b); rhs.NegateInPlace(); - return BignumIntermediate::Add(heap, lhs, rhs); + auto result_value = BignumIntermediate::Add(heap, lhs, rhs); + rhs.NegateInPlace(); + return result_value; }); } @@ -552,9 +550,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi function.SetFallible(); // INTERVAL ordering uses 30-day months but DATE arithmetic uses calendar months, // e.g. '2025-02-01' + INTERVAL '29 days' = '2025-03-02' > + INTERVAL '1 month' = '2025-03-01'. - // Adding months clamps the day, e.g. both '2000-01-30' and '2000-01-31' + INTERVAL '1 month' are - // '2000-02-29', so this is not strictly increasing in the DATE. - function.SetArgProperties({ArgProperties().NonDecreasing(), unset}); + function.SetArgProperties({inc, unset}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, @@ -590,7 +586,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({unset, ArgProperties().NonDecreasing()}); + function.SetArgProperties({unset, inc}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { // TIME +/- INTERVAL wraps modulo 24h. @@ -608,8 +604,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - // not monotonic: adding months clamps the day but keeps the time, e.g. - // '2000-01-30 23:00' + INTERVAL '1 month' > '2000-01-31 01:00' + INTERVAL '1 month' + function.SetArgProperties({unset, inc}); return function; } break; @@ -645,7 +640,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - // not monotonic: see INTERVAL + TIMESTAMP + function.SetArgProperties({inc, unset}); return function; } break; @@ -879,8 +874,7 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const function.SetFallible(); // Monotonic in DATE only; INTERVAL ordering uses 30-day months but DATE arithmetic uses // calendar months so monotonicity in the INTERVAL arg is broken (see OperatorAddFun). - // Subtracting months clamps the day, so this is not strictly increasing in the DATE. - function.SetArgProperties({ArgProperties().NonDecreasing(), ArgProperties()}); + function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); return function; } break; @@ -897,9 +891,8 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const "-", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - // not monotonic: subtracting months clamps the day but keeps the time, e.g. - // '2000-03-30 23:00' - INTERVAL '1 month' > '2000-03-31 01:00' - INTERVAL '1 month' - + // Monotonic in TIMESTAMP only; see DATE - INTERVAL above. + function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); return function; } break; diff --git a/src/duckdb/src/function/scalar/sequence/nextval.cpp b/src/duckdb/src/function/scalar/sequence/nextval.cpp index b8257b420..c8bd13b70 100644 --- a/src/duckdb/src/function/scalar/sequence/nextval.cpp +++ b/src/duckdb/src/function/scalar/sequence/nextval.cpp @@ -157,16 +157,6 @@ void NextValModifiedDatabases(ClientContext &context, FunctionModifiedDatabasesI input.properties.RegisterDBModify(seq.sequence.ParentCatalog(), context, DatabaseModificationType::SEQUENCE); } -//! currval reads the sequence but holds a reference to its catalog entry, so a prepared statement must be rebound -//! when the catalog changes (e.g. the sequence is dropped) without marking the database as modified -void CurrValReadDatabases(ClientContext &context, FunctionModifiedDatabasesInput &input) { - if (!input.bind_data) { - return; - } - auto &seq = input.bind_data->Cast(); - input.properties.RegisterDBRead(seq.sequence.ParentCatalog(), context); -} - } // namespace ScalarFunction NextvalFun::GetFunction() { @@ -191,7 +181,6 @@ ScalarFunction CurrvalFun::GetFunction() { curr_val.SetBindCallback(NextValBind); curr_val.SetSerializeCallback(Serialize); curr_val.SetDeserializeCallback(Deserialize); - curr_val.SetModifiedDatabasesCallback(CurrValReadDatabases); curr_val.SetInitStateCallback(NextValLocalFunction); curr_val.SetVolatile(); curr_val.SetFallible(); diff --git a/src/duckdb/src/function/scalar/string/contains.cpp b/src/duckdb/src/function/scalar/string/contains.cpp index dece4681e..a980ce1fc 100644 --- a/src/duckdb/src/function/scalar/string/contains.cpp +++ b/src/duckdb/src/function/scalar/string/contains.cpp @@ -32,6 +32,13 @@ struct ContainsUnaligned { } }; +struct ContainsGeneric { + template + static bool MatchRemainder(const unsigned char *haystack, const unsigned char *needle, idx_t needle_size) { + return memcmp(haystack + sizeof(UNSIGNED), needle + sizeof(UNSIGNED), needle_size - sizeof(UNSIGNED)) == 0; + } +}; + template static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, idx_t needle_size, idx_t base_offset) { @@ -60,66 +67,6 @@ static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const return DConstants::INVALID_INDEX; } -//! Linear-time (Knuth-Morris-Pratt) search, used when the needle keeps almost-matching the haystack -static idx_t ContainsLinear(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, - idx_t needle_size, idx_t base_offset) { - // prefix[i] is the length of the longest proper prefix of needle[0..i] that is also a suffix of it - vector prefix(needle_size, 0); - for (idx_t i = 1, k = 0; i < needle_size; i++) { - while (k > 0 && needle[i] != needle[k]) { - k = prefix[k - 1]; - } - if (needle[i] == needle[k]) { - k++; - } - prefix[i] = k; - } - for (idx_t i = 0, k = 0; i < haystack_size; i++) { - while (k > 0 && haystack[i] != needle[k]) { - k = prefix[k - 1]; - } - if (haystack[i] == needle[k]) { - k++; - } - if (k == needle_size) { - return base_offset + i + 1 - needle_size; - } - } - return DConstants::INVALID_INDEX; -} - -//! Like Contains, but every candidate position compares up to needle_size bytes, which is -//! quadratic when the needle keeps almost-matching - switch to a linear-time search once that happens -static idx_t ContainsLong(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, - idx_t needle_size, idx_t base_offset) { - if (needle_size > haystack_size) { - return DConstants::INVALID_INDEX; - } - const idx_t candidate_end = haystack_size - (needle_size - 1); - auto needle_entry = Load(needle); - // the number of candidates whose remainder we compare before switching, roughly linear in the haystack - const idx_t max_full_compares = 16 + (haystack_size * 8) / needle_size; - idx_t full_compares = 0; - for (idx_t offset = 0; offset < candidate_end; offset++) { - auto location = - static_cast(memchr(haystack + offset, needle[0], candidate_end - offset)); - if (!location) { - return DConstants::INVALID_INDEX; - } - offset = UnsafeNumericCast(location - haystack); - if (Load(location) != needle_entry) { - continue; - } - if (memcmp(location + sizeof(uint64_t), needle + sizeof(uint64_t), needle_size - sizeof(uint64_t)) == 0) { - return base_offset + offset; - } - if (++full_compares > max_full_compares) { - return ContainsLinear(location, haystack_size - offset, needle, needle_size, base_offset + offset); - } - } - return DConstants::INVALID_INDEX; -} - struct ContainsOperator { template static inline TR Operation(TA left, TB right) { @@ -158,7 +105,7 @@ idx_t FindStrInStr(const unsigned char *haystack, idx_t haystack_size, const uns case 8: return Contains(haystack, haystack_size, needle, 8, base_offset); default: - return ContainsLong(haystack, haystack_size, needle, needle_size, base_offset); + return Contains(haystack, haystack_size, needle, needle_size, base_offset); } } diff --git a/src/duckdb/src/function/scalar/string/length.cpp b/src/duckdb/src/function/scalar/string/length.cpp index e1e3e440e..8fddb4375 100644 --- a/src/duckdb/src/function/scalar/string/length.cpp +++ b/src/duckdb/src/function/scalar/string/length.cpp @@ -1,8 +1,6 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/numeric_utils.hpp" #include "duckdb/common/types/bit.hpp" -#include "duckdb/common/vector/vector_iterator.hpp" -#include "duckdb/common/vector/vector_writer.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/storage/statistics/base_statistics.hpp" #include "duckdb/storage/statistics/numeric_stats.hpp" @@ -222,24 +220,13 @@ void ArrayLengthBinaryFunction(DataChunk &args, ExpressionState &state, Vector & auto &dimensions = data.dimensions; auto max_dimension = static_cast(dimensions.size()); - // the result is NULL for NULL arrays - const auto count = args.size(); - auto array_validity = args.data[0].Validity(); - auto dimension_values = dimension.Values(); - auto writer = FlatVector::Writer(result, count); - for (idx_t i = 0; i < count; i++) { - auto entry = dimension_values[i]; - if (!array_validity.IsValid(i) || !entry.IsValid()) { - writer.WriteNull(); - continue; - } - auto dim = entry.GetValue(); - if (dim < 1 || dim > max_dimension) { + UnaryExecutor::Execute(dimension, result, [&](int64_t dimension) { + if (dimension < 1 || dimension > max_dimension) { throw OutOfRangeException(StringUtil::Format( - "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dim, max_dimension)); + "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dimension, max_dimension)); } - writer.WriteValue(dimensions[UnsafeNumericCast(dim - 1)]); - } + return dimensions[UnsafeNumericCast(dimension - 1)]; + }); } void ArrayOrListLengthBinaryResolveTypes(ResolveScalarFunctionTypesInput &input) { diff --git a/src/duckdb/src/function/scalar/string/regexp.cpp b/src/duckdb/src/function/scalar/string/regexp.cpp index 8143e451b..cdc2b5edb 100644 --- a/src/duckdb/src/function/scalar/string/regexp.cpp +++ b/src/duckdb/src/function/scalar/string/regexp.cpp @@ -415,15 +415,6 @@ static unique_ptr RegexExtractBind(BindScalarFunctionInput &input) } } - if (constant_pattern && constant_string.find("\\C") == string::npos) { - auto validation_options = options; - validation_options.set_log_errors(false); - if (RE2(constant_string, validation_options).ok()) { - // A valid UTF-8 pattern without byte escapes cannot return a partial character. - bound_function.SetErrorMode(FunctionErrors::CANNOT_ERROR); - } - } - return make_uniq(options, std::move(constant_string), constant_pattern, group_index, no_match_returns_input); } diff --git a/src/duckdb/src/function/scalar/system/aggregate_export.cpp b/src/duckdb/src/function/scalar/system/aggregate_export.cpp index d43e1de71..7a598344f 100644 --- a/src/duckdb/src/function/scalar/system/aggregate_export.cpp +++ b/src/duckdb/src/function/scalar/system/aggregate_export.cpp @@ -754,14 +754,10 @@ unique_ptr BindToAggregateState(INPUT &input) { "constant"); } } - // the arguments are not yet cast to the parameter types when binding - auto function_name_val = input.GetNonNullConstant(1).DefaultCastAs(LogicalType::VARCHAR); + auto function_name_val = input.GetNonNullConstant(1); auto function_name = StringValue::Get(function_name_val); auto signature_val = input.GetNonNullConstant(2); - if (signature_val.type().id() != LogicalTypeId::LIST) { - throw BinderException("to_aggregate_state: the signature must be a list of types"); - } // the signature lists all of the argument types in order vector argument_types; for (auto &arg : ListValue::GetChildren(signature_val)) { @@ -909,20 +905,6 @@ unique_ptr ExportAggregateFunction::StateToSQL(const LogicalTy return make_uniq(QualifiedName("system", "main", "to_aggregate_state"), std::move(arguments)); } -void ExportAggregateFunction::GetSortedStateOrders(const LogicalType &state_type, - vector &orders) { - auto ext_info = state_type.GetExtensionInfo(); - if (!ext_info || state_type.id() != LogicalTypeId::LIST) { - throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); - } - auto order_entry = ext_info->properties.find("order_bys"); - if (order_entry == ext_info->properties.end()) { - throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); - } - auto &buffer_struct = ListType::GetChildType(state_type); - ParseOrderBys(order_entry->second, StructType::GetChildTypes(buffer_struct).size(), orders); -} - void ExportAggregateFunction::SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout) { auto &bound_function = aggregate.FunctionMutable(); // functions with an explicit export callback use it as the finalize; others use the field-based serialization diff --git a/src/duckdb/src/function/table/arrow_conversion.cpp b/src/duckdb/src/function/table/arrow_conversion.cpp index b2bbae167..89a30178f 100644 --- a/src/duckdb/src/function/table/arrow_conversion.cpp +++ b/src/duckdb/src/function/table/arrow_conversion.cpp @@ -48,50 +48,6 @@ T *ArrowBufferData(ArrowArray &array, idx_t buffer_idx) { return (T *)array.buffers[buffer_idx]; // NOLINT } -static bool IsAligned(const_data_ptr_t ptr, idx_t alignment) { - return reinterpret_cast(ptr) % alignment == 0; -} - -//! Arrow buffers are not required to be aligned to their value type, and reading an unaligned buffer through a -//! typed pointer is undefined behavior. This reads the values in place when they are aligned, and from an aligned -//! copy of the [offset, offset + count) range when they are not. -template -class ArrowAlignedBufferData { -public: - ArrowAlignedBufferData(const ArrowArray &array, idx_t buffer_idx, idx_t offset, idx_t count) { - auto source = static_cast(array.buffers[buffer_idx]) + offset * sizeof(T); - if (IsAligned(source, alignof(T))) { - data = reinterpret_cast(source); - return; - } - copy = make_unsafe_uniq_array_uninitialized(count); - memcpy(static_cast(copy.get()), source, count * sizeof(T)); - data = copy.get(); - } - - const T &operator[](idx_t idx) const { - return data[idx]; - } - const T *get() const { - return data; - } - -private: - const T *data; - unsafe_unique_array copy; -}; - -//! Points the vector to the values when they are aligned to their type, and copies them into it when they are not -static void SetVectorData(Vector &vector, data_ptr_t data, idx_t size) { - auto type_size = GetTypeIdSize(vector.GetType().InternalType()); - if (IsAligned(data, MinValue(type_size, sizeof(uint64_t)))) { - FlatVector::SetData(vector, data, count_t(size)); - return; - } - memcpy(FlatVector::GetDataMutable(vector), data, type_size * size); - FlatVector::SetSize(vector, size); -} - static void GetValidityMask(ValidityMask &mask, ArrowArray &array, idx_t chunk_offset, idx_t size, int64_t parent_offset, int64_t nested_offset = -1, bool add_null = false) { // In certain we don't need to or cannot copy arrow's validity mask to duckdb. @@ -170,7 +126,7 @@ static ArrowListOffsetData ConvertArrowListOffsetsTemplated(Vector &vector, Arro } idx_t cur_offset = 0; - ArrowAlignedBufferData offsets(array, 1, effective_offset, size + 1); + auto offsets = ArrowBufferData(array, 1) + effective_offset; start_offset = offsets[0]; auto list_data = FlatVector::Writer(vector, size); for (idx_t i = 0; i < size; i++) { @@ -193,8 +149,8 @@ static ArrowListOffsetData ConvertArrowListViewOffsetsTemplated(Vector &vector, auto &list_size = result.list_size; list_size = 0; - ArrowAlignedBufferData offsets(array, 1, effective_offset, size); - ArrowAlignedBufferData sizes(array, 2, effective_offset, size); + auto offsets = ArrowBufferData(array, 1) + effective_offset; + auto sizes = ArrowBufferData(array, 2) + effective_offset; // In ListArrays the offsets have to be sequential // ListViewArrays do not have this same constraint @@ -386,7 +342,7 @@ static void ArrowToDuckDBMapVerify(const Vector &vector, idx_t count) { } template -static void SetVectorString(Vector &vector, idx_t size, char *cdata, const T *offsets) { +static void SetVectorString(Vector &vector, idx_t size, char *cdata, T *offsets) { auto strings = FlatVector::GetDataMutable(vector); for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -403,7 +359,7 @@ static void SetVectorString(Vector &vector, idx_t size, char *cdata, const T *of static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, idx_t current_pos) { auto strings = FlatVector::Writer(vector, size); - ArrowAlignedBufferData arrow_string(array, 1, current_pos, size); + auto arrow_string = ArrowBufferData(array, 1) + current_pos; for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -416,7 +372,7 @@ static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, i // | Bytes 0-3 | Bytes 4-15 | // |------------|---------------------------------------| // | length | data (padded with 0) | - strings.WriteValue(string_t(arrow_string[row_idx].GetInlineData(), length)); + strings.WriteStringRef(string_t(arrow_string[row_idx].GetInlineData(), length)); } else { // This string is not inlined, we have to check a different buffer and offsets // | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | @@ -437,7 +393,7 @@ static void DirectConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs auto data_ptr = ArrowBufferData(array, 1) + internal_type * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - SetVectorData(vector, data_ptr, size); + FlatVector::SetData(vector, data_ptr, count_t(size)); } template @@ -445,8 +401,8 @@ static void TimeConversion(Vector &vector, ArrowArray &array, idx_t chunk_offset int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_writer = FlatVector::Writer(vector, size); auto &validity_mask = FlatVector::ValidityMutable(vector); - ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), - size); + auto src_ptr = static_cast(array.buffers[1]) + + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { int64_t result; @@ -475,8 +431,8 @@ static void TimeNSConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), - size); + auto src_ptr = static_cast(array.buffers[1]) + + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { // dtime_ns_t.micros actually holds nanos (!) @@ -501,8 +457,8 @@ static void UUIDConversion(Vector &vector, const ArrowArray &array, idx_t chunk_ int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); + auto src_ptr = static_cast(array.buffers[1]) + + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { tgt_ptr[row].lower = static_cast(BSwapIfLE(src_ptr[row].upper)); @@ -527,8 +483,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); + auto src_ptr = + ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { if (!TryMultiplyOperator::Operation(src_ptr[row], conversion, tgt_ptr[row].value)) { @@ -550,8 +506,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); + auto src_ptr = + ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].months = 0; @@ -564,8 +520,8 @@ static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); + auto src_ptr = + ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].micros = 0; @@ -576,8 +532,8 @@ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t ch static void IntervalConversionMonthDayNanos(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = src_ptr[row].days; tgt_ptr[row].micros = src_ptr[row].nanoseconds / Interval::NANOS_PER_MICRO; @@ -802,15 +758,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBRunEndEncoded(Vector &vector, c } } template -static void ConvertDecimal(Vector &vector, ArrowArray &array, idx_t size, idx_t offset) { - if (sizeof(SRC) == GetTypeIdSize(vector.GetType().InternalType())) { - auto data = ArrowBufferData(array, 1) + offset * sizeof(SRC); - SetVectorData(vector, data, size); - return; - } - - ArrowAlignedBufferData src_ptr(array, 1, offset, size); - auto &val_mask = FlatVector::Validity(vector); +void ConvertDecimal(SRC src_ptr, Vector &vector, ArrowArray &array, idx_t size, int64_t nested_offset, + uint64_t parent_offset, idx_t chunk_offset, ValidityMask &val_mask, + DecimalBitWidth arrow_bit_width) { switch (vector.GetType().InternalType()) { case PhysicalType::INT16: { auto tgt_ptr = FlatVector::GetDataMutable(vector); @@ -824,36 +774,58 @@ static void ConvertDecimal(Vector &vector, ArrowArray &array, idx_t size, idx_t break; } case PhysicalType::INT32: { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; + if (arrow_bit_width == DecimalBitWidth::DECIMAL_32) { + auto data = ArrowBufferData(array, 1) + + GetTypeIdSize(vector.GetType().InternalType()) * + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + FlatVector::SetData(vector, data, count_t(size)); + } else { + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; + } } } break; } case PhysicalType::INT64: { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; + if (arrow_bit_width == DecimalBitWidth::DECIMAL_64) { + auto data = ArrowBufferData(array, 1) + + GetTypeIdSize(vector.GetType().InternalType()) * + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + FlatVector::SetData(vector, data, count_t(size)); + } else { + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; + } } } break; } case PhysicalType::INT128: { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; + if (arrow_bit_width == DecimalBitWidth::DECIMAL_128) { + auto data = ArrowBufferData(array, 1) + + GetTypeIdSize(vector.GetType().InternalType()) * + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + FlatVector::SetData(vector, data, count_t(size)); + } else { + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; + } } } + break; } default: @@ -946,18 +918,16 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar switch (size_type) { case ArrowVariableSizeType::SUPER_SIZE: { auto cdata = ArrowBufferData(array, 2); - ArrowAlignedBufferData offsets( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size + 1); - SetVectorString(vector, size, cdata, offsets.get()); + auto offsets = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + SetVectorString(vector, size, cdata, offsets); break; } case ArrowVariableSizeType::NORMAL: { auto cdata = ArrowBufferData(array, 2); - ArrowAlignedBufferData offsets( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size + 1); - SetVectorString(vector, size, cdata, offsets.get()); + auto offsets = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + SetVectorString(vector, size, cdata, offsets); break; } case ArrowVariableSizeType::VIEW: { @@ -998,9 +968,8 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::MILLISECONDS: { //! convert date from nanoseconds to days - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size); + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); auto tgt_ptr = FlatVector::GetDataMutable(vector); for (idx_t row = 0; row < size; row++) { tgt_ptr[row] = date_t(UnsafeNumericCast(static_cast(src_ptr[row]) / @@ -1034,9 +1003,8 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size); + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1096,9 +1064,8 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size); + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1155,9 +1122,8 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - ArrowAlignedBufferData src_ptr( - array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), - size); + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].micros = src_ptr[row] / 1000; tgt_ptr[row].days = 0; @@ -1181,18 +1147,34 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar break; } case LogicalTypeId::DECIMAL: { + auto &val_mask = FlatVector::ValidityMutable(vector); auto &datetime_info = arrow_type.GetTypeInfo(); - auto offset = GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - switch (datetime_info.GetBitWidth()) { - case DecimalBitWidth::DECIMAL_32: - ConvertDecimal(vector, array, size, offset); + auto bit_width = datetime_info.GetBitWidth(); + + switch (bit_width) { + case DecimalBitWidth::DECIMAL_32: { + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, + bit_width); break; - case DecimalBitWidth::DECIMAL_64: - ConvertDecimal(vector, array, size, offset); + } + + case DecimalBitWidth::DECIMAL_64: { + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, + bit_width); break; - case DecimalBitWidth::DECIMAL_128: - ConvertDecimal(vector, array, size, offset); + } + + case DecimalBitWidth::DECIMAL_128: { + auto src_ptr = ArrowBufferData(array, 1) + + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, + bit_width); break; + } default: throw NotImplementedException("Unsupported precision for Arrow Decimal Type."); } @@ -1506,16 +1488,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBDictionary(Vector &vector, Arro } auto offset_type = arrow_type.GetDuckType(); //! Get Pointer to Indices of Dictionary - auto index_size = GetTypeIdSize(offset_type.InternalType()); - auto indices = - ArrowBufferData(array, 1) + - index_size * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - unsafe_unique_array aligned_indices; - if (!IsAligned(indices, index_size)) { - aligned_indices = make_unsafe_uniq_array_uninitialized((index_size * size + 7) / 8); - memcpy(aligned_indices.get(), indices, index_size * size); - indices = reinterpret_cast(aligned_indices.get()); - } + auto indices = ArrowBufferData(array, 1) + + GetTypeIdSize(offset_type.InternalType()) * + GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); SelectionVector sel; if (has_nulls) { diff --git a/src/duckdb/src/function/table/checkpoint.cpp b/src/duckdb/src/function/table/checkpoint.cpp index c696a4d4d..a59e3dd9d 100644 --- a/src/duckdb/src/function/table/checkpoint.cpp +++ b/src/duckdb/src/function/table/checkpoint.cpp @@ -11,7 +11,7 @@ struct CheckpointBindData : public FunctionData { explicit CheckpointBindData(optional_ptr db) : db(db) { } - mutable optional_ptr db; + optional_ptr db; public: unique_ptr Copy() const override { @@ -49,7 +49,7 @@ static unique_ptr CheckpointBind(ClientContext &context, TableFunc template static void TemplatedCheckpointFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { auto &bind_data = data_p.bind_data->Cast(); - auto &transaction_manager = TransactionManager::Get(*bind_data.db); + auto &transaction_manager = TransactionManager::Get(*bind_data.db.get_mutable()); transaction_manager.Checkpoint(context, FORCE); } diff --git a/src/duckdb/src/function/table/range.cpp b/src/duckdb/src/function/table/range.cpp index 038189e31..8af91754c 100644 --- a/src/duckdb/src/function/table/range.cpp +++ b/src/duckdb/src/function/table/range.cpp @@ -262,8 +262,7 @@ struct RangeDateTimeBindData : public TableFunctionData { return; } - // a step pointing away from the end produces an empty range - cardinality = delta / increment < 0 ? 0 : idx_t(delta / increment); + cardinality = idx_t(delta / increment); } idx_t cardinality; diff --git a/src/duckdb/src/function/table/system/create_external_resource.cpp b/src/duckdb/src/function/table/system/create_external_resource.cpp index 65a585e5f..046a3678c 100644 --- a/src/duckdb/src/function/table/system/create_external_resource.cpp +++ b/src/duckdb/src/function/table/system/create_external_resource.cpp @@ -212,11 +212,6 @@ static void CreateExternalResourceFunction(ClientContext &context, TableFunction type->create_function); } handle = RequireResourceMap(result->Collection().GetRows().GetValue(0, 0), type->create_function, "handle"); - if (handle.IsNull()) { - // the handle identifies the resource for status and teardown - without one it can never be destroyed - throw InvalidInputException("create_external_resource: create function \"%s\" returned a NULL handle", - type->create_function); - } LogExternalResourceOperation(context, bind_data.type_name, bind_data.resource_name, "create", string(), NoExtraInfo()); } catch (std::exception &ex) { diff --git a/src/duckdb/src/function/table/system/enable_profiling.cpp b/src/duckdb/src/function/table/system/enable_profiling.cpp index f5979b71e..0b830beb6 100644 --- a/src/duckdb/src/function/table/system/enable_profiling.cpp +++ b/src/duckdb/src/function/table/system/enable_profiling.cpp @@ -1,6 +1,5 @@ #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/client_context.hpp" -#include "duckdb/main/config.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" @@ -90,21 +89,6 @@ static unique_ptr BindEnableProfiling(ClientContext &context, Tabl // The metrics are placed by position however they were passed; the overload settled pattern or list of names bind_data->metrics = input.inputs[0]; - auto &config = DBConfig::GetConfig(context); - config.CheckLock(EnableProfilingSetting::Name); - if (!bind_data->coverage.IsNull()) { - config.CheckLock(ProfilingCoverageSetting::Name); - } - if (!bind_data->save_location.IsNull()) { - config.CheckLock(ProfilingOutputSetting::Name); - } - if (!bind_data->mode.IsNull()) { - config.CheckLock(ProfilingModeSetting::Name); - } - if (!bind_data->metrics.IsNull()) { - config.CheckLock(TrackedMetricsSetting::Name); - } - return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); @@ -118,7 +102,6 @@ static void DisableProfiling(ClientContext &context, TableFunctionInput &data, D static unique_ptr BindDisableProfiling(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { - DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/system/external_resources.cpp b/src/duckdb/src/function/table/system/external_resources.cpp index c612011ed..faef29aa9 100644 --- a/src/duckdb/src/function/table/system/external_resources.cpp +++ b/src/duckdb/src/function/table/system/external_resources.cpp @@ -241,9 +241,6 @@ static unique_ptr RegisterExternalResourceBind(ClientContext &cont if (input.inputs[0].IsNull() || input.inputs[1].IsNull()) { throw InvalidInputException("register_external_resource: the type and name must not be NULL"); } - if (input.inputs[2].IsNull()) { - throw InvalidInputException("register_external_resource: the handle must not be NULL"); - } resource.type = StringValue::Get(input.inputs[0]); resource.name = StringValue::Get(input.inputs[1]); // The handle is the durable identity and doubles as the deleter payload. diff --git a/src/duckdb/src/function/table/system/logging_utils.cpp b/src/duckdb/src/function/table/system/logging_utils.cpp index 1b1a0004d..198e83b5d 100644 --- a/src/duckdb/src/function/table/system/logging_utils.cpp +++ b/src/duckdb/src/function/table/system/logging_utils.cpp @@ -6,8 +6,6 @@ #include "duckdb/logging/log_manager.hpp" #include "duckdb/logging/logging.hpp" #include "duckdb/main/client_context.hpp" -#include "duckdb/main/config.hpp" -#include "duckdb/main/settings.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_entries.hpp" #include "duckdb/main/extension_helper.hpp" @@ -70,13 +68,6 @@ static unique_ptr BindEnableLogging(ClientContext &context, TableF throw InvalidInputException("EnableLogging: expected 0 or 1 parameter"); } - auto &config = DBConfig::GetConfig(context); - config.CheckLock(EnableLogging::Name); - config.CheckLock(EnabledLogTypes::Name); - config.CheckLock(LoggingLevel::Name); - config.CheckLock(LoggingMode::Name); - config.CheckLock(LoggingStorage::Name); - auto result = make_uniq(); bool storage_isset = false; @@ -179,7 +170,6 @@ static void TruncateLogs(ClientContext &context, TableFunctionInput &data, DataC static unique_ptr BindDisableLogging(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { - DBConfig::GetConfig(context).CheckLock(EnableLogging::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index cde76bec0..4a5eb618b 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48256" +#define DUCKDB_PATCH_VERSION "0-alpha47887" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48256" +#define DUCKDB_VERSION "v2.0.0-alpha47887" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "8588363bf1" +#define DUCKDB_SOURCE_ID "f702a1f801" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/variant/variant_shredding.cpp b/src/duckdb/src/function/variant/variant_shredding.cpp index f3f511ff7..ea748f388 100644 --- a/src/duckdb/src/function/variant/variant_shredding.cpp +++ b/src/duckdb/src/function/variant/variant_shredding.cpp @@ -343,10 +343,10 @@ bool VariantShreddingState::ValueIsShredded(UnifiedVariantVectorData &variant, i return false; } if (type_id == VariantLogicalType::DECIMAL) { - // the shredded column stores the unscaled value, so it only holds decimals of its scale that fit its width + auto physical_type = type.InternalType(); auto decimal_data = VariantUtils::DecodeDecimalData(variant, row, values_index); - return type.InternalType() == decimal_data.GetPhysicalType() && - decimal_data.scale == DecimalType::GetScale(type) && decimal_data.width <= DecimalType::GetWidth(type); + auto decimal_physical_type = decimal_data.GetPhysicalType(); + return physical_type == decimal_physical_type; } return true; } diff --git a/src/duckdb/src/function/window/window_value_function.cpp b/src/duckdb/src/function/window/window_value_function.cpp index 6d9e66ecc..a9735aa08 100644 --- a/src/duckdb/src/function/window/window_value_function.cpp +++ b/src/duckdb/src/function/window/window_value_function.cpp @@ -628,19 +628,6 @@ unique_ptr WindowLeadLagExecutor::GetLocal(ExecutionContext &con return make_uniq(context, glstate); } -//! The index of the row at the offset - saturated if it does not fit, as it is outside the partition anyway -static int64_t LeadLagIndex(ExpressionType type, int64_t row_idx, int64_t offset) { - int64_t result; - if (type == ExpressionType::WINDOW_LEAD) { - if (!TryAddOperator::Operation(row_idx, offset, result)) { - result = offset < 0 ? NumericLimits::Minimum() : NumericLimits::Maximum(); - } - } else if (!TrySubtractOperator::Operation(row_idx, offset, result)) { - result = offset < 0 ? NumericLimits::Maximum() : NumericLimits::Minimum(); - } - return result; -} - void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_chunk, DataChunk &bounds, Vector &result, idx_t row_idx, OperatorSinkInput &sink) { auto &glstate = sink.global_state.Cast(); @@ -681,7 +668,12 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c frame = FrameBounds(frame_begin[i], MaxValue(frame_end[i], frame_begin[i])); const auto own_row = glstate.row_tree->Rank(frame.start, frame.end, row_idx) - 1; // (2) adjust the row number by adding or subtracting an offset - const auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(own_row), offset); + auto val_idx = NumericCast(own_row); + if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { + val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); + } else { + val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); + } const auto frame_width = NumericCast(frame.end - frame.start); if (val_idx >= 0 && val_idx < frame_width) { // (3) find the row at that offset @@ -735,7 +727,12 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c } offset = leadlag_offset.GetCell(i); } - auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(row_idx), offset); + int64_t val_idx = (int64_t)row_idx; + if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { + val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); + } else { + val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); + } idx_t delta = 0; if (val_idx < (int64_t)row_idx) { @@ -1549,8 +1546,7 @@ void WindowFillExecutor::GetSharing(WindowExecutor &executor, WindowSharedExpres if (arg_order_idx.empty()) { // We use the range ordering, even if it has not been defined if (executor.range_idx == DConstants::INVALID_INDEX) { - // the first ordering - also when it is shared with an argument ordering that is a prefix of the orderings - D_ASSERT(!wexpr.OrderBy().empty()); + D_ASSERT(wexpr.OrderBy().size() == 1); // We don't need the validity mask because we have also requested the valid range for the ordering. executor.range_idx = shared.RegisterCollection(wexpr.OrderBy()[0].expression, false); } diff --git a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp index b859af388..cc7ef078c 100644 --- a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +++ b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp @@ -64,8 +64,6 @@ class SequenceCatalogEntry : public StandardEntry { int64_t NextValue(DuckTransaction &transaction); int64_t SetValue(DuckTransaction &transaction, int64_t value, bool is_called); void ReplayValue(uint64_t usage_count, int64_t counter, optional last_value); - //! Derive the last value from the counter - storage versions prior to v2.0.0 do not store it - void DeriveLastValue(); string ToSQL() const override; diff --git a/src/duckdb/src/include/duckdb/common/allocator.hpp b/src/duckdb/src/include/duckdb/common/allocator.hpp index c69fbe2cf..12f9eb5fb 100644 --- a/src/duckdb/src/include/duckdb/common/allocator.hpp +++ b/src/duckdb/src/include/duckdb/common/allocator.hpp @@ -125,8 +125,6 @@ class Allocator { DUCKDB_API static Allocator &DefaultAllocator(); DUCKDB_API static shared_ptr &DefaultAllocatorReference(); - //! Total mapped allocator memory, if supported by this allocator. - optional_idx GetMappedMemory() const; static bool SupportsFlush(); static optional_idx DecayDelay(); static void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count); diff --git a/src/duckdb/src/include/duckdb/common/enum_util.hpp b/src/duckdb/src/include/duckdb/common/enum_util.hpp index 5159f95e2..6a2297a91 100644 --- a/src/duckdb/src/include/duckdb/common/enum_util.hpp +++ b/src/duckdb/src/include/duckdb/common/enum_util.hpp @@ -292,8 +292,6 @@ enum class HLLStorageType : uint8_t; enum class HTTPClientCachePolicy : uint8_t; -enum class HTTPRetryOutcomeOwner : uint8_t; - enum class HTTPStatusCode : uint16_t; enum class HTTPTransportReusePolicy : uint8_t; @@ -1061,9 +1059,6 @@ const char* EnumUtil::ToChars(HLLStorageType value); template<> const char* EnumUtil::ToChars(HTTPClientCachePolicy value); -template<> -const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value); - template<> const char* EnumUtil::ToChars(HTTPStatusCode value); @@ -2019,9 +2014,6 @@ HLLStorageType EnumUtil::FromString(const char *value); template<> HTTPClientCachePolicy EnumUtil::FromString(const char *value); -template<> -HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value); - template<> HTTPStatusCode EnumUtil::FromString(const char *value); diff --git a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp index 10617fde2..b0d0e9c2d 100644 --- a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp +++ b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp @@ -23,11 +23,6 @@ class HTTPException : public Exception { explicit HTTPException(const string &message) : Exception(ExceptionType::HTTP, message) { } - //! Rebuild an HTTP exception from extra info captured earlier (e.g. to extend its message). - explicit HTTPException(const unordered_map &extra_info, const string &message) - : Exception(extra_info, ExceptionType::HTTP, message) { - } - template ::status = 0, typename... ARGS> explicit HTTPException(RESPONSE &response, const string &msg, ARGS &&...params) : HTTPException(static_cast(response.status), response.body, response.headers, response.reason, msg, diff --git a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp index a5b0d97fc..534020b92 100644 --- a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp +++ b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp @@ -8,7 +8,6 @@ #pragma once -#include "duckdb/common/identifier.hpp" #include "duckdb/common/types/column/partitioned_column_data.hpp" #include "duckdb/common/open_file_info.hpp" #include "duckdb/original/std/sstream.hpp" @@ -19,8 +18,7 @@ namespace duckdb { struct MultiFilePushdownInfo; struct HivePartitioningFilterInfo { - identifier_map_t column_map; - Identifier filename_column; + unordered_map column_map; bool hive_enabled; bool filename_enabled; }; diff --git a/src/duckdb/src/include/duckdb/common/hugeint.hpp b/src/duckdb/src/include/duckdb/common/hugeint.hpp index 844106506..acdc4fb4b 100644 --- a/src/duckdb/src/include/duckdb/common/hugeint.hpp +++ b/src/duckdb/src/include/duckdb/common/hugeint.hpp @@ -17,9 +17,7 @@ struct hugeint_t { // NOLINT: use numeric casing public: hugeint_t() = default; - //! constexpr so that arithmetic against a constant operand can be folded at compile - //! time; the body is at the bottom of this header - constexpr hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` + DUCKDB_API hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` constexpr hugeint_t(int64_t upper, uint64_t lower) : lower(lower), upper(upper) { } constexpr hugeint_t(const hugeint_t &rhs) = default; @@ -30,22 +28,20 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API string ToString() const; // comparison operators - //! constexpr so that comparisons fold at compile time - constexpr bool operator==(const hugeint_t &rhs) const; - constexpr bool operator!=(const hugeint_t &rhs) const; - constexpr bool operator<=(const hugeint_t &rhs) const; - constexpr bool operator<(const hugeint_t &rhs) const; - constexpr bool operator>(const hugeint_t &rhs) const; - constexpr bool operator>=(const hugeint_t &rhs) const; + DUCKDB_API bool operator==(const hugeint_t &rhs) const; + DUCKDB_API bool operator!=(const hugeint_t &rhs) const; + DUCKDB_API bool operator<=(const hugeint_t &rhs) const; + DUCKDB_API bool operator<(const hugeint_t &rhs) const; + DUCKDB_API bool operator>(const hugeint_t &rhs) const; + DUCKDB_API bool operator>=(const hugeint_t &rhs) const; // arithmetic operators - //! constexpr so the compiler can fold these when an operand is a compile-time constant - constexpr hugeint_t operator+(const hugeint_t &rhs) const; - constexpr hugeint_t operator-(const hugeint_t &rhs) const; - constexpr hugeint_t operator*(const hugeint_t &rhs) const; - constexpr hugeint_t operator/(const hugeint_t &rhs) const; + DUCKDB_API hugeint_t operator+(const hugeint_t &rhs) const; + DUCKDB_API hugeint_t operator-(const hugeint_t &rhs) const; + DUCKDB_API hugeint_t operator*(const hugeint_t &rhs) const; + DUCKDB_API hugeint_t operator/(const hugeint_t &rhs) const; DUCKDB_API hugeint_t operator%(const hugeint_t &rhs) const; - constexpr hugeint_t operator-() const; + DUCKDB_API hugeint_t operator-() const; // bitwise operators DUCKDB_API hugeint_t operator>>(const hugeint_t &rhs) const; @@ -83,321 +79,6 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API explicit operator uhugeint_t() const; }; -//===--------------------------------------------------------------------===// -// constexpr 128-bit arithmetic -// -// These live here rather than next to the rest of Hugeint because -// duckdb/common/types/hugeint.hpp includes duckdb/common/limits.hpp, which -// includes this file back. Everything the digit-parsing loops need has to be -// reachable from here, and the bodies are at the bottom of the file because a -// member function body defined inside the class is looked up at the class's -// closing brace -- above the helpers below. -//===--------------------------------------------------------------------===// - -//! NumericLimits::Minimum()/Maximum(), spelled out because limits.hpp -//! includes this file back so the traits cannot be used here -constexpr hugeint_t HugeintMinimum() { - return hugeint_t(INT64_MIN, 0); -} - -constexpr hugeint_t HugeintMaximum() { - return hugeint_t(INT64_MAX, UINT64_MAX); -} - -constexpr bool HugeintEquals(hugeint_t lhs, hugeint_t rhs) { - return lhs.upper == rhs.upper && lhs.lower == rhs.lower; -} - -constexpr bool HugeintNotEquals(hugeint_t lhs, hugeint_t rhs) { - return !HugeintEquals(lhs, rhs); -} - -constexpr bool HugeintGreaterThan(hugeint_t lhs, hugeint_t rhs) { - return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower > rhs.lower); -} - -constexpr bool HugeintGreaterThanEquals(hugeint_t lhs, hugeint_t rhs) { - return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower >= rhs.lower); -} - -constexpr bool HugeintLessThan(hugeint_t lhs, hugeint_t rhs) { - return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower < rhs.lower); -} - -constexpr bool HugeintLessThanEquals(hugeint_t lhs, hugeint_t rhs) { - return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower <= rhs.lower); -} - -//! Two's-complement negation, written out because the operators below cannot call -//! operator-() on themselves without the fold failing -constexpr hugeint_t HugeintNegate(hugeint_t n) { - return hugeint_t(static_cast(~static_cast(n.upper) + (n.lower == 0)), 0 - n.lower); -} - -constexpr int HugeintSign(hugeint_t n) { - // the sign is not decided by upper alone: upper == 0 with a non-zero lower is positive - return int(HugeintGreaterThan(n, hugeint_t(0))) - int(HugeintLessThan(n, hugeint_t(0))); -} - -constexpr hugeint_t HugeintAbs(hugeint_t n) { - return n.upper < 0 ? HugeintNegate(n) : n; -} - -constexpr hugeint_t HugeintAddOne(hugeint_t n) { - const int carry = (n.lower == UINT64_MAX) ? 1 : 0; - return hugeint_t(static_cast(static_cast(n.upper) + carry), n.lower + 1); -} - -constexpr hugeint_t HugeintSubtractOne(hugeint_t n) { - // lower wraps to UINT64_MAX, so a borrow is taken out of upper - const int borrow = (n.lower == 0) ? 1 : 0; - return hugeint_t(static_cast(static_cast(n.upper) - borrow), n.lower - 1); -} - -//! Helpers for the bit-at-a-time division, from the same uint128_t reference the -//! runtime implementation uses -constexpr uint8_t HugeintHighestBit(hugeint_t bits) { - uint8_t out = 0; - if (bits.upper) { - // bit index 64 is the lowest bit of upper - out = 64; - uint64_t up = static_cast(bits.upper); - while (up) { - up >>= 1; - out++; - } - } else { - uint64_t low = bits.lower; - while (low) { - low >>= 1; - out++; - } - } - return out; -} - -constexpr bool HugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { - if (bit_position < 64) { - return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); - } - return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); -} - -//! Shift left by 1..63 bits. Called by the division loop below with amount == 1. -//! Takes no D_ASSERT: in assertion builds that macro is an unconditional function call, which -//! would make this function unusable in a constant expression. -constexpr hugeint_t HugeintLeftShift(hugeint_t lhs, uint32_t amount) { - hugeint_t result {0, 0}; - result.lower = lhs.lower << amount; - result.upper = static_cast((static_cast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); - return result; -} - -constexpr hugeint_t HugeintMultiplyUnchecked(hugeint_t lhs, hugeint_t rhs) { - // Unchecked multiplication uses the low 128 bits, including for negative operands. -#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) - const auto left = (static_cast<__uint128_t>(static_cast(lhs.upper)) << 64) | lhs.lower; - const auto right = (static_cast<__uint128_t>(static_cast(rhs.upper)) << 64) | rhs.lower; - const auto result = left * right; - return hugeint_t(static_cast(result >> 64), static_cast(result)); -#else - // Multiply code adapted from: - // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - - // split values into 4 32-bit parts - uint64_t top[4] {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, - lhs.lower & 0xffffffff}; - uint64_t bottom[4] {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, - rhs.lower & 0xffffffff}; - uint64_t products[4][4] {}; - - // multiply each component of the values - for (idx_t x = 0; x < 4; x++) { - for (idx_t y = 0; y < 4; y++) { - products[x][y] = top[x] * bottom[y]; - } - } - - // first row - uint64_t fourth32 = (products[3][3] & 0xffffffff); - uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); - uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); - uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); - - // second row - third32 += (products[2][3] & 0xffffffff); - second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); - first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); - - // third row - second32 += (products[1][3] & 0xffffffff); - first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); - - // fourth row - first32 += (products[0][3] & 0xffffffff); - - // move carry to next digit - third32 += fourth32 >> 32; - second32 += third32 >> 32; - first32 += second32 >> 32; - - // remove carry from current digit - fourth32 &= 0xffffffff; - third32 &= 0xffffffff; - second32 &= 0xffffffff; - first32 &= 0xffffffff; - - return hugeint_t(static_cast((first32 << 32) + second32), (third32 << 32) + fourth32); -#endif -} - -//! Result of a 128-bit division. Hugeint::DivMod's out-parameter form is declared in -//! types/hugeint.hpp, which is not visible from this header -struct HugeintDivModResult { - hugeint_t quotient; - hugeint_t remainder; -}; - -constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs); - -//! Precondition: lhs or rhs equals HugeintMinimum(). Only reached through HugeintDivMod. -//! Takes no D_ASSERT for the same reason as HugeintLeftShift. -constexpr HugeintDivModResult HugeintDivModMinimum(hugeint_t lhs, hugeint_t rhs) { - // rhs == MINIMUM: the quotient is 1 with remainder 0 only when lhs == MINIMUM; - // every other lhs has |lhs| < |MINIMUM|, so the quotient is 0 and the remainder is lhs - if (rhs.upper == INT64_MIN && rhs.lower == 0) { - if (lhs.upper == INT64_MIN && lhs.lower == 0) { - return HugeintDivModResult {hugeint_t(1), hugeint_t(0)}; - } - return HugeintDivModResult {hugeint_t(0), lhs}; - } - - auto shifted = HugeintDivMod(HugeintAddOne(lhs), rhs); - auto quotient = shifted.quotient; - auto remainder = shifted.remainder; - - // If the 1 mattered we need to adjust the result, otherwise the remainder - if (HugeintEquals(HugeintAddOne(HugeintAbs(remainder)), HugeintAbs(rhs))) { - quotient = HugeintSign(rhs) > 0 ? HugeintSubtractOne(quotient) : HugeintAddOne(quotient); - remainder = hugeint_t(0); - } else { - remainder = HugeintSubtractOne(remainder); - } - return HugeintDivModResult {quotient, remainder}; -} - -//! Bit-at-a-time 128-bit division, adapted from -//! https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp -constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs) { - if (HugeintEquals(rhs, hugeint_t(0))) { - return HugeintDivModResult {hugeint_t(0), lhs}; - } - - // MINIMUM cannot be negated, so it has to be handled before any sign is stripped - if (HugeintEquals(lhs, HugeintMinimum()) || HugeintEquals(rhs, HugeintMinimum())) { - return HugeintDivModMinimum(lhs, rhs); - } - - bool lhs_negative = lhs.upper < 0; - bool rhs_negative = rhs.upper < 0; - if (lhs_negative) { - lhs = HugeintNegate(lhs); - } - if (rhs_negative) { - rhs = HugeintNegate(rhs); - } - - hugeint_t quotient {0, 0}; - hugeint_t remainder {0, 0}; - - const uint8_t highest_bit_set = HugeintHighestBit(lhs); - for (uint8_t x = highest_bit_set; x > 0; x--) { - // left-shift the current quotient and remainder by 1 - quotient = HugeintLeftShift(quotient, 1); - remainder = HugeintLeftShift(remainder, 1); - - // we get the value of the bit at position X, where position 0 is the least-significant bit - if (HugeintIsBitSet(lhs, x - 1)) { - remainder.lower += 1; - if (remainder.lower == 0) { - remainder.upper += 1; - } - } - if (HugeintGreaterThanEquals(remainder, rhs)) { - // the remainder has passed the division multiplier: subtract the divisor, - // carrying a borrow out of lower, and add one to the quotient - const int64_t borrow = remainder.lower < rhs.lower ? 1 : 0; - remainder.lower -= rhs.lower; - remainder.upper -= rhs.upper + borrow; - quotient.lower += 1; - if (quotient.lower == 0) { - quotient.upper += 1; - } - } - } - if (lhs_negative ^ rhs_negative) { - quotient = HugeintNegate(quotient); - } - if (lhs_negative) { - remainder = HugeintNegate(remainder); - } - return HugeintDivModResult {quotient, remainder}; -} - -constexpr hugeint_t::hugeint_t(int64_t value) // NOLINT: Allow implicit conversion from `int64_t` - : lower(static_cast(value)), upper(value < 0 ? -1 : 0) { -} - -constexpr bool hugeint_t::operator==(const hugeint_t &rhs) const { - return HugeintEquals(*this, rhs); -} - -constexpr bool hugeint_t::operator!=(const hugeint_t &rhs) const { - return HugeintNotEquals(*this, rhs); -} - -constexpr bool hugeint_t::operator<(const hugeint_t &rhs) const { - return HugeintLessThan(*this, rhs); -} - -constexpr bool hugeint_t::operator<=(const hugeint_t &rhs) const { - return HugeintLessThanEquals(*this, rhs); -} - -constexpr bool hugeint_t::operator>(const hugeint_t &rhs) const { - return HugeintGreaterThan(*this, rhs); -} - -constexpr bool hugeint_t::operator>=(const hugeint_t &rhs) const { - return HugeintGreaterThanEquals(*this, rhs); -} - -constexpr hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { - return hugeint_t(static_cast(static_cast(upper) + static_cast(rhs.upper) + - ((lower + rhs.lower) < lower)), - lower + rhs.lower); -} - -constexpr hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { - return hugeint_t(static_cast(static_cast(upper) - static_cast(rhs.upper) - - ((lower - rhs.lower) > lower)), - lower - rhs.lower); -} - -constexpr hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { - return HugeintMultiplyUnchecked(*this, rhs); -} - -constexpr hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { - // this is the unchecked division: dividing by zero yields 0 and MINIMUM / -1 wraps, - // matching Hugeint::Divide. Callers that need overflow checks use TryDivMod. - return HugeintDivMod(*this, rhs).quotient; -} - -constexpr hugeint_t hugeint_t::operator-() const { - return HugeintNegate(*this); -} - } // namespace duckdb namespace std { diff --git a/src/duckdb/src/include/duckdb/common/mutex.hpp b/src/duckdb/src/include/duckdb/common/mutex.hpp index 9d55a8141..11e3b9bd9 100644 --- a/src/duckdb/src/include/duckdb/common/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/mutex.hpp @@ -34,32 +34,6 @@ class DUCKDB_CAPABILITY("mutex") annotated_mutex : public internal::mutex_impl_t } }; -// Annotated shared_mutex implementation. -class DUCKDB_CAPABILITY("mutex") annotated_shared_mutex : public internal::mutex_impl_t { -private: - using Impl = internal::mutex_impl_t; - -public: - void lock() DUCKDB_ACQUIRE() { - Impl::lock(); - } - void unlock() DUCKDB_RELEASE() { - Impl::unlock(); - } - bool try_lock() DUCKDB_TRY_ACQUIRE(true) { - return Impl::try_lock(); - } - void lock_shared() DUCKDB_ACQUIRE_SHARED() { - Impl::lock_shared(); - } - void unlock_shared() DUCKDB_RELEASE_SHARED() { - Impl::unlock_shared(); - } - bool try_lock_shared() DUCKDB_TRY_ACQUIRE_SHARED(true) { - return Impl::try_lock_shared(); - } -}; - // Annotated lock_guard implementation. template class DUCKDB_SCOPED_CAPABILITY annotated_lock_guard : public internal::lock_impl_t> { @@ -125,50 +99,6 @@ class DUCKDB_SCOPED_CAPABILITY annotated_unique_lock : public internal::lock_imp } }; -// Annotated shared_lock implementation. -template -class DUCKDB_SCOPED_CAPABILITY annotated_shared_lock : public internal::lock_impl_t> { -private: - using Impl = internal::lock_impl_t>; - -public: - annotated_shared_lock() = default; - explicit annotated_shared_lock(M &m) DUCKDB_ACQUIRE_SHARED(m) : Impl(m) { - } - annotated_shared_lock(M &m, std::defer_lock_t t) noexcept DUCKDB_EXCLUDES(m) : Impl(m, t) { - } - annotated_shared_lock(M &m, std::try_to_lock_t t) DUCKDB_TRY_ACQUIRE_SHARED(true, m) : Impl(m, t) { - } - annotated_shared_lock(M &m, std::adopt_lock_t t) DUCKDB_REQUIRES_SHARED(m) : Impl(m, t) { - } - - // Disable copy and enable move. - annotated_shared_lock(const annotated_shared_lock &) = delete; - annotated_shared_lock &operator=(const annotated_shared_lock &) = delete; - annotated_shared_lock(annotated_shared_lock &&) = default; - annotated_shared_lock &operator=(annotated_shared_lock &&) = default; - - ~annotated_shared_lock() DUCKDB_RELEASE() = default; - - void lock() DUCKDB_ACQUIRE_SHARED() { - Impl::lock(); - } - bool try_lock() DUCKDB_TRY_ACQUIRE_SHARED(true) { - return Impl::try_lock(); - } - template - bool try_lock_for(const std::chrono::duration &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { - return Impl::try_lock_for(timeout); - } - template - bool try_lock_until(const std::chrono::time_point &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { - return Impl::try_lock_until(timeout); - } - void unlock() DUCKDB_RELEASE() { - Impl::unlock(); - } -}; - // Unannotated mutex type, which is alias for STL ones. using mutex = std::mutex; template diff --git a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp index 755515ff9..0f9b6a338 100644 --- a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp @@ -497,17 +497,6 @@ struct DecimalCastData { StoreType limit; }; -//! StoreType max/min divided by 10, for the overflow check in DecimalCastOperation::HandleDigit. -//! These are forced to be compile-time constants so the division is folded away: with a hugeint_t -//! store type it is a 128-bit division that would otherwise run for every digit. hugeint_t::operator/ -//! is constexpr, but it is too large to be inlined at the call site, so the compiler does not fold it -//! unless constant evaluation is actually required. -template -struct DecimalCastLimits { - static constexpr T MaximumDiv10 = NumericLimits::Maximum() / 10; - static constexpr T MinimumDiv10 = NumericLimits::Minimum() / 10; -}; - struct DecimalCastOperation { template static bool HandleDigit(T &state, uint8_t digit) { @@ -521,12 +510,12 @@ struct DecimalCastOperation { } state.digit_count++; if (NEGATIVE) { - if (state.result < DecimalCastLimits::MinimumDiv10) { + if (state.result < (NumericLimits::Minimum() / 10)) { return false; } state.result = state.result * 10 - digit; } else { - if (state.result > DecimalCastLimits::MaximumDiv10) { + if (state.result > (NumericLimits::Maximum() / 10)) { return false; } state.result = state.result * 10 + digit; diff --git a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp index 4debd3d80..6e1fb2db5 100644 --- a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp @@ -83,7 +83,7 @@ struct IntegerDecimalCastData { using StoreType = int64_t; StoreType result; StoreType decimal; - idx_t decimal_digits; + uint16_t decimal_digits; }; template <> @@ -92,7 +92,7 @@ struct IntegerDecimalCastData { using StoreType = uint64_t; StoreType result; StoreType decimal; - idx_t decimal_digits; + uint16_t decimal_digits; }; struct IntegerDecimalCastOperation : IntegerCastOperation { @@ -129,12 +129,12 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { } // Handle decimals - auto shift = static_cast(exponent) - static_cast(state.decimal_digits); + e = UnsafeNumericCast(exponent - state.decimal_digits); store_t remainder = 0; - if (shift < 0) { - if (static_cast(-shift) < NumericLimits::Digits()) { + if (e < 0) { + if (static_cast(-e) < NumericLimits::Digits()) { store_t power = 1; - while (shift++ < 0) { + while (e++ < 0) { power *= 10; } remainder = state.decimal % power; @@ -145,7 +145,7 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { state.decimal = 0; } } else { - while (shift-- > 0) { + while (e-- > 0) { if (!TryMultiplyOperator::Operation(state.decimal, (store_t)10, state.decimal)) { return false; } @@ -168,10 +168,7 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { template static bool HandleDecimal(T &state, uint8_t digit) { using store_t = typename T::StoreType; - // digits past this position cannot affect the result for any int16_t exponent - static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); - if (DUCKDB_UNLIKELY(state.decimal_digits == MAX_DECIMAL_DIGITS || - state.decimal > (NumericLimits::Maximum() - digit) / 10)) { + if (DUCKDB_UNLIKELY(state.decimal > (NumericLimits::Maximum() - digit) / 10)) { // Simply ignore any more decimals return true; } @@ -266,10 +263,6 @@ static bool IntegerCastLoop(const char *buf, idx_t len, T &result, bool strict) } } if (StringUtil::CharacterIsSpace(buf[pos])) { - if (pos == start_pos) { - // a sign followed only by whitespace is not a number - return false; - } // skip any trailing spaces while (++pos < len) { if (!StringUtil::CharacterIsSpace(buf[pos])) { diff --git a/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp b/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp deleted file mode 100644 index 773357fdc..000000000 --- a/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp +++ /dev/null @@ -1,192 +0,0 @@ -//===----------------------------------------------------------------------===// -// DuckDB -// -// duckdb/common/sorting/buffered_merge.hpp -// -// -//===----------------------------------------------------------------------===// - -#pragma once - -#include "duckdb/common/algorithm.hpp" -#include "duckdb/common/helper.hpp" -#include "duckdb/common/typedefs.hpp" -#include "duckdb/common/allocator.hpp" -#include "duckdb/common/exception.hpp" - -namespace duckdb { - -//! Scratch buffer for merging sorted runs: the merge is correct without it, so the allocation must never fail a sort. -//! Starting at half the keys, the size is halved until the buffer manager can provide it (or it becomes too small to -//! be worth having). -class SortMergeBuffer { -public: - //! Number of keys the buffer holds at most: the smaller side of a merge is at most half the keys - static idx_t MaximumKeyCount(idx_t key_count) { - return key_count / 2 + 1; - } - //! Memory the buffer takes at most, so that it can be budgeted along with the keys it merges - static idx_t MaximumSize(idx_t key_count, idx_t key_size) { - return MaximumKeyCount(key_count) * key_size; - } - - SortMergeBuffer(Allocator &allocator, idx_t key_count, idx_t key_size) : count(0) { - static constexpr idx_t MINIMUM_KEYS = 1024; - for (idx_t keys = MaximumKeyCount(key_count); keys >= MINIMUM_KEYS; keys /= 2) { - try { - data = allocator.Allocate(keys * key_size); - count = keys; - return; - } catch (OutOfMemoryException &) { - // try a smaller buffer - } - } - } - - template - SORT_KEY *Keys() { - return reinterpret_cast(data.get()); - } - idx_t Count() const { - return count; - } - -private: - AllocatedData data; - idx_t count; -}; - -//! Rotates [first, last) so that [middle, last) comes before [first, middle) and returns the new middle. The smaller -//! of the two pieces goes through the scratch buffer when it fits, otherwise this is a plain std::rotate; neither -//! allocates. -template -ITERATOR BufferedRotate(ITERATOR first, ITERATOR middle, ITERATOR last, SORT_KEY *buffer, const idx_t buffer_count) { - const auto left_count = static_cast(middle - first); - const auto right_count = static_cast(last - middle); - if (left_count > right_count && right_count <= buffer_count) { - std::move(middle, last, buffer); - std::move_backward(first, middle, last); - std::move(buffer, buffer + right_count, first); - return first + right_count; - } - if (left_count <= buffer_count) { - std::move(first, middle, buffer); - std::move(middle, last, first); - std::move_backward(buffer, buffer + left_count, last); - return last - left_count; - } - return std::rotate(first, middle, last); -} - -//! Merges the sorted ranges [first, middle) and [middle, last) in place using the given scratch buffer, which may be -//! any size (including empty): nothing is allocated. When the smaller side fits in the buffer it is moved out and -//! merged back, forward when it is the left side and backward when it is the right side, which keeps equal keys in -//! order. Otherwise the larger side is split in half, the other side is split at the matching key by binary search, -//! the two middle pieces are rotated and both halves are merged recursively, until the pieces fit the buffer. Without a -//! buffer this is the O(n log n) in-place merge that std::inplace_merge uses when it cannot get memory, with a buffer -//! of half the keys it is a single linear pass, and anything in between makes progressively fewer passes. -template -void BufferedMerge(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare, SORT_KEY *buffer, - const idx_t buffer_count) { - while (true) { - const auto left_count = static_cast(middle - first); - const auto right_count = static_cast(last - middle); - if (left_count == 0 || right_count == 0) { - return; - } - // the ranges are already in order, nothing to merge - if (!compare(*middle, *(middle - 1))) { - return; - } - // one key on each side: swap them (the split below needs at least two keys on the larger side to progress) - if (left_count + right_count == 2) { - std::iter_swap(first, middle); - return; - } - if (left_count <= right_count && left_count <= buffer_count) { - // move the left side out and merge forward into [first, last) - std::move(first, middle, buffer); - auto buffer_end = buffer + left_count; - auto buffer_it = buffer; - auto right_it = middle; - auto out = first; - while (buffer_it != buffer_end && right_it != last) { - if (compare(*right_it, *buffer_it)) { - *out = std::move(*right_it); - ++right_it; - } else { - *out = std::move(*buffer_it); - ++buffer_it; - } - ++out; - } - std::move(buffer_it, buffer_end, out); - return; - } - if (right_count <= buffer_count) { - // move the right side out and merge backward into [first, last) - std::move(middle, last, buffer); - auto buffer_begin = buffer; - auto buffer_it = buffer + right_count; - auto left_it = middle; - auto out = last; - while (buffer_it != buffer_begin && left_it != first) { - --out; - if (compare(*(buffer_it - 1), *(left_it - 1))) { - --left_it; - *out = std::move(*left_it); - } else { - --buffer_it; - *out = std::move(*buffer_it); - } - } - // anything left in the buffer goes to the front of the remaining gap - std::move_backward(buffer_begin, buffer_it, out); - return; - } - // neither side fits: split the larger side in half and the other side at the matching key, so that - // everything in [first_cut, middle) is greater than or equal to everything in [middle, second_cut) ... - // (copies of existing iterators: a default-constructed block iterator has no state to iterate over) - ITERATOR first_cut = first; - ITERATOR second_cut = middle; - if (left_count > right_count) { - first_cut = first + left_count / 2; - second_cut = std::lower_bound(middle, last, *first_cut, compare); - } else { - second_cut = middle + right_count / 2; - first_cut = std::upper_bound(first, middle, *second_cut, compare); - } - // ... swap those two pieces, which leaves two independent merges on either side of the new middle - auto new_middle = BufferedRotate(first_cut, middle, second_cut, buffer, buffer_count); - // recurse into the smaller one and loop on the larger one, which bounds the recursion depth by log2(n) - if (new_middle - first < last - new_middle) { - BufferedMerge(first, first_cut, new_middle, compare, buffer, buffer_count); - first = new_middle; - middle = second_cut; - } else { - BufferedMerge(new_middle, second_cut, last, compare, buffer, buffer_count); - last = new_middle; - middle = first_cut; - } - } -} - -//! Merge policy for vergesort (and the sorts it calls into) that merges through a SortMergeBuffer, so that every run -//! merge of a sort, including the nested ones, uses the one buffer that was budgeted for it and nothing else -template -class BufferedMergePolicy { -public: - explicit BufferedMergePolicy(SortMergeBuffer &buffer) : keys(buffer.Keys()), count(buffer.Count()) { - } - - template - void operator()(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare) const { - BufferedMerge(first, middle, last, compare, keys, count); - } - -private: - SORT_KEY *keys; - idx_t count; -}; - -} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp index 560c58078..fedb2dba4 100644 --- a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp +++ b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp @@ -58,8 +58,6 @@ class SortedRun { idx_t Count() const; //! Size of this sorted run idx_t SizeInBytes() const; - //! Size of this sorted run plus the merge buffer that Finalize allocates to sort it - idx_t SizeInBytesForSorting() const; private: mutex merger_global_state_lock; diff --git a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp index 61355a8ac..5bc8261cc 100644 --- a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp @@ -12,21 +12,17 @@ #pragma once #include -#include namespace duckdb { // Forward declaration for annotated mutex types. class annotated_mutex; -class annotated_shared_mutex; // Forward declaration for annotated lock types. template class annotated_unique_lock; template class annotated_lock_guard; -template -class annotated_shared_lock; namespace internal { @@ -44,12 +40,6 @@ struct standard_impl<::duckdb::annotated_mutex> { using type = std::mutex; }; -// Specialization for `std::shared_mutex`. -template <> -struct standard_impl<::duckdb::annotated_shared_mutex> { - using type = std::shared_mutex; -}; - // Type alias for lock types. template using mutex_impl_t = standard_impl_t; @@ -66,12 +56,6 @@ struct standard_impl<::duckdb::annotated_lock_guard> { using type = std::lock_guard>; }; -// Specialization for `std::shared_lock`. -template -struct standard_impl<::duckdb::annotated_shared_lock> { - using type = std::shared_lock>; -}; - template using lock_impl_t = standard_impl_t; diff --git a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp index b1346c193..cf943612d 100644 --- a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp +++ b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp @@ -71,11 +71,11 @@ union arrow_string_view_t { const char *GetInlineData() const { return IsInline() ? inlined.data : ref.prefix; } - int32_t GetBufferIndex() const { + int32_t GetBufferIndex() { D_ASSERT(!IsInline()); return ref.buffer_index; } - int32_t GetOffset() const { + int32_t GetOffset() { D_ASSERT(!IsInline()); return ref.offset; } diff --git a/src/duckdb/src/include/duckdb/common/types/time.hpp b/src/duckdb/src/include/duckdb/common/types/time.hpp index d15c2752d..764ed6857 100644 --- a/src/duckdb/src/include/duckdb/common/types/time.hpp +++ b/src/duckdb/src/include/duckdb/common/types/time.hpp @@ -29,8 +29,7 @@ class Time { optional_ptr nanos = nullptr); // No hour limit DUCKDB_API static bool TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, - bool strict = false, optional_ptr nanos = nullptr, - bool negative = false); + bool strict = false, optional_ptr nanos = nullptr); //! Convert a time object to a string in the format "hh:mm:ss" DUCKDB_API static string ToString(dtime_t time); @@ -57,7 +56,7 @@ class Time { private: static bool TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos = nullptr, bool negative = false); + optional_ptr nanos = nullptr); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp index a2b86440c..96c1612de 100644 --- a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp +++ b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp @@ -74,8 +74,6 @@ struct ExecuteFunctionState : public ExpressionState { public: unique_ptr local_state; - //! The buffers of all-constant arguments, which can be shared with the input and have to get their size back - vector> constant_argument_buffers; private: //! The column index of the "unary" input column that may be a dictionary vector diff --git a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp index 15860231e..1fe4827a9 100644 --- a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp +++ b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp @@ -554,12 +554,10 @@ class JoinHashTable { //! An empty tuple that's a "dead end", can be used to stop chains early unsafe_unique_array dead_end; - //! Bloom filter over the join keys. An external join fills it during Sink, an in-memory join during - //! finalize. Shared by every thread-local hash table of this operator, and shared again with any scan it - //! gets published to, so it outlives a reset of this table - shared_ptr bloom_filter; - //! Whether finalize should build the bloom filter from the materialized build side + //! Whether or not to use a bloom filter will be determined by the operator + BloomFilter bloom_filter; bool should_build_bloom_filter = false; + idx_t bloom_filter_init_count = 0; unique_ptr prefix_range_filter; bool should_build_prefix_range_filter = false; @@ -641,27 +639,14 @@ class JoinHashTable { return PointerTableCapacity(count) * sizeof(data_ptr_t); } - void SetBloomFilter(shared_ptr filter) { - bloom_filter = std::move(filter); - } - //! Requests a filter built during finalize, from the materialized build side (in-memory joins only) void SetBuildBloomFilter(const bool should_build) { this->should_build_bloom_filter = should_build; } + void PrepareBloomFilterForFinalize(); - shared_ptr GetBloomFilter() { + BloomFilter &GetBloomFilter() { return bloom_filter; } - //! Marks the bloom filter complete. The reference stays, because the thread-local tables hold it too - void SealBloomFilter() { - if (bloom_filter) { - bloom_filter->Seal(context); - } - } - //! Whether the filter is complete and rejects enough for pushing it into a scan to pay off - bool HasPublishableBloomFilter() const { - return bloom_filter && bloom_filter->IsSealed() && bloom_filter->IsUseful(); - } void SetPrefixRangeFilter(unique_ptr filter) { prefix_range_filter = std::move(filter); diff --git a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp index 9ed0dc06b..4bb415cb7 100644 --- a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp +++ b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp @@ -506,7 +506,6 @@ pair MergeSortTree::SelectNth(const SubFrames &fr // Go over all children until we found enough in range const auto *level_data = tree[level_no].first.data(); - const auto level_size = tree[level_no].first.size(); while (true) { idx_t matched = 0; std::array matches; @@ -527,8 +526,7 @@ pair MergeSortTree::SelectNth(const SubFrames &fr matched += idx_t(match.second - match.first); } - // Move down if there are enough in this child, or if this is the last child of the level - if (matched > n || (result + 1) * level_width >= level_size) { + if (matched > n) { // Too much in this level, so move down to leftmost child candidate within the cascade range for (idx_t f = 0; f < frames.size(); ++f) { auto &cascade_idx = cascades[f]; diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp index 1671b85fc..d2eab3a84 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp @@ -27,13 +27,13 @@ class PhysicalBatchInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality); //! The table to insert into - mutable optional_ptr insert_table; + optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table vector> bound_constraints; //! Table schema, in case of CREATE TABLE AS - mutable optional_ptr schema; + optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Preferred input batch size diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp index 965f4ff11..b67be1ab7 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp @@ -87,7 +87,7 @@ class PhysicalInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality, bool parallel); //! The table to insert into - mutable optional_ptr insert_table; + optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table @@ -95,7 +95,7 @@ class PhysicalInsert : public PhysicalOperator { //! If the returning statement is present, return the whole chunk bool return_chunk; //! Table schema, in case of CREATE TABLE AS - mutable optional_ptr schema; + optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Whether or not the INSERT can be executed in parallel diff --git a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp index 417427b69..b12125a65 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp @@ -24,7 +24,6 @@ class PhysicalProjection : public PhysicalOperator { vector> select_list; public: - void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const override; unique_ptr GetOperatorState(ExecutionContext &context) const override; OperatorResultType Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state) const override; diff --git a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp index df0a6d347..ddb6e10b1 100644 --- a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp +++ b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp @@ -91,11 +91,6 @@ class PhysicalOperator { return false; } - //! Initialize the vectors this operator uses for its output - virtual void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const { - chunk.Initialize(allocator, types); - } - //! Functions to help decide how to set up pipeline dependencies idx_t EstimatedThreadCount() const; bool CanSaturateThreads(ClientContext &context) const; diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp index 25faa7444..104c71ae4 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp @@ -162,8 +162,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res VarintEncode(length, str_blob_data); memcpy(str_blob_data + length_varint_size, const_data_ptr_cast(str), length); } - OffsetData::AddBlobOffset(blob_offset_data[result_index], length_varint_size); - OffsetData::AddBlobOffset(blob_offset_data[result_index], length); + blob_offset_data[result_index] += length_varint_size + length; break; } case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: { @@ -183,7 +182,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_uint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(uint64_t)); } - OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(uint64_t)); + blob_offset_data[result_index] += sizeof(uint64_t); break; } case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: { @@ -193,7 +192,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_sint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(int64_t)); } - OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(int64_t)); + blob_offset_data[result_index] += sizeof(int64_t); break; } case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: @@ -212,7 +211,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res } memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(double)); } - OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(double)); + blob_offset_data[result_index] += sizeof(double); break; } default: diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp index 86abb7d4b..0c972435e 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp @@ -16,14 +16,6 @@ struct EnumConversionPayload { EnumConversionPayload(const string_t *values, idx_t size) : values(values), size(size) { } -public: - const string_t &GetString(idx_t index) const { - if (index >= size) { - throw InternalException("ENUM index %llu is out of range for an ENUM with %llu values", index, size); - } - return values[index]; - } - public: const string_t *values; idx_t size; @@ -109,19 +101,19 @@ static void WriteData(data_ptr_t ptr, const T &val, uint32_t lengths[3], EnumCon template <> void WriteData(data_ptr_t ptr, const uint8_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint16_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint32_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; WriteData(ptr, str, lengths, empty_payload); } @@ -192,19 +184,19 @@ static void GetValueSize(const T &val, uint32_t lengths[3], idx_t &lengths_size, template <> void GetValueSize(const uint8_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint16_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint32_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.GetString(val); + auto &str = payload.values[val]; GetValueSize(str, lengths, lengths_size, empty_payload); } @@ -223,10 +215,9 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob auto &variant = result.variant; uint32_t lengths[3]; + idx_t lengths_size = 0; for (idx_t i = 0; i < count; i++) { auto index = source[i]; - // the blob space to reserve for this row - nothing for NULL rows - idx_t lengths_size = 0; auto result_index = selvec ? selvec->get_index(i) : i; auto &blob_offset = blob_offset_data[result_index]; @@ -234,6 +225,7 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob if (TYPE_ID != VariantLogicalType::VARIANT_NULL && source_validity.RowIsValid(index)) { //! Write the value auto &val = source_data[index]; + lengths_size = 0; GetValueSize(val, lengths, lengths_size, payload); WriteVariantMetadata(result, result_index, values_offset_data, blob_offset, values_index_selvec, i, GetTypeId(val)); diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp index 65ead7f16..83c37856a 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp @@ -74,7 +74,8 @@ template void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t &blob_offset, idx_t length, idx_t children_offset) { const auto length_varint_size = GetVarintSize(length); - const auto child_offset_varint_size = NumericCast(length ? GetVarintSize(children_offset) : 0); + const auto child_offset_varint_size = + NumericCast(length_varint_size ? GetVarintSize(children_offset) : 0); if (WRITE_DATA) { auto &blob_value = result.blob_data[result_index]; @@ -85,7 +86,7 @@ void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t } } - OffsetData::AddBlobOffset(blob_offset, length_varint_size + child_offset_varint_size); + blob_offset += length_varint_size + child_offset_varint_size; } struct ContainerSelectionVectors { diff --git a/src/duckdb/src/include/duckdb/function/compression_function.hpp b/src/duckdb/src/include/duckdb/function/compression_function.hpp index 0068ee52b..54cebc0c5 100644 --- a/src/duckdb/src/include/duckdb/function/compression_function.hpp +++ b/src/duckdb/src/include/duckdb/function/compression_function.hpp @@ -10,7 +10,6 @@ #include "duckdb/common/common.hpp" #include "duckdb/common/enums/compression_type.hpp" -#include "duckdb/common/enums/scan_vector_type.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/insertion_order_preserving_map.hpp" #include "duckdb/common/mutex.hpp" @@ -163,11 +162,8 @@ typedef void (*compression_scan_vector_t)(ColumnSegment &segment, ColumnScanStat typedef void (*compression_scan_partial_t)(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset); //! Function prototype used for reading a subset of the values of a vector indicated by a selection vector -//! The selected values are written to 'result' starting at 'result_offset'. Emitting a compressed vector (e.g. a -//! constant vector) is only allowed if 'scan_type' is SCAN_ENTIRE_VECTOR typedef void (*compression_select_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, - ScanVectorType scan_type); + const SelectionVector &sel, idx_t sel_count); //! Function prototype used for applying a filter to a vector while scanning that vector typedef void (*compression_filter_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, diff --git a/src/duckdb/src/include/duckdb/function/function.hpp b/src/duckdb/src/include/duckdb/function/function.hpp index affaae7c9..aab9fa6b5 100644 --- a/src/duckdb/src/include/duckdb/function/function.hpp +++ b/src/duckdb/src/include/duckdb/function/function.hpp @@ -56,14 +56,10 @@ enum class FunctionStability : uint8_t { CONSISTENT = 0, VOLATILE = 1, CONSISTEN //! PROPAGATE_COLLATIONS -> this function combines collation from its inputs and emits them again (default) //! PUSH_COMBINABLE_COLLATIONS -> combinable collations are executed for the input arguments //! IGNORE_COLLATIONS -> collations are completely ignored by the function -//! PUSH_DISTINCT_COLLATIONS -> aggregate DISTINCT arguments are replaced by their comparison keys -//! Only valid for aggregates that can consume keys instead of original values. -//! Ordinary (non-DISTINCT) calls keep their original arguments. enum class FunctionCollationHandling : uint8_t { PROPAGATE_COLLATIONS = 0, PUSH_COMBINABLE_COLLATIONS = 1, - IGNORE_COLLATIONS = 2, - PUSH_DISTINCT_COLLATIONS = 3 + IGNORE_COLLATIONS = 2 }; struct FunctionData { diff --git a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp index 6e77374f9..4a4361232 100644 --- a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp +++ b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp @@ -16,7 +16,6 @@ namespace duckdb { class BoundFunctionExpression; -struct SortedAggregateStateOrder; struct ConstantOrNull { static bool IsConstantOrNull(BoundFunctionExpression &expr, const Value &val); @@ -33,8 +32,6 @@ struct ExportAggregateFunction { static unique_ptr Bind(unique_ptr child_aggregate); static void SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout); static unique_ptr StateToSQL(const LogicalType &type, unique_ptr value); - //! The per-key (column, modifiers) of the AGGREGATE_STATE type of an ordered aggregate - static void GetSortedStateOrders(const LogicalType &state_type, vector &orders); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/logging/log_type.hpp b/src/duckdb/src/include/duckdb/logging/log_type.hpp index bf8758d60..94bfbef13 100644 --- a/src/duckdb/src/include/duckdb/logging/log_type.hpp +++ b/src/duckdb/src/include/duckdb/logging/log_type.hpp @@ -17,7 +17,6 @@ namespace duckdb { struct FileHandle; struct BaseRequest; struct HTTPResponse; -struct MetricTiming; class PhysicalOperator; enum class PhysicalOperatorType : uint8_t; class AttachedDatabase; @@ -126,8 +125,7 @@ class MetricsLogType : public LogType { static LogicalType GetLogType(); - static string ConstructLogMessage(const string &metric, const Value &value, - optional_ptr timing = nullptr); + static string ConstructLogMessage(const string &metric, const Value &value); }; class CheckpointLogType : public LogType { diff --git a/src/duckdb/src/include/duckdb/main/client_context.hpp b/src/duckdb/src/include/duckdb/main/client_context.hpp index 62ef1312e..42984f6e5 100644 --- a/src/duckdb/src/include/duckdb/main/client_context.hpp +++ b/src/duckdb/src/include/duckdb/main/client_context.hpp @@ -161,8 +161,6 @@ class ClientContext : public enable_shared_from_this { //! Check for interrupt or timeout, throws InterruptException if triggered DUCKDB_API void InterruptCheck() const; - //! InterruptCheck that reads the clock on every call, for waits that block between checks - DUCKDB_API void BlockingInterruptCheck() const; //! Enable query profiling DUCKDB_API void EnableProfiling(); diff --git a/src/duckdb/src/include/duckdb/main/config.hpp b/src/duckdb/src/include/duckdb/main/config.hpp index 29c921aca..ff6e23f80 100644 --- a/src/duckdb/src/include/duckdb/main/config.hpp +++ b/src/duckdb/src/include/duckdb/main/config.hpp @@ -172,45 +172,6 @@ struct DBConfigOptions { bool operator==(const DBConfigOptions &other) const; }; -//! The replacement scans of a database. Scans can be registered while queries are being bound, so the list is -//! guarded by a lock and the binder iterates over a snapshot. -class ReplacementScanList { -public: - ReplacementScanList() = default; - ReplacementScanList(ReplacementScanList &&other) noexcept { - lock_guard guard(other.lock); - scans = std::move(other.scans); - } - ReplacementScanList &operator=(ReplacementScanList &&other) noexcept { - if (this != &other) { - lock_guard other_guard(other.lock); - lock_guard guard(lock); - scans = std::move(other.scans); - } - return *this; - } - - template - void emplace_back(ARGS &&...args) { // NOLINT - auto scan = make_shared_ptr(std::forward(args)...); - lock_guard guard(lock); - scans.push_back(std::move(scan)); - } - void push_back(ReplacementScan scan) { // NOLINT - auto shared_scan = make_shared_ptr(std::move(scan)); - lock_guard guard(lock); - scans.push_back(std::move(shared_scan)); - } - vector> Snapshot() const { - lock_guard guard(lock); - return scans; - } - -private: - mutable mutex lock; - vector> scans; -}; - struct DBConfig { friend class DatabaseInstance; friend class StorageManager; @@ -222,7 +183,7 @@ struct DBConfig { DUCKDB_API ~DBConfig(); //! Replacement table scans are automatically attempted when a table name cannot be found in the schema - ReplacementScanList replacement_scans; + vector replacement_scans; //! The FileSystem to use, can be overwritten to allow for injecting custom file systems for testing purposes (e.g. //! RamFS or something similar) @@ -359,21 +320,16 @@ struct DBConfig { //! Returns the default value of an option static SettingLookupResult TryGetDefaultValue(optional_ptr option, Value &result); - //! The opener (if any) is used to expand "~" using the connection-specific home_directory - bool CanAccessFile(const string &path, FileType type, optional_ptr opener = nullptr); + bool CanAccessFile(const string &path, FileType type); void AddAllowedConfig(const Identifier &config_name); void AddAllowedDirectory(const string &path); void AddAllowedPath(const string &path); - //! Whether the given optimizer is disabled (disabled_optimizers) - DUCKDB_API bool IsOptimizerDisabled(OptimizerType type) const; - DUCKDB_API set GetDisabledOptimizers() const; - DUCKDB_API void SetDisabledOptimizers(set disabled_optimizers); //! Allows a database file and its WAL files, so a database can be opened while external access is disabled. //! Only possible through API calls, not SQL calls. void AddAllowedDatabasePath(const string &database_path); vector GetAllowedDirectories() const; vector GetAllowedPaths() const; - string SanitizeAllowedPath(const string &path, optional_ptr opener = nullptr) const; + string SanitizeAllowedPath(const string &path) const; ExtensionCallbackManager &GetCallbackManager(); const ExtensionCallbackManager &GetCallbackManager() const; @@ -389,8 +345,6 @@ struct DBConfig { mutable mutex config_lock; //! Guards allowed_paths and allowed_directories, which a running instance can extend while files are being opened mutable mutex allowed_paths_lock; - //! Guards disabled_optimizers, which can be changed while other connections plan queries - mutable mutex disabled_optimizers_lock; unique_ptr compression_functions; unique_ptr encoding_functions; unique_ptr arrow_extensions; diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index 0671844d9..ceb405b13 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -717,12 +717,10 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"pi", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"position", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_clear_cache", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, - {"postgres_close_pinned_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_configure_pool", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_execute", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_hstore_get", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_hstore_to_json", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, - {"postgres_pin_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_query", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"pow", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"power", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -1359,7 +1357,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"mysql_bit1_as_boolean", "mysql_scanner"}, {"mysql_debug_show_queries", "mysql_scanner"}, {"mysql_enable_filter_pushdown", "mysql_scanner"}, - {"mysql_enable_remote_pushdown", "mysql_scanner"}, + {"mysql_enable_remote_pushdown_ddl", "mysql_scanner"}, {"mysql_enable_transactions", "mysql_scanner"}, {"mysql_incomplete_dates_as_nulls", "mysql_scanner"}, {"mysql_pool_acquire_mode", "mysql_scanner"}, @@ -1379,12 +1377,13 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_connection_cache", "postgres_scanner"}, {"pg_connection_limit", "postgres_scanner"}, {"pg_debug_show_queries", "postgres_scanner"}, - {"pg_enable_filter_pushdown", "postgres_scanner"}, + {"pg_experimental_filter_pushdown", "postgres_scanner"}, {"pg_idle_in_transaction_timeout_millis", "postgres_scanner"}, {"pg_null_byte_replacement", "postgres_scanner"}, {"pg_numeric_as_varchar", "postgres_scanner"}, {"pg_numeric_nan_as_null", "postgres_scanner"}, {"pg_oauth_token", "postgres_scanner"}, + {"pg_order_pushdown", "postgres_scanner"}, {"pg_pages_per_task", "postgres_scanner"}, {"pg_pool_acquire_mode", "postgres_scanner"}, {"pg_pool_enable_reaper_thread", "postgres_scanner"}, @@ -1394,7 +1393,6 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_pool_max_connections", "postgres_scanner"}, {"pg_pool_max_lifetime_millis", "postgres_scanner"}, {"pg_pool_wait_timeout_millis", "postgres_scanner"}, - {"pg_query_mode", "postgres_scanner"}, {"pg_staleness_query", "postgres_scanner"}, {"pg_staleness_query_enabled", "postgres_scanner"}, {"pg_statement_timeout_millis", "postgres_scanner"}, diff --git a/src/duckdb/src/include/duckdb/main/extension_manager.hpp b/src/duckdb/src/include/duckdb/main/extension_manager.hpp index d45cc4e4b..b7c0f731b 100644 --- a/src/duckdb/src/include/duckdb/main/extension_manager.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_manager.hpp @@ -32,16 +32,10 @@ class ExtensionActiveLoad { ExtensionActiveLoad(DatabaseInstance &db, ExtensionInfo &info, Identifier extension_name_p, Identifier alias_p) : db(db), load_lock(info.lock), info(info), extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; - ExtensionActiveLoad(DatabaseInstance &db, shared_ptr info_p, Identifier extension_name_p, - Identifier alias_p) - : db(db), info_owner(std::move(info_p)), load_lock(info_owner->lock), info(*info_owner), - extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; ~ExtensionActiveLoad() = default; DatabaseInstance &db; - //! Keeps the info alive when the extension is removed from the manager during the load - shared_ptr info_owner; unique_lock load_lock; ExtensionInfo &info; Identifier extension_name; @@ -58,7 +52,7 @@ class ExtensionManager { DUCKDB_API bool ExtensionIsLoaded(const string &name); DUCKDB_API vector GetExtensions(); - DUCKDB_API shared_ptr GetExtensionInfo(const string &name); + DUCKDB_API optional_ptr GetExtensionInfo(const string &name); DUCKDB_API unique_ptr BeginLoad(const ExtensionLoadOptions &options); DUCKDB_API static ExtensionManager &Get(DatabaseInstance &db); @@ -69,7 +63,7 @@ class ExtensionManager { private: DatabaseInstance &db; mutex lock; - identifier_map_t> loaded_extensions_info; + identifier_map_t> loaded_extensions_info; string extension_load_prefix; }; diff --git a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp index 7ea6228e3..42305444e 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp @@ -9,9 +9,6 @@ #pragma once #include "duckdb/common/common.hpp" -#include "duckdb/common/mutex.hpp" -#include "duckdb/common/shared_ptr.hpp" -#include "duckdb/common/time_point.hpp" #include namespace duckdb { @@ -19,116 +16,32 @@ namespace duckdb { struct HTTPParams; //! An attempt's decision; constructing one does not consume retry budget. -//! -//! The loop that runs an operation is its coordinator: after inspecting a response (status, headers and body) it -//! decides explicitly whether the operation succeeded, failed for good, is retried, or is re-sent corrected. The budget -//! only admits retries, waits between them and keeps the host quota's books, refunding once after a reported success. -//! Finish() is the decision of a loop that does not judge the operation itself (core's request loop, or a provider -//! that has not adopted the explicit protocol): with a status-owned budget the quota is then settled from the HTTP -//! status core recorded; a coordinator-owned budget waits for the coordinator's report. class HTTPRetryDecision { friend class HTTPRetryBudget; friend class HTTPUtil; public: - //! Stop without judging the operation: a nested loop leaves the decision to the coordinator. The outermost loop of - //! a status-owned budget settles the quota from the HTTP status core recorded (the pre-protocol behaviour); a - //! coordinator-owned budget stays unsettled until ReportSuccess() or ReportFailure(). DUCKDB_API static HTTPRetryDecision Finish(); - //! The operation succeeded: the coordinator validated the response, the quota is refunded once. Only the owner - //! of a coordinator-owned budget decides this. - DUCKDB_API static HTTPRetryDecision Success(); - //! The operation failed for good (e.g. an error document behind HTTP 200): nothing is refunded. Only the owner of - //! a coordinator-owned budget decides this. - DUCKDB_API static HTTPRetryDecision Failure(); - //! Re-send after correcting the request (credentials, region, endpoint): no retry is consumed and no back-off is - //! waited, bounded by HTTPRetryBudget::MAX_CORRECTIONS per operation. - DUCKDB_API static HTTPRetryDecision Correction(); - //! Retry after a received server error (e.g. HTTP 500). DUCKDB_API static HTTPRetryDecision Retry(); - //! Retry after a transport failure (connection error, timeout, truncated body): no response was received. - DUCKDB_API static HTTPRetryDecision Transient(); - //! Retry after a throttling response (HTTP 429/503), honoring an optional Retry-After header value. - DUCKDB_API static HTTPRetryDecision Throttled(const string &retry_after = string()); private: - enum class Type : uint8_t { FINISH, SUCCESS, FAILURE, CORRECTION, RETRY, TRANSIENT, THROTTLED }; + enum class Type : uint8_t { FINISH, RETRY, THROTTLED }; explicit HTTPRetryDecision(Type type_p, string retry_after_p = {}) : type(type_p), retry_after(std::move(retry_after_p)) { } + static HTTPRetryDecision Throttled(const string &retry_after); private: Type type; string retry_after; }; -//! Retry tokens shared by every operation against one host, after the AWS SDK retry quota. -//! Retries spend tokens and successes refund them, so a host that keeps failing stops being retried. -//! A host nobody has talked to for a while starts over with a full quota, like the CRT drops an idle host's bucket. -class HTTPRetryQuota { -public: - static constexpr int64_t INITIAL_CAPACITY = 500; - //! Cost of retrying a received server error or throttle: the server answered, it is just unhappy. - static constexpr int64_t RETRY_COST = 5; - //! Cost of retrying a transport failure: nothing is known about the server, so it costs more. - static constexpr int64_t TRANSIENT_RETRY_COST = 10; - //! Refund for a request that succeeded without retrying. - static constexpr int64_t NO_RETRY_REFUND = 1; - //! A quota untouched for this long is full again when it is next used. - static constexpr int64_t IDLE_RESET_MS = 60 * 1000; - -public: - //! Take `cost` tokens; false (and nothing taken) when fewer are available. - DUCKDB_API bool TryAcquire(int64_t cost, int64_t now_ms = TimePoint::GetTickMs()); - //! Return tokens, never above the initial capacity. - DUCKDB_API void Release(int64_t amount, int64_t now_ms = TimePoint::GetTickMs()); - DUCKDB_API int64_t Available() const; - //! Milliseconds since the quota was last charged or refunded. - DUCKDB_API int64_t IdleMs(int64_t now_ms = TimePoint::GetTickMs()) const; - -private: - //! Refill an idle quota and record the use; the lock must be held. - void Touch(int64_t now_ms); - -private: - mutable mutex lock; - int64_t capacity = INITIAL_CAPACITY; - int64_t last_used_ms = TimePoint::GetTickMs(); -}; - -//! Who settles an operation's outcome with the host quota, fixed when the operation starts. -enum class HTTPRetryOutcomeOwner : uint8_t { - //! Nobody validates beyond the HTTP status: the status core recorded settles the quota once the outermost loop - //! stops. Core's own request loop and loops that have not adopted the explicit protocol. - STATUS, - //! The coordinator that runs the operation validates the response and settles the quota itself, with a - //! Success/Failure decision or ReportSuccess/ReportFailure after its loop; core's status is never final for it. - //! An operation whose coordinator never reports stays charged, like one whose outcome is unknown. - COORDINATOR -}; - -//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. One budget is one -//! logical operation (one `amz-sdk-invocation-id`): every send it makes, including corrected re-sends, is one of -//! its attempts, as in the AWS SDK's attempt loop. The host quota is settled once per operation, when its outcome is -//! known, by the owner fixed at construction: a coordinator reports it (Success/Failure decisions or -//! ReportSuccess/ReportFailure, whenever it has validated the response, inside or after its loop); for a status-owned -//! budget it is taken from the HTTP status core recorded (RecordStatusOutcome). A status recorded inside an outer Run -//! waits for that loop to stop, since an outer loop may still retry. +//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. class HTTPRetryBudget { friend class HTTPUtil; public: - //! Longest single back-off sleep, as in the AWS SDKs. - static constexpr uint64_t MAX_BACKOFF_MS = 20000; - //! Retries granted beyond `http_retries` for throttled responses: the server asked to slow down, not to stop. - static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; - //! Corrected re-sends admitted per operation: a credential refresh and a region redirect, with room for a - //! provider that needs both again after the other; past this the operation ends with the last response. - static constexpr uint64_t MAX_CORRECTIONS = 4; - -public: - DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms, - HTTPRetryOutcomeOwner owner = HTTPRetryOutcomeOwner::STATUS); + DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms); HTTPRetryBudget(const HTTPRetryBudget &) = delete; HTTPRetryBudget &operator=(const HTTPRetryBudget &) = delete; @@ -138,88 +51,21 @@ class HTTPRetryBudget { DUCKDB_API void Run(const std::function &attempt); //! Retries admitted for HTTP 429 or 503 responses. DUCKDB_API uint64_t ThrottledRetries() const; - //! Retries admitted across all participating requests. - DUCKDB_API uint64_t RetriesUsed() const; - //! Number the next send of the operation: the first call returns 1. - DUCKDB_API uint64_t BeginAttempt(); - //! Sends so far that were neither the first nor an admitted retry: re-sends after the coordinator corrected the - //! request (credentials, region), which do not consume retries. - DUCKDB_API uint64_t Corrections() const; - //! Most attempts the operation can make: the first one, `http_retries`, the throttle extras and the corrected - //! re-sends made so far, so that every numbered attempt stays within the bound advertised with it. - DUCKDB_API uint64_t MaxAttempts() const; - //! Whether the current Run refused a retry because the host's quota ran out. - DUCKDB_API bool QuotaExhausted() const; - //! Whether the current Run refused a corrected re-send because MAX_CORRECTIONS were made already. - DUCKDB_API bool CorrectionsExhausted() const; - //! Charge the next admitted retries to this host's quota; a request to another host rebinds. - DUCKDB_API void BindQuota(shared_ptr quota); - //! Who settles this operation's outcome. - DUCKDB_API HTTPRetryOutcomeOwner Owner() const; - //! The coordinator's final word, for a coordinator that validates the response after its Run returned: the - //! operation succeeded, refund the last admitted retry to the quota it was charged to, or NO_RETRY_REFUND to the - //! bound quota. Settles once; a later report of the same operation changes nothing. Coordinator-owned budgets only. - DUCKDB_API void ReportSuccess(); - //! The coordinator's final word: the operation failed, the last admitted retry is not refunded. Coordinator-owned - //! budgets only. - DUCKDB_API void ReportFailure(); - //! What the HTTP status of the last response says, recorded by core's request loop. For a status-owned budget it - //! settles the quota, at once outside a Run or when the outermost Run stops; a coordinator-owned budget ignores - //! it, its coordinator judges the response. - DUCKDB_API void RecordStatusOutcome(bool success); - //! Whether a Run of this budget is in progress, i.e. a nested loop is running inside an outer one. - DUCKDB_API bool InRun() const; - - //! Back-off window for `retry` is [0, min(wait * backoff^(retry - 1), MAX_BACKOFF_MS)]: full jitter, or its upper - //! half for throttled retries so they always keep some distance. The result is raised to `floor_ms`. - DUCKDB_API static uint64_t BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, - uint64_t random_value, bool throttled = false); - //! Parse a Retry-After header (delay in seconds or an HTTP-date) into a delay in milliseconds. - DUCKDB_API static bool TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms); private: //! Core's hook runs after admission/backoff, outside the attempt's transport-error handling. void Run(const std::function &attempt, const std::function &before_retry); bool ConsumeAndWait(const HTTPRetryDecision &decision); - //! Admit a corrected re-send within MAX_CORRECTIONS. - bool AdmitCorrection(); - uint64_t ThrottleExtraRetries() const; - //! Settle the operation's outcome with the quota once: refund on success, then forget the last admitted retry. - void Settle(bool success); - //! Explicit decisions and reports belong to a coordinator-owned budget; anything else is a programming error. - void RequireCoordinator(const char *what) const; - //! Forget the last admitted retry, so that nothing is refunded for it. - void ForgetLastRetry(); private: //! Retry settings captured for the operation. const uint64_t retries; const uint64_t retry_wait_ms; const float retry_backoff; - //! Who settles the outcome with the quota. - const HTTPRetryOutcomeOwner owner; //! Retries admitted across all participating requests. uint64_t retries_used = 0; uint64_t throttled_retries = 0; - //! Sends numbered so far. - uint64_t attempts = 0; - //! Corrected re-sends admitted by Correction() decisions. - uint64_t corrections_admitted = 0; - //! Nesting depth of Run; status outcomes recorded while this is positive wait for the outermost loop's decision. - uint64_t run_depth = 0; - //! Outcome core recorded from the HTTP status, pending the outermost loop's stop (status-owned budgets only). - enum class RecordedOutcome : uint8_t { NONE, SUCCESS, FAILURE }; - RecordedOutcome status_outcome = RecordedOutcome::NONE; - //! Whether the operation's outcome has been settled with the quota; a new admitted retry starts a new settlement. - bool outcome_settled = false; - //! Quota of the host the current request goes to, if any. - shared_ptr quota; - //! Quota and tokens of the most recent admitted retry, refunded on success. - shared_ptr last_retry_quota; - int64_t last_retry_cost = 0; - bool quota_exhausted = false; - bool corrections_exhausted = false; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/http/http_util.hpp b/src/duckdb/src/include/duckdb/main/http/http_util.hpp index 40385438e..309dcfe77 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_util.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_util.hpp @@ -14,9 +14,6 @@ #include "duckdb/common/enums/http_status_code.hpp" #include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/time_point.hpp" -#include "duckdb/common/mutex.hpp" -#include "duckdb/common/unordered_map.hpp" -#include "duckdb/main/http/http_retry_budget.hpp" #include namespace duckdb { @@ -402,18 +399,8 @@ class HTTPUtil { virtual unique_ptr SendRequest(BaseRequest &request, unique_ptr &client); virtual void LogRequest(BaseRequest &request, optional_ptr response); - //! Whether a failed request should be retried. Judges received responses and statuses a handler turned into an - //! exception alike; core knows only the generic statuses (IsRetryableStatus), a provider adds what its service - //! says in the body (e.g. an S3 error document whose code is retryable behind a final-looking status). + //! Whether a failed request should be retried, possibly using HTTPResponse information, and allowing overrides DUCKDB_API virtual bool ShouldRetry(const BaseRequest &request, const HTTPResponse &response); - //! Runs before every attempt with the 1-based attempt number and the configured maximum, e.g. to tag headers. - DUCKDB_API virtual void PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts); - //! Retry quota shared by all requests of this provider against one host. - DUCKDB_API shared_ptr GetRetryQuota(const string &proto_host_port); - //! Whether a received status is worth retrying: 408, 418, 429 and the server errors that can clear up. - DUCKDB_API static bool IsRetryableStatus(HTTPStatusCode status); - //! Hosts remembered for retry quotas; past this the host unused for longest makes room. - static constexpr idx_t MAX_RETRY_QUOTAS = 1024; //! Whether replaying this request is safe. POST is the only method we cannot assume is idempotent. DUCKDB_API static bool IsIdempotent(RequestType type); @@ -432,15 +419,9 @@ class HTTPUtil { HTTPClientCachePolicy initial_cache_policy, HTTPClientCachePolicy &retry_cache_policy); -private: - //! Protects retry_quotas. - mutex retry_quota_lock; - //! Per-host retry quotas, created on first use; budgets keep theirs alive across an eviction. - unordered_map> retry_quotas; - public: static duckdb::unique_ptr - RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, + RunRequestWithRetry(const std::function(void)> &on_request, const BaseRequest &request, const std::function &retry_cb); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp index 9e0261a74..d9319c0db 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp @@ -12,7 +12,6 @@ #include "duckdb/common/enums/output_type.hpp" #include "duckdb/common/progress_bar/progress_bar.hpp" #include "duckdb/common/types/value.hpp" -#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unordered_set.hpp" #include "duckdb/common/constants.hpp" #include "duckdb/common/enums/metric_type.hpp" @@ -29,41 +28,6 @@ namespace duckdb { struct QueryProfileResult; enum class ProfilingParameterNames : uint8_t { FORMAT, COVERAGE, SAVE_LOCATION, MODE, METRICS }; -struct MetricTiming { - idx_t duration = 0; - timestamp_t start_time = timestamp_t::epoch(); - timestamp_t end_time = timestamp_t::epoch(); - bool has_bounds = false; - - void Add(idx_t addition) { - duration += addition; - } - - void Add(idx_t addition, timestamp_t start, timestamp_t end) { - Add(addition); - UpdateBounds(start, end); - } - - void Merge(const MetricTiming &other) { - Add(other.duration); - if (other.has_bounds) { - UpdateBounds(other.start_time, other.end_time); - } - } - -private: - void UpdateBounds(timestamp_t start, timestamp_t end) { - if (!has_bounds) { - start_time = start; - end_time = end; - has_bounds = true; - return; - } - start_time = MinValue(start_time, start); - end_time = MaxValue(end_time, end); - } -}; - class GatheredMetrics { public: GatheredMetrics() = default; @@ -108,7 +72,7 @@ class GatheredMetrics { } public: - void WriteMetricsToLog(ClientContext &context, const unordered_map &timings) const; + void WriteMetricsToLog(ClientContext &context) const; //! Copy all enabled metrics into a QueryProfileResult node using lowercase string keys void MetricsToProfileResult(QueryProfileResult &result) const; diff --git a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp index fa5fdcc00..383266135 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp @@ -13,7 +13,6 @@ #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/profiler/profiling_node.hpp" #include "duckdb/common/profiler.hpp" -#include "duckdb/common/types/timestamp.hpp" namespace duckdb_yyjson { struct yyjson_mut_doc; @@ -45,11 +44,7 @@ struct QueryMetrics { public: void UpdateMetric(const string &key, idx_t addition) { - string_timings[key].Add(addition); - } - - void UpdateMetric(const string &key, idx_t addition, timestamp_t start_time, timestamp_t end_time) { - string_timings[key].Add(addition, start_time, end_time); + string_timings[key] += addition; } void UpdateMetricCounter(const string &key, idx_t addition) { @@ -73,7 +68,7 @@ struct QueryMetrics { if (it == string_timings.end()) { return 0.0; } - return static_cast(it->second.duration) / 1e9; + return static_cast(it->second) / 1e9; } idx_t GetStringCounter(const string &key) const { @@ -96,7 +91,7 @@ struct QueryMetrics { return total_memory_allocated.load(); } - const unordered_map &GetMetricTimings() const { + const unordered_map &GetMetricTimings() const { return string_timings; } @@ -124,7 +119,7 @@ struct QueryMetrics { void Merge(const QueryMetrics &other) { for (const auto &entry : other.string_timings) { - string_timings[entry.first].Merge(entry.second); + string_timings[entry.first] += entry.second; } for (const auto &entry : other.string_counters) { string_counters[entry.first] += entry.second; @@ -136,7 +131,7 @@ struct QueryMetrics { private: // String-keyed timings for optimizer, storage and phase timing metrics - unordered_map string_timings; + unordered_map string_timings; // String-keyed counters for storage counter metrics (e.g. "storage.wal_replay_entry_count") unordered_map string_counters; @@ -154,7 +149,6 @@ struct MetricsTimer { if (!is_active) { return; } - start_time = Timestamp::GetCurrentTimestamp(); profiler.Start(); } ~MetricsTimer() { @@ -170,14 +164,12 @@ struct MetricsTimer { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); - std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); } MetricsTimer &operator=(MetricsTimer &&other) noexcept { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); - std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); return *this; } @@ -189,7 +181,7 @@ struct MetricsTimer { } is_active = false; profiler.End(); - query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos(), start_time, Timestamp::GetCurrentTimestamp()); + query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos()); } void Reset() { @@ -204,7 +196,6 @@ struct MetricsTimer { optional_ptr query_metrics; string metric_name; Profiler profiler; - timestamp_t start_time = timestamp_t::epoch(); bool is_active; }; diff --git a/src/duckdb/src/include/duckdb/main/query_profiler.hpp b/src/duckdb/src/include/duckdb/main/query_profiler.hpp index 9446666a1..1ed2c856e 100644 --- a/src/duckdb/src/include/duckdb/main/query_profiler.hpp +++ b/src/duckdb/src/include/duckdb/main/query_profiler.hpp @@ -113,7 +113,7 @@ class QueryProfiler { //! Add to a metric counter (profiling-only). DUCKDB_API void AddToMetricCounter(const string &key, idx_t amount); //! Add parser time measured before the per-statement profiler was started. - void AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time); + void AddParserTime(const Profiler &parser_timer); //! Set an arbitrary metric value (profiling-only; no-op when profiling is disabled). DUCKDB_API void SetMetric(const string &key, Value new_value); diff --git a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp index 010b1afe9..99a81cbb6 100644 --- a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp @@ -9,7 +9,6 @@ #pragma once #include "duckdb/common/common.hpp" -#include "duckdb/common/identifier.hpp" namespace duckdb { class DatabaseInstance; @@ -26,8 +25,6 @@ struct CreateHTTPSecretFunctions { static vector GetDefaultSecretTypes(); //! Get the default secret functions static vector GetDefaultSecretFunctions(); - //! Current sensitive keys for both new and persisted HTTP secrets. - static identifier_set_t GetRedactedKeys(); protected: //! HTTP secret CONFIG provider diff --git a/src/duckdb/src/include/duckdb/main/secret/secret.hpp b/src/duckdb/src/include/duckdb/main/secret/secret.hpp index 7299301da..6183a6e5c 100644 --- a/src/duckdb/src/include/duckdb/main/secret/secret.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/secret.hpp @@ -217,7 +217,13 @@ class KeyValueSecret : public BaseSecret { Deserialize(Deserializer &deserializer, BaseSecret base_secret) { // NOLINT(performance-unnecessary-value-param): callback signature. auto result = make_uniq(base_secret); - ReadSecretMap(deserializer, result->secret_map); + Value secret_map_value; + deserializer.ReadProperty(201, "secret_map", secret_map_value); + + for (const auto &entry : ListValue::GetChildren(secret_map_value)) { + auto kv_struct = StructValue::GetChildren(entry); + result->secret_map[Identifier(kv_struct[0].ToString())] = kv_struct[1]; + } Value redact_set_value; deserializer.ReadProperty(202, "redact_keys", redact_set_value); @@ -255,9 +261,6 @@ class KeyValueSecret : public BaseSecret { identifier_tree_t secret_map; //! keys that are sensitive and should be redacted identifier_set_t redact_keys; - -private: - static void ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result); }; // Helper class to fetch secret parameters in a cascading way. The idea being that in many cases there is a direct diff --git a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp index a7a8bc886..cbece416e 100644 --- a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp +++ b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp @@ -9,7 +9,6 @@ #pragma once #include "duckdb/common/profiler.hpp" -#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unique_ptr.hpp" #include "duckdb/main/parse_iterator.hpp" @@ -63,8 +62,6 @@ class StatementIterator { unique_ptr pending_statement; //! Parser timing for the statement parsed ahead. Profiler parser_timer; - timestamp_t parser_start_time = timestamp_t::epoch(); - timestamp_t parser_end_time = timestamp_t::epoch(); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp index 25407233e..0647b333d 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp @@ -21,12 +21,6 @@ class JoinOrderOptimizer { explicit JoinOrderOptimizer(ClientContext &context); public: - //! Deepest nesting of child optimizers (one per non-reorderable operator, projection, aggregate, ...) that is still - //! join-order optimized. Every level recurses through Optimize/Build/ExtractJoinRelations, so a very deep plan - //! (e.g. a long chain of EXCEPT/INTERSECT) would otherwise overflow the stack long before the plan depth limit is - //! hit; subplans below this depth keep their original join order, which is always a valid plan. - static constexpr idx_t MAX_RECURSION_DEPTH = 128; - JoinOrderOptimizer CreateChildOptimizer(); //! Perform join reordering inside a plan unique_ptr Optimize(unique_ptr plan, optional_ptr stats = nullptr); diff --git a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp index c7171f095..334f3cde2 100644 --- a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp @@ -12,7 +12,6 @@ #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/types.hpp" #include "duckdb/planner/column_binding.hpp" -#include "duckdb/planner/column_binding_map.hpp" namespace duckdb { @@ -55,18 +54,4 @@ struct RelationStats { Identifier table_name; }; -//! Looks up column statistics by binding, the first relation and column with a binding wins -class RelationColumnStatsLookup { -public: - RelationColumnStatsLookup() = default; - explicit RelationColumnStatsLookup(const RelationStats &stats); - -public: - void AddRelation(const RelationStats &stats); - optional_ptr Find(ColumnBinding binding) const; - -private: - column_binding_map_t> columns; -}; - } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp index 82a6d54d4..716dc2f72 100644 --- a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp @@ -129,8 +129,6 @@ class BaseColumnPruner : public LogicalOperatorVisitor { private: //! The current mode of the pruner, enables/disables certain behaviors BaseColumnPrunerMode mode = BaseColumnPrunerMode::DEFAULT; - //! True while visiting the child of OPERATOR_TRY. Do not push throwing CASTs (#25236). - bool inside_try = false; }; //! The RemoveUnusedColumns optimizer traverses the logical operator tree and removes any columns that are not required diff --git a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp index b40a20121..afc6450a5 100644 --- a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp +++ b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp @@ -14,8 +14,6 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/mutex.hpp" -#include "duckdb/common/condition_variable.hpp" -#include "duckdb/common/optional.hpp" #include "duckdb/common/deque.hpp" #include "duckdb/common/vector.hpp" #include "duckdb/common/serializer/async_memory_governor.hpp" @@ -179,10 +177,6 @@ class ScanReadAhead { read_ahead.pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; } --read_ahead.active_producers; - // a pushed job was signalled already, a released slot frees one thread and a done scan all of them - if (!committed || read_ahead.IsDone()) { - read_ahead.SignalProgress(read_ahead.IsDone()); - } } ScanReadAhead &read_ahead; @@ -204,29 +198,16 @@ class ScanReadAhead { lock_guard guard(lock); return !pending_jobs.empty(); } - //! The number of jobs that may be in flight under the current budget - idx_t ReservationDepth() const; //! Reserve an in-flight job slot for producing a job bool TryReserveSlot(); //! Schedule the job's I/O and admit the job to the queue void PushJob(unique_ptr job, vector> io_tasks); //! Release a read-ahead slot void ReleaseSlot(); - //! Wake a thread waiting for another thread to push a job, release a slot or finish producing, or all of them - void SignalProgress(bool wake_all); - //! SignalProgress for a caller that holds the lock - void NotifyProgress(bool wake_all); - //! Wake one waiting thread, or all of them - void WakeWaiters(bool wake_all); - //! Wait until the read-ahead made progress since the given epoch, or the timeout passed - void WaitForProgress(ClientContext &context, idx_t epoch); - idx_t ProgressEpoch() const; private: //! Minimum budget charge per job, beyond its scheduled I/O a job carries scan-state overhead (decode buffers) static constexpr idx_t MINIMUM_JOB_IO_CHARGE = 16ULL * 1024 * 1024; - //! How long a thread yields for another thread to push a job before it waits for it - static constexpr idx_t PRODUCER_YIELD_MICROS = 1000; private: ClientContext &context; @@ -252,11 +233,6 @@ class ScanReadAhead { atomic done {false}; //! Threads that reserved a slot but have not pushed their job yet atomic active_producers {0}; - //! Incremented whenever a waiting thread could make progress - atomic progress_epoch {0}; - //! Threads waiting for progress, only these need a notification - atomic waiting_threads {0}; - condition_variable progress; //! File-opens scheduled on the async pool that have not completed yet, shared with the open tasks shared_ptr> pending_opens; //! Async I/O executor (async pool) diff --git a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp index a2054a041..d1d76b01e 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp @@ -11,13 +11,13 @@ struct IsDistinctFromTail { struct ComparisonExpressionTail { ExpressionType comparison_type; + vector not_keywords; unique_ptr expression; }; struct BetweenInLikeOperator { bool has_not = false; unique_ptr expression; - vector> casts; }; struct ParsedOperator { @@ -31,7 +31,7 @@ struct OtherOperatorTail { unique_ptr expression; }; -enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR, CAST }; +enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR }; struct IsExpressionTail { ExpressionTailType type = ExpressionTailType::IS_TEST; @@ -39,7 +39,6 @@ struct IsExpressionTail { IsDistinctFromTail distinct; ComparisonExpressionTail comparison; OtherOperatorTail other; - unique_ptr cast; }; struct BinaryExpressionTail { diff --git a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp index f8a55ed8f..1d5f222e6 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp @@ -1,6 +1,5 @@ #pragma once -#include "duckdb/common/pair.hpp" #include "duckdb/parser/peg/matcher.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/peg/parsed_grammar.hpp" @@ -42,7 +41,7 @@ struct CompiledGrammar { static shared_ptr DefaultGrammar(); //! Compile the base DuckDB grammar. static shared_ptr Create(); - //! Get the (cached) grammar for the selected extensions without changing the client configuration. + //! Compile a grammar for the selected extensions without changing the client configuration. static shared_ptr Create(const ClientContext &context, const vector &active_extensions); private: @@ -54,27 +53,19 @@ struct CompiledGrammar { const Matcher &top_level_statement_matcher; }; -//! Per-database holder for the compiled base grammar and the grammars compiled for active grammar extensions. +//! Per-database holder for the compiled base grammar. struct ParserCache { public: ParserCache(); ~ParserCache(); - //! The maximum number of grammars compiled for grammar extensions that are kept - static constexpr idx_t MAX_CACHED_EXTENSION_MATCHERS = 16; - shared_ptr GetMatcher(); - //! The grammar compiled for the given grammar extensions, in order - shared by all connections that use them - shared_ptr GetMatcher(const ClientContext &context, const vector &active_extensions); //! The grammar that forwards statements instead of interpreting them, used while CONNECT-ed shared_ptr GetPassthroughMatcher(const ClientContext &context); private: std::mutex mutex; shared_ptr matcher; - std::mutex extension_mutex; - //! The compiled grammars with the names of their grammar extensions in order, from least to most recently used - vector, shared_ptr>> extension_matchers; std::mutex passthrough_mutex; unique_ptr passthrough_dialect; }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp index 0b57885bf..5032d4713 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp @@ -155,14 +155,13 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 84, 121, 112, 101, 77, 111, 100, 105, 102, 105, 101, 114, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 63, 41, 10, - 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, 82, 111, 119, 84, 121, 112, 101, 32, 60, 45, 32, 82, 111, 119, 79, 114, 83, 116, 114, 117, 99, 116, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 63, 10, - 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, + 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 84, 121, 112, 101, 10, 85, 110, 105, 111, 110, 84, 121, 112, 101, 32, 60, 45, 32, 39, 85, 78, 73, 79, 78, 39, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 10, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 84, 121, 112, 101, 41, 41, 10, - 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 63, 10, - 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 10, - 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, + 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 63, 10, + 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 10, + 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 84, 121, 112, 101, 10, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 47, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 32, 47, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 10, @@ -180,364 +179,6 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 37, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 32, 60, 45, 32, 91, 32, 92, 116, 92, 110, 92, 114, 93, 42, 10, 76, 105, 115, 116, 40, 68, 41, 32, 60, 45, 32, 68, 32, 40, 39, 44, 39, 32, 68, 41, 42, 32, 39, 44, 39, 63, 10, 80, 97, 114, 101, 110, 115, 40, 68, 41, 32, 60, 45, 32, 39, 40, 39, 32, 68, 32, 39, 41, 39, 10, - 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 97, 98, 111, 114, 116, 39, 32, 47, 10, - 39, 97, 98, 115, 111, 108, 117, 116, 101, 39, 32, 47, 10, - 39, 97, 99, 99, 101, 115, 115, 39, 32, 47, 10, - 39, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 97, 100, 100, 39, 32, 47, 10, - 39, 97, 100, 109, 105, 110, 39, 32, 47, 10, - 39, 97, 102, 116, 101, 114, 39, 32, 47, 10, - 39, 97, 103, 103, 114, 101, 103, 97, 116, 101, 39, 32, 47, 10, - 39, 97, 108, 115, 111, 39, 32, 47, 10, - 39, 97, 108, 116, 101, 114, 39, 32, 47, 10, - 39, 97, 108, 119, 97, 121, 115, 39, 32, 47, 10, - 39, 97, 112, 112, 114, 111, 120, 39, 32, 47, 10, - 39, 97, 115, 115, 101, 114, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 97, 115, 115, 105, 103, 110, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 97, 116, 116, 97, 99, 104, 39, 32, 47, 10, - 39, 97, 116, 116, 114, 105, 98, 117, 116, 101, 39, 32, 47, 10, - 39, 98, 97, 99, 107, 119, 97, 114, 100, 39, 32, 47, 10, - 39, 98, 101, 102, 111, 114, 101, 39, 32, 47, 10, - 39, 98, 101, 103, 105, 110, 39, 32, 47, 10, - 39, 98, 101, 116, 119, 101, 101, 110, 39, 32, 47, 10, - 39, 98, 105, 103, 105, 110, 116, 39, 32, 47, 10, - 39, 98, 105, 116, 39, 32, 47, 10, - 39, 98, 111, 111, 108, 101, 97, 110, 39, 32, 47, 10, - 39, 99, 97, 99, 104, 101, 39, 32, 47, 10, - 39, 99, 97, 108, 108, 39, 32, 47, 10, - 39, 99, 97, 108, 108, 101, 100, 39, 32, 47, 10, - 39, 99, 97, 115, 99, 97, 100, 101, 39, 32, 47, 10, - 39, 99, 97, 115, 99, 97, 100, 101, 100, 39, 32, 47, 10, - 39, 99, 97, 116, 97, 108, 111, 103, 39, 32, 47, 10, - 39, 99, 104, 97, 105, 110, 39, 32, 47, 10, - 39, 99, 104, 97, 114, 39, 32, 47, 10, - 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 39, 32, 47, 10, - 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, - 39, 99, 104, 101, 99, 107, 112, 111, 105, 110, 116, 39, 32, 47, 10, - 39, 99, 108, 97, 115, 115, 39, 32, 47, 10, - 39, 99, 108, 111, 115, 101, 39, 32, 47, 10, - 39, 99, 108, 117, 115, 116, 101, 114, 39, 32, 47, 10, - 39, 99, 111, 97, 108, 101, 115, 99, 101, 39, 32, 47, 10, - 39, 99, 111, 108, 117, 109, 110, 115, 39, 32, 47, 10, - 39, 99, 111, 109, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 99, 111, 109, 109, 101, 110, 116, 115, 39, 32, 47, 10, - 39, 99, 111, 109, 109, 105, 116, 39, 32, 47, 10, - 39, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, - 39, 99, 111, 109, 112, 114, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, - 39, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 99, 111, 110, 102, 108, 105, 99, 116, 39, 32, 47, 10, - 39, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, - 39, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 99, 111, 110, 115, 116, 114, 97, 105, 110, 116, 115, 39, 32, 47, 10, - 39, 99, 111, 110, 116, 101, 110, 116, 39, 32, 47, 10, - 39, 99, 111, 110, 116, 105, 110, 117, 101, 39, 32, 47, 10, - 39, 99, 111, 110, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, - 39, 99, 111, 112, 121, 39, 32, 47, 10, - 39, 99, 111, 115, 116, 39, 32, 47, 10, - 39, 99, 115, 118, 39, 32, 47, 10, - 39, 99, 117, 98, 101, 39, 32, 47, 10, - 39, 99, 117, 114, 114, 101, 110, 116, 39, 32, 47, 10, - 39, 99, 117, 114, 115, 111, 114, 39, 32, 47, 10, - 39, 99, 121, 99, 108, 101, 39, 32, 47, 10, - 39, 100, 97, 116, 97, 39, 32, 47, 10, - 39, 100, 97, 116, 97, 98, 97, 115, 101, 39, 32, 47, 10, - 39, 100, 101, 97, 108, 108, 111, 99, 97, 116, 101, 39, 32, 47, 10, - 39, 100, 101, 99, 39, 32, 47, 10, - 39, 100, 101, 99, 105, 109, 97, 108, 39, 32, 47, 10, - 39, 100, 101, 99, 108, 97, 114, 101, 39, 32, 47, 10, - 39, 100, 101, 102, 97, 117, 108, 116, 115, 39, 32, 47, 10, - 39, 100, 101, 102, 101, 114, 114, 101, 100, 39, 32, 47, 10, - 39, 100, 101, 102, 105, 110, 101, 114, 39, 32, 47, 10, - 39, 100, 101, 108, 101, 116, 101, 39, 32, 47, 10, - 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 39, 32, 47, 10, - 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 115, 39, 32, 47, 10, - 39, 100, 101, 112, 101, 110, 100, 115, 39, 32, 47, 10, - 39, 100, 101, 115, 116, 114, 111, 121, 39, 32, 47, 10, - 39, 100, 101, 116, 97, 99, 104, 39, 32, 47, 10, - 39, 100, 105, 99, 116, 105, 111, 110, 97, 114, 121, 39, 32, 47, 10, - 39, 100, 105, 115, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 100, 105, 115, 99, 97, 114, 100, 39, 32, 47, 10, - 39, 100, 105, 115, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, - 39, 100, 105, 115, 116, 97, 110, 99, 101, 39, 32, 47, 10, - 39, 100, 111, 99, 117, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 100, 111, 109, 97, 105, 110, 39, 32, 47, 10, - 39, 100, 111, 117, 98, 108, 101, 39, 32, 47, 10, - 39, 100, 114, 111, 112, 39, 32, 47, 10, - 39, 101, 97, 99, 104, 39, 32, 47, 10, - 39, 101, 110, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 101, 110, 99, 111, 100, 105, 110, 103, 39, 32, 47, 10, - 39, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, - 39, 101, 110, 117, 109, 39, 32, 47, 10, - 39, 101, 114, 114, 111, 114, 39, 32, 47, 10, - 39, 101, 118, 101, 110, 116, 39, 32, 47, 10, - 39, 101, 120, 97, 99, 116, 39, 32, 47, 10, - 39, 101, 120, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, - 39, 101, 120, 99, 108, 117, 115, 105, 118, 101, 39, 32, 47, 10, - 39, 101, 120, 101, 99, 117, 116, 101, 39, 32, 47, 10, - 39, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, - 39, 101, 120, 112, 108, 97, 105, 110, 39, 32, 47, 10, - 39, 101, 120, 112, 111, 114, 116, 39, 32, 47, 10, - 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 39, 32, 47, 10, - 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 39, 32, 47, 10, - 39, 101, 120, 116, 101, 114, 110, 97, 108, 39, 32, 47, 10, - 39, 101, 120, 116, 114, 97, 99, 116, 39, 32, 47, 10, - 39, 102, 97, 109, 105, 108, 121, 39, 32, 47, 10, - 39, 102, 105, 114, 115, 116, 39, 32, 47, 10, - 39, 102, 108, 111, 97, 116, 39, 32, 47, 10, - 39, 102, 111, 108, 108, 111, 119, 105, 110, 103, 39, 32, 47, 10, - 39, 102, 111, 114, 99, 101, 39, 32, 47, 10, - 39, 102, 111, 114, 119, 97, 114, 100, 39, 32, 47, 10, - 39, 102, 117, 110, 99, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 102, 117, 110, 99, 116, 105, 111, 110, 115, 39, 32, 47, 10, - 39, 103, 101, 110, 101, 114, 97, 116, 101, 100, 39, 32, 47, 10, - 39, 103, 108, 111, 98, 97, 108, 39, 32, 47, 10, - 39, 103, 114, 97, 110, 116, 39, 32, 47, 10, - 39, 103, 114, 97, 110, 116, 101, 100, 39, 32, 47, 10, - 39, 103, 114, 111, 117, 112, 105, 110, 103, 39, 32, 47, 10, - 39, 103, 114, 111, 117, 112, 105, 110, 103, 95, 105, 100, 39, 32, 47, 10, - 39, 103, 114, 111, 117, 112, 115, 39, 32, 47, 10, - 39, 104, 97, 110, 100, 108, 101, 114, 39, 32, 47, 10, - 39, 104, 101, 97, 100, 101, 114, 39, 32, 47, 10, - 39, 104, 111, 108, 100, 39, 32, 47, 10, - 39, 105, 100, 101, 110, 116, 105, 116, 121, 39, 32, 47, 10, - 39, 105, 102, 39, 32, 47, 10, - 39, 105, 109, 109, 101, 100, 105, 97, 116, 101, 39, 32, 47, 10, - 39, 105, 109, 109, 117, 116, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 105, 109, 112, 108, 105, 99, 105, 116, 39, 32, 47, 10, - 39, 105, 109, 112, 111, 114, 116, 39, 32, 47, 10, - 39, 105, 110, 99, 108, 117, 100, 101, 39, 32, 47, 10, - 39, 105, 110, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, - 39, 105, 110, 99, 114, 101, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 105, 110, 100, 101, 120, 39, 32, 47, 10, - 39, 105, 110, 100, 101, 120, 101, 115, 39, 32, 47, 10, - 39, 105, 110, 104, 101, 114, 105, 116, 39, 32, 47, 10, - 39, 105, 110, 104, 101, 114, 105, 116, 115, 39, 32, 47, 10, - 39, 105, 110, 108, 105, 110, 101, 39, 32, 47, 10, - 39, 105, 110, 111, 117, 116, 39, 32, 47, 10, - 39, 105, 110, 112, 117, 116, 39, 32, 47, 10, - 39, 105, 110, 115, 101, 110, 115, 105, 116, 105, 118, 101, 39, 32, 47, 10, - 39, 105, 110, 115, 101, 114, 116, 39, 32, 47, 10, - 39, 105, 110, 115, 116, 97, 108, 108, 39, 32, 47, 10, - 39, 105, 110, 115, 116, 101, 97, 100, 39, 32, 47, 10, - 39, 105, 110, 116, 39, 32, 47, 10, - 39, 105, 110, 116, 101, 103, 101, 114, 39, 32, 47, 10, - 39, 105, 110, 116, 101, 114, 118, 97, 108, 39, 32, 47, 10, - 39, 105, 110, 118, 111, 107, 101, 114, 39, 32, 47, 10, - 39, 105, 115, 111, 108, 97, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 106, 115, 111, 110, 39, 32, 47, 10, - 39, 107, 101, 121, 39, 32, 47, 10, - 39, 108, 97, 98, 101, 108, 39, 32, 47, 10, - 39, 108, 97, 110, 103, 117, 97, 103, 101, 39, 32, 47, 10, - 39, 108, 97, 114, 103, 101, 39, 32, 47, 10, - 39, 108, 97, 115, 116, 39, 32, 47, 10, - 39, 108, 101, 97, 107, 112, 114, 111, 111, 102, 39, 32, 47, 10, - 39, 108, 101, 118, 101, 108, 39, 32, 47, 10, - 39, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, - 39, 108, 111, 97, 100, 39, 32, 47, 10, - 39, 108, 111, 99, 97, 108, 39, 32, 47, 10, - 39, 108, 111, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 108, 111, 99, 107, 39, 32, 47, 10, - 39, 108, 111, 99, 107, 101, 100, 39, 32, 47, 10, - 39, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, - 39, 109, 97, 99, 114, 111, 39, 32, 47, 10, - 39, 109, 97, 112, 39, 32, 47, 10, - 39, 109, 97, 112, 112, 105, 110, 103, 39, 32, 47, 10, - 39, 109, 97, 116, 99, 104, 39, 32, 47, 10, - 39, 109, 97, 116, 99, 104, 101, 100, 39, 32, 47, 10, - 39, 109, 97, 116, 101, 114, 105, 97, 108, 105, 122, 101, 100, 39, 32, 47, 10, - 39, 109, 97, 120, 118, 97, 108, 117, 101, 39, 32, 47, 10, - 39, 109, 101, 114, 103, 101, 39, 32, 47, 10, - 39, 109, 101, 116, 104, 111, 100, 39, 32, 47, 10, - 39, 109, 105, 110, 118, 97, 108, 117, 101, 39, 32, 47, 10, - 39, 109, 111, 100, 101, 39, 32, 47, 10, - 39, 109, 111, 118, 101, 39, 32, 47, 10, - 39, 110, 97, 109, 101, 39, 32, 47, 10, - 39, 110, 97, 109, 101, 115, 39, 32, 47, 10, - 39, 110, 97, 116, 105, 111, 110, 97, 108, 39, 32, 47, 10, - 39, 110, 99, 104, 97, 114, 39, 32, 47, 10, - 39, 110, 101, 97, 114, 101, 115, 116, 39, 32, 47, 10, - 39, 110, 101, 119, 39, 32, 47, 10, - 39, 110, 101, 120, 116, 39, 32, 47, 10, - 39, 110, 111, 39, 32, 47, 10, - 39, 110, 111, 110, 101, 39, 32, 47, 10, - 39, 110, 111, 116, 104, 105, 110, 103, 39, 32, 47, 10, - 39, 110, 111, 116, 105, 102, 121, 39, 32, 47, 10, - 39, 110, 111, 119, 97, 105, 116, 39, 32, 47, 10, - 39, 110, 117, 108, 108, 105, 102, 39, 32, 47, 10, - 39, 110, 117, 108, 108, 115, 39, 32, 47, 10, - 39, 110, 117, 109, 101, 114, 105, 99, 39, 32, 47, 10, - 39, 111, 98, 106, 101, 99, 116, 39, 32, 47, 10, - 39, 111, 102, 39, 32, 47, 10, - 39, 111, 102, 102, 39, 32, 47, 10, - 39, 111, 105, 100, 115, 39, 32, 47, 10, - 39, 111, 108, 100, 39, 32, 47, 10, - 39, 111, 112, 101, 114, 97, 116, 111, 114, 39, 32, 47, 10, - 39, 111, 112, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 111, 112, 116, 105, 111, 110, 115, 39, 32, 47, 10, - 39, 111, 114, 100, 105, 110, 97, 108, 105, 116, 121, 39, 32, 47, 10, - 39, 111, 116, 104, 101, 114, 115, 39, 32, 47, 10, - 39, 111, 117, 116, 39, 32, 47, 10, - 39, 111, 118, 101, 114, 108, 97, 121, 39, 32, 47, 10, - 39, 111, 118, 101, 114, 114, 105, 100, 105, 110, 103, 39, 32, 47, 10, - 39, 111, 119, 110, 101, 100, 39, 32, 47, 10, - 39, 111, 119, 110, 101, 114, 39, 32, 47, 10, - 39, 112, 97, 114, 97, 108, 108, 101, 108, 39, 32, 47, 10, - 39, 112, 97, 114, 115, 101, 114, 39, 32, 47, 10, - 39, 112, 97, 114, 116, 105, 97, 108, 39, 32, 47, 10, - 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 39, 32, 47, 10, - 39, 112, 97, 115, 115, 105, 110, 103, 39, 32, 47, 10, - 39, 112, 97, 115, 115, 119, 111, 114, 100, 39, 32, 47, 10, - 39, 112, 101, 114, 99, 101, 110, 116, 39, 32, 47, 10, - 39, 112, 101, 114, 115, 105, 115, 116, 101, 110, 116, 39, 32, 47, 10, - 39, 112, 108, 97, 110, 115, 39, 32, 47, 10, - 39, 112, 111, 108, 105, 99, 121, 39, 32, 47, 10, - 39, 112, 111, 115, 105, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 112, 114, 97, 103, 109, 97, 39, 32, 47, 10, - 39, 112, 114, 101, 99, 101, 100, 105, 110, 103, 39, 32, 47, 10, - 39, 112, 114, 101, 112, 97, 114, 101, 39, 32, 47, 10, - 39, 112, 114, 101, 112, 97, 114, 101, 100, 39, 32, 47, 10, - 39, 112, 114, 101, 115, 101, 114, 118, 101, 39, 32, 47, 10, - 39, 112, 114, 105, 111, 114, 39, 32, 47, 10, - 39, 112, 114, 105, 118, 105, 108, 101, 103, 101, 115, 39, 32, 47, 10, - 39, 112, 114, 111, 99, 101, 100, 117, 114, 97, 108, 39, 32, 47, 10, - 39, 112, 114, 111, 99, 101, 100, 117, 114, 101, 39, 32, 47, 10, - 39, 112, 114, 111, 103, 114, 97, 109, 39, 32, 47, 10, - 39, 112, 117, 98, 108, 105, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 113, 117, 111, 116, 101, 39, 32, 47, 10, - 39, 114, 97, 110, 103, 101, 39, 32, 47, 10, - 39, 114, 101, 97, 100, 39, 32, 47, 10, - 39, 114, 101, 97, 108, 39, 32, 47, 10, - 39, 114, 101, 97, 115, 115, 105, 103, 110, 39, 32, 47, 10, - 39, 114, 101, 99, 104, 101, 99, 107, 39, 32, 47, 10, - 39, 114, 101, 99, 117, 114, 115, 105, 118, 101, 39, 32, 47, 10, - 39, 114, 101, 102, 39, 32, 47, 10, - 39, 114, 101, 102, 101, 114, 101, 110, 99, 105, 110, 103, 39, 32, 47, 10, - 39, 114, 101, 102, 114, 101, 115, 104, 39, 32, 47, 10, - 39, 114, 101, 103, 105, 115, 116, 101, 114, 39, 32, 47, 10, - 39, 114, 101, 105, 110, 100, 101, 120, 39, 32, 47, 10, - 39, 114, 101, 108, 97, 116, 105, 118, 101, 39, 32, 47, 10, - 39, 114, 101, 108, 101, 97, 115, 101, 39, 32, 47, 10, - 39, 114, 101, 112, 101, 97, 116, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 114, 101, 112, 108, 105, 99, 97, 39, 32, 47, 10, - 39, 114, 101, 115, 101, 116, 39, 32, 47, 10, - 39, 114, 101, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, - 39, 114, 101, 115, 111, 117, 114, 99, 101, 115, 39, 32, 47, 10, - 39, 114, 101, 115, 116, 97, 114, 116, 39, 32, 47, 10, - 39, 114, 101, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, - 39, 114, 101, 116, 117, 114, 110, 115, 39, 32, 47, 10, - 39, 114, 101, 118, 111, 107, 101, 39, 32, 47, 10, - 39, 114, 111, 108, 101, 39, 32, 47, 10, - 39, 114, 111, 108, 108, 98, 97, 99, 107, 39, 32, 47, 10, - 39, 114, 111, 108, 108, 117, 112, 39, 32, 47, 10, - 39, 114, 111, 119, 39, 32, 47, 10, - 39, 114, 111, 119, 115, 39, 32, 47, 10, - 39, 114, 117, 108, 101, 39, 32, 47, 10, - 39, 115, 97, 109, 112, 108, 101, 39, 32, 47, 10, - 39, 115, 97, 118, 101, 112, 111, 105, 110, 116, 39, 32, 47, 10, - 39, 115, 99, 104, 101, 109, 97, 39, 32, 47, 10, - 39, 115, 99, 104, 101, 109, 97, 115, 39, 32, 47, 10, - 39, 115, 99, 111, 112, 101, 39, 32, 47, 10, - 39, 115, 99, 114, 111, 108, 108, 39, 32, 47, 10, - 39, 115, 101, 97, 114, 99, 104, 39, 32, 47, 10, - 39, 115, 101, 99, 114, 101, 116, 39, 32, 47, 10, - 39, 115, 101, 99, 117, 114, 101, 39, 32, 47, 10, - 39, 115, 101, 99, 117, 114, 105, 116, 121, 39, 32, 47, 10, - 39, 115, 101, 113, 117, 101, 110, 99, 101, 39, 32, 47, 10, - 39, 115, 101, 113, 117, 101, 110, 99, 101, 115, 39, 32, 47, 10, - 39, 115, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 115, 101, 114, 118, 101, 114, 39, 32, 47, 10, - 39, 115, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, - 39, 115, 101, 116, 39, 32, 47, 10, - 39, 115, 101, 116, 111, 102, 39, 32, 47, 10, - 39, 115, 101, 116, 115, 39, 32, 47, 10, - 39, 115, 104, 97, 114, 101, 39, 32, 47, 10, - 39, 115, 105, 109, 105, 108, 97, 114, 105, 116, 121, 39, 32, 47, 10, - 39, 115, 105, 109, 112, 108, 101, 39, 32, 47, 10, - 39, 115, 107, 105, 112, 39, 32, 47, 10, - 39, 115, 109, 97, 108, 108, 105, 110, 116, 39, 32, 47, 10, - 39, 115, 110, 97, 112, 115, 104, 111, 116, 39, 32, 47, 10, - 39, 115, 111, 114, 116, 101, 100, 39, 32, 47, 10, - 39, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, - 39, 115, 113, 108, 39, 32, 47, 10, - 39, 115, 116, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 115, 116, 97, 110, 100, 97, 108, 111, 110, 101, 39, 32, 47, 10, - 39, 115, 116, 97, 114, 116, 39, 32, 47, 10, - 39, 115, 116, 97, 116, 101, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 115, 116, 97, 116, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, - 39, 115, 116, 100, 105, 110, 39, 32, 47, 10, - 39, 115, 116, 100, 111, 117, 116, 39, 32, 47, 10, - 39, 115, 116, 111, 114, 97, 103, 101, 39, 32, 47, 10, - 39, 115, 116, 111, 114, 101, 100, 39, 32, 47, 10, - 39, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, - 39, 115, 116, 114, 105, 112, 39, 32, 47, 10, - 39, 115, 116, 114, 117, 99, 116, 39, 32, 47, 10, - 39, 115, 117, 98, 115, 99, 114, 105, 112, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 115, 117, 98, 115, 116, 114, 105, 110, 103, 39, 32, 47, 10, - 39, 115, 121, 115, 105, 100, 39, 32, 47, 10, - 39, 115, 121, 115, 116, 101, 109, 39, 32, 47, 10, - 39, 116, 97, 98, 108, 101, 115, 39, 32, 47, 10, - 39, 116, 97, 98, 108, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, - 39, 116, 97, 114, 103, 101, 116, 39, 32, 47, 10, - 39, 116, 101, 109, 112, 39, 32, 47, 10, - 39, 116, 101, 109, 112, 108, 97, 116, 101, 39, 32, 47, 10, - 39, 116, 101, 109, 112, 111, 114, 97, 114, 121, 39, 32, 47, 10, - 39, 116, 101, 120, 116, 39, 32, 47, 10, - 39, 116, 105, 101, 115, 39, 32, 47, 10, - 39, 116, 105, 109, 101, 39, 32, 47, 10, - 39, 116, 105, 109, 101, 115, 116, 97, 109, 112, 39, 32, 47, 10, - 39, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, - 39, 116, 114, 97, 110, 115, 102, 111, 114, 109, 39, 32, 47, 10, - 39, 116, 114, 101, 97, 116, 39, 32, 47, 10, - 39, 116, 114, 105, 103, 103, 101, 114, 39, 32, 47, 10, - 39, 116, 114, 105, 109, 39, 32, 47, 10, - 39, 116, 114, 117, 110, 99, 97, 116, 101, 39, 32, 47, 10, - 39, 116, 114, 117, 115, 116, 101, 100, 39, 32, 47, 10, - 39, 116, 114, 121, 95, 99, 97, 115, 116, 39, 32, 47, 10, - 39, 116, 117, 112, 108, 101, 39, 32, 47, 10, - 39, 116, 121, 112, 101, 39, 32, 47, 10, - 39, 116, 121, 112, 101, 115, 39, 32, 47, 10, - 39, 117, 110, 98, 111, 117, 110, 100, 101, 100, 39, 32, 47, 10, - 39, 117, 110, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, - 39, 117, 110, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, - 39, 117, 110, 107, 110, 111, 119, 110, 39, 32, 47, 10, - 39, 117, 110, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, - 39, 117, 110, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, - 39, 117, 110, 116, 105, 108, 39, 32, 47, 10, - 39, 117, 112, 100, 97, 116, 101, 39, 32, 47, 10, - 39, 117, 115, 101, 39, 32, 47, 10, - 39, 117, 115, 101, 114, 39, 32, 47, 10, - 39, 118, 97, 99, 117, 117, 109, 39, 32, 47, 10, - 39, 118, 97, 108, 105, 100, 39, 32, 47, 10, - 39, 118, 97, 108, 105, 100, 97, 116, 101, 39, 32, 47, 10, - 39, 118, 97, 108, 105, 100, 97, 116, 111, 114, 39, 32, 47, 10, - 39, 118, 97, 108, 117, 101, 39, 32, 47, 10, - 39, 118, 97, 108, 117, 101, 115, 39, 32, 47, 10, - 39, 118, 97, 114, 99, 104, 97, 114, 39, 32, 47, 10, - 39, 118, 97, 114, 105, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, - 39, 118, 105, 101, 119, 39, 32, 47, 10, - 39, 118, 105, 101, 119, 115, 39, 32, 47, 10, - 39, 118, 105, 114, 116, 117, 97, 108, 39, 32, 47, 10, - 39, 118, 111, 108, 97, 116, 105, 108, 101, 39, 32, 47, 10, - 39, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, - 39, 119, 111, 114, 107, 39, 32, 47, 10, - 39, 119, 114, 97, 112, 112, 101, 114, 39, 32, 47, 10, - 39, 119, 114, 105, 116, 101, 39, 32, 47, 10, - 39, 120, 109, 108, 39, 32, 47, 10, - 39, 120, 109, 108, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 39, 32, 47, 10, - 39, 120, 109, 108, 99, 111, 110, 99, 97, 116, 39, 32, 47, 10, - 39, 120, 109, 108, 101, 108, 101, 109, 101, 110, 116, 39, 32, 47, 10, - 39, 120, 109, 108, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, - 39, 120, 109, 108, 102, 111, 114, 101, 115, 116, 39, 32, 47, 10, - 39, 120, 109, 108, 110, 97, 109, 101, 115, 112, 97, 99, 101, 115, 39, 32, 47, 10, - 39, 120, 109, 108, 112, 97, 114, 115, 101, 39, 32, 47, 10, - 39, 120, 109, 108, 112, 105, 39, 32, 47, 10, - 39, 120, 109, 108, 114, 111, 111, 116, 39, 32, 47, 10, - 39, 120, 109, 108, 115, 101, 114, 105, 97, 108, 105, 122, 101, 39, 32, 47, 10, - 39, 120, 109, 108, 116, 97, 98, 108, 101, 39, 32, 47, 10, - 39, 121, 101, 115, 39, 32, 47, 10, - 39, 122, 111, 110, 101, 39, 10, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 66, 69, 84, 87, 69, 69, 78, 39, 32, 47, 10, 39, 66, 73, 71, 73, 78, 84, 39, 32, 47, 10, 39, 66, 73, 84, 39, 32, 47, 10, @@ -1077,7 +718,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 83, 116, 109, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 76, 105, 115, 116, 40, 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 41, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 83, 116, 109, 116, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, - 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 10, + 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 104, 97, 110, 103, 101, 78, 117, 108, 108, 97, 98, 105, 108, 105, 116, 121, 32, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 82, 101, 110, 97, 109, 101, 67, 111, 108, 117, 109, 110, 32, 47, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 65, 68, 68, 39, 32, 84, 111, 112, 76, 101, 118, 101, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 68, 82, 79, 80, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, @@ -1150,7 +791,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 83, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, 67, 111, 112, 121, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 67, 79, 80, 89, 39, 32, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 10, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 60, 45, 32, 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 47, 32, 67, 111, 112, 121, 83, 101, 108, 101, 99, 116, 32, 47, 32, 67, 111, 112, 121, 70, 114, 111, 109, 68, 97, 116, 97, 98, 97, 115, 101, 10, - 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, + 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, 70, 114, 111, 109, 79, 114, 84, 111, 32, 60, 45, 32, 67, 111, 112, 121, 70, 114, 111, 109, 32, 47, 32, 67, 111, 112, 121, 84, 111, 10, 67, 111, 112, 121, 70, 114, 111, 109, 32, 60, 45, 32, 39, 70, 82, 79, 77, 39, 10, 67, 111, 112, 121, 84, 111, 32, 60, 45, 32, 39, 84, 79, 39, 10, @@ -1162,7 +803,6 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 10, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 43, 10, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 32, 60, 45, 32, 39, 46, 39, 32, 67, 111, 108, 73, 100, 10, - 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 63, 32, 39, 68, 69, 76, 73, 77, 73, 84, 69, 82, 83, 39, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 67, 111, 112, 121, 71, 101, 110, 101, 114, 105, 99, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 47, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 84, 97, 105, 108, 42, 10, @@ -1344,7 +984,6 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 82, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 85, 110, 114, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 70, 117, 110, 99, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 84, 121, 112, 101, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 10, - 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 63, 32, 39, 65, 83, 39, 32, 80, 97, 114, 101, 110, 115, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 84, 121, 112, 101, 63, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 65, 108, 119, 97, 121, 115, 32, 47, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 66, 121, 68, 101, 102, 97, 117, 108, 116, 10, @@ -1381,18 +1020,14 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 32, 60, 45, 32, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 47, 32, 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 47, 32, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 10, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 10, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 41, 10, - 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, + 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 10, 67, 114, 101, 97, 116, 101, 86, 105, 101, 119, 83, 116, 109, 116, 32, 60, 45, 32, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 63, 32, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 63, 32, 39, 86, 73, 69, 87, 39, 32, 73, 102, 78, 111, 116, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 87, 105, 116, 104, 76, 105, 115, 116, 63, 32, 39, 65, 83, 39, 32, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 10, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 32, 60, 45, 32, 39, 82, 69, 67, 85, 82, 83, 73, 86, 69, 39, 10, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 32, 60, 45, 32, 39, 83, 69, 67, 85, 82, 69, 39, 10, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 68, 69, 65, 76, 76, 79, 67, 65, 84, 69, 39, 32, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 63, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 10, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 32, 60, 45, 32, 39, 80, 82, 69, 80, 65, 82, 69, 39, 10, 68, 101, 108, 101, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 68, 69, 76, 69, 84, 69, 39, 32, 39, 70, 82, 79, 77, 39, 32, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 63, 10, - 35, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 102, 105, 114, 115, 116, 44, 32, 115, 111, 32, 67, 65, 83, 67, 65, 68, 69, 47, 82, 69, 83, 84, 82, 73, 67, 84, 32, 97, 114, 101, 32, 110, 111, 116, 32, 99, 111, 110, 115, 117, 109, 101, 100, 32, 97, 115, 32, 97, 110, 32, 97, 108, 105, 97, 115, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 47, 32, 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 60, 45, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 63, 10, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 10, @@ -1445,7 +1080,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 9, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 10, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 32, 60, 45, 32, 39, 84, 82, 73, 71, 71, 69, 82, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 84, 114, 105, 103, 103, 101, 114, 78, 97, 109, 101, 32, 39, 79, 78, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 68, 114, 111, 112, 84, 97, 98, 108, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 79, 114, 86, 105, 101, 119, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 41, 10, - 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 41, 10, + 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 41, 10, 68, 114, 111, 112, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 70, 117, 110, 99, 116, 105, 111, 110, 84, 121, 112, 101, 77, 97, 99, 114, 111, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 41, 10, 68, 114, 111, 112, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 41, 10, 68, 114, 111, 112, 73, 110, 100, 101, 120, 32, 60, 45, 32, 39, 73, 78, 68, 69, 88, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 73, 110, 100, 101, 120, 78, 97, 109, 101, 41, 10, @@ -1664,12 +1299,11 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 35, 32, 76, 69, 86, 69, 76, 32, 52, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 63, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 10, + 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 10, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 84, 97, 105, 108, 10, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 10, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, - 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 10, 73, 115, 84, 101, 115, 116, 32, 60, 45, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 111, 116, 78, 117, 108, 108, 32, 47, 32, 73, 115, 78, 117, 108, 108, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 32, 60, 45, 32, 84, 114, 117, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 70, 97, 108, 115, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 117, 108, 108, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 85, 110, 107, 110, 111, 119, 110, 76, 105, 116, 101, 114, 97, 108, 10, @@ -1685,10 +1319,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 79, 112, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 39, 68, 73, 83, 84, 73, 78, 67, 84, 39, 32, 39, 70, 82, 79, 77, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 54, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 10, - 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 47, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 35, 32, 78, 79, 84, 32, 97, 102, 116, 101, 114, 32, 97, 32, 99, 111, 109, 112, 97, 114, 105, 115, 111, 110, 32, 111, 112, 101, 114, 97, 116, 111, 114, 32, 98, 105, 110, 100, 115, 32, 101, 118, 101, 114, 121, 116, 104, 105, 110, 103, 32, 117, 112, 32, 116, 111, 32, 116, 104, 101, 32, 73, 83, 32, 108, 101, 118, 101, 108, 58, 32, 97, 32, 60, 32, 78, 79, 84, 32, 98, 32, 73, 83, 32, 78, 85, 76, 76, 32, 105, 115, 32, 97, 32, 60, 32, 40, 78, 79, 84, 32, 40, 98, 32, 73, 83, 32, 78, 85, 76, 76, 41, 41, 10, - 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 69, 113, 117, 97, 108, 32, 47, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 78, 111, 116, 69, 113, 117, 97, 108, 32, 47, 10, @@ -1704,7 +1335,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 112, 101, 114, 97, 116, 111, 114, 71, 114, 101, 97, 116, 101, 114, 84, 104, 97, 110, 69, 113, 117, 97, 108, 115, 32, 60, 45, 32, 39, 62, 61, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 55, 10, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 42, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 63, 10, - 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 42, 10, + 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 10, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 76, 105, 107, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 69, 115, 99, 97, 112, 101, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1790,11 +1421,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 77, 101, 116, 104, 111, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 68, 105, 115, 116, 105, 110, 99, 116, 79, 114, 65, 108, 108, 63, 32, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 63, 32, 79, 114, 100, 101, 114, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 73, 103, 110, 111, 114, 101, 79, 114, 82, 101, 115, 112, 101, 99, 116, 78, 117, 108, 108, 115, 63, 10, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 10, 83, 108, 105, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 91, 39, 32, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 39, 93, 39, 10, - 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 63, 10, - 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 32, 60, 45, 32, 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 47, 32, 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, - 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 39, 45, 39, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, - 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, + 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, + 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 63, 10, + 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 10, + 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 32, 60, 45, 32, 39, 45, 39, 10, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 80, 111, 115, 116, 102, 105, 120, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 32, 39, 33, 39, 10, 83, 112, 101, 99, 105, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 97, 108, 101, 115, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 85, 110, 112, 97, 99, 107, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 67, 111, 108, 117, 109, 110, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 116, 114, 97, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 76, 97, 109, 98, 100, 97, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 78, 117, 108, 108, 73, 102, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 82, 111, 119, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 83, 117, 98, 115, 116, 114, 105, 110, 103, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 105, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 79, 118, 101, 114, 108, 97, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, @@ -1861,7 +1491,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 83, 111, 117, 114, 99, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 47, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 78, 69, 87, 39, 32, 39, 84, 69, 77, 80, 79, 82, 65, 82, 89, 39, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 105, 111, 110, 79, 112, 116, 105, 111, 110, 115, 63, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 67, 111, 108, 73, 100, 10, - 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 79, 114, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 47, 32, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 10, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 82, 69, 80, 76, 65, 67, 69, 39, 10, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 73, 71, 78, 79, 82, 69, 39, 10, @@ -1870,8 +1500,6 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 79, 114, 100, 101, 114, 32, 60, 45, 32, 39, 66, 89, 39, 32, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 10, 73, 110, 115, 101, 114, 116, 66, 121, 78, 97, 109, 101, 32, 60, 45, 32, 39, 78, 65, 77, 69, 39, 10, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 32, 60, 45, 32, 39, 80, 79, 83, 73, 84, 73, 79, 78, 39, 10, - 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 79, 86, 69, 82, 82, 73, 68, 73, 78, 71, 39, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 39, 86, 65, 76, 85, 69, 39, 10, - 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 60, 45, 32, 39, 85, 83, 69, 82, 39, 32, 47, 32, 39, 83, 89, 83, 84, 69, 77, 39, 10, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 63, 10, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 41, 10, @@ -1884,7 +1512,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 73, 100, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 73, 110, 100, 101, 120, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 39, 79, 78, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 47, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 10, - 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 82, 69, 84, 85, 82, 78, 73, 78, 71, 39, 32, 84, 97, 114, 103, 101, 116, 76, 105, 115, 116, 10, 76, 111, 97, 100, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 76, 79, 65, 68, 39, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 70, 114, 111, 109, 83, 111, 117, 114, 99, 101, 63, 32, 69, 120, 116, 101, 110, 115, 105, 111, 110, 65, 108, 105, 97, 115, 63, 10, @@ -1919,7 +1547,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 111, 78, 111, 116, 104, 105, 110, 103, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 69, 114, 114, 111, 114, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 69, 82, 82, 79, 82, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 10, - 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, + 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 65, 78, 68, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 111, 116, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 72, 69, 78, 39, 32, 39, 78, 79, 84, 39, 32, 39, 77, 65, 84, 67, 72, 69, 68, 39, 32, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 63, 32, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 39, 84, 72, 69, 78, 39, 32, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 65, 99, 116, 105, 111, 110, 10, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 121, 83, 111, 117, 114, 99, 101, 32, 47, 32, 66, 121, 84, 97, 114, 103, 101, 116, 10, @@ -1978,7 +1606,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 84, 97, 98, 108, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 79, 112, 116, 105, 111, 110, 97, 108, 80, 97, 114, 101, 110, 115, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 47, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 10, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 41, 10, - 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 47, 32, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 10, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1999,10 +1627,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 105, 115, 116, 105, 110, 99, 116, 79, 110, 84, 97, 114, 103, 101, 116, 115, 32, 60, 45, 32, 39, 79, 78, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 47, 32, 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 47, 32, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 10, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 42, 10, - 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 65, 116, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 39, 58, 39, 10, - 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 32, 60, 45, 32, 74, 111, 105, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 85, 110, 112, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 10, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 80, 73, 86, 79, 84, 39, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 66, 111, 100, 121, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, @@ -2025,13 +1653,13 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 97, 98, 108, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 10, - 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 32, 39, 79, 82, 68, 73, 78, 65, 76, 73, 84, 89, 39, 10, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 63, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 42, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 63, 41, 10, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 10, - 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 10, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 84, 121, 112, 101, 63, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 60, 45, 32, 39, 58, 61, 39, 32, 47, 32, 39, 61, 62, 39, 10, @@ -2086,8 +1714,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 71, 82, 79, 85, 80, 39, 32, 39, 66, 89, 39, 32, 71, 114, 111, 117, 112, 66, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 10, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 72, 65, 86, 73, 78, 71, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 81, 85, 65, 76, 73, 70, 89, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, - 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, + 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 40, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 47, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 41, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 39, 83, 65, 77, 80, 76, 69, 39, 10, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 83, 65, 77, 80, 76, 69, 39, 10, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 73, 78, 68, 79, 87, 39, 32, 76, 105, 115, 116, 40, 87, 105, 110, 100, 111, 119, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 41, 10, @@ -2143,7 +1770,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 105, 97, 115, 101, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 39, 65, 83, 39, 32, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 10, - 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 63, 10, + 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 63, 10, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 86, 65, 76, 85, 69, 83, 39, 32, 76, 105, 115, 116, 40, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 41, 10, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 83, 101, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 83, 101, 116, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 79, 114, 84, 105, 109, 101, 90, 111, 110, 101, 10, @@ -2182,20 +1809,20 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 82, 101, 97, 100, 87, 114, 105, 116, 101, 32, 60, 45, 32, 39, 87, 82, 73, 84, 69, 39, 10, 65, 98, 111, 114, 116, 79, 114, 82, 111, 108, 108, 98, 97, 99, 107, 32, 60, 45, 32, 39, 65, 66, 79, 82, 84, 39, 32, 47, 32, 39, 82, 79, 76, 76, 66, 65, 67, 75, 39, 10, 67, 111, 109, 109, 105, 116, 79, 114, 69, 110, 100, 32, 60, 45, 32, 39, 67, 79, 77, 77, 73, 84, 39, 32, 47, 32, 39, 69, 78, 68, 39, 10, - 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 39, 83, 69, 84, 39, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 63, 32, 39, 83, 69, 84, 39, 10, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 41, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 41, 41, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 41, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 85, 115, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 85, 83, 69, 39, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 47, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 47, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, - 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, - 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, + 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 10, + 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 39, 46, 39, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 39, 46, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 86, 97, 99, 117, 117, 109, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 86, 65, 67, 85, 85, 77, 39, 32, 86, 97, 99, 117, 117, 109, 79, 112, 116, 105, 111, 110, 115, 63, 32, 65, 110, 97, 108, 121, 122, 101, 84, 97, 114, 103, 101, 116, 63, 10, diff --git a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp index 2cd19d5a1..1c3d2d9bd 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp @@ -24,8 +24,6 @@ class DefaultKeywordMaps { case_insensitive_set_t colname_keyword_map; case_insensitive_set_t typefunc_keyword_map; case_insensitive_set_t typename_keyword_map; - //! Keywords that can be used as a column alias without AS (orthogonal to the other categories) - case_insensitive_set_t bare_label_keyword_map; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp index b4150dd88..758766438 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp @@ -46,8 +46,7 @@ enum class SuggestionState : uint8_t { SUGGEST_TABLE_FUNCTION_NAME, SUGGEST_PRAGMA_NAME, SUGGEST_SETTING_NAME, - SUGGEST_RESERVED_VARIABLE, - SUGGEST_BARE_LABEL + SUGGEST_RESERVED_VARIABLE }; enum class CandidateType { KEYWORD, IDENTIFIER, LITERAL }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp index a8163c699..f37612674 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp @@ -54,10 +54,6 @@ class IdentifierMatcher : public AtomicMatcher { if (Tokenizer::CharacterIsInitialNumber(text[0])) { return false; } - if (text.back() == '\'') { - // a prefixed string literal such as x'41' or e'ab' - return false; - } return Tokenizer::CharacterIsKeyword(text[0]); } diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp index 64936d1c2..b7358cb14 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp @@ -61,15 +61,7 @@ class NumberLiteralMatcher : public AtomicMatcher { return false; } bool scientific_notation = false; - bool has_dot = token_text[0] == '.'; for (idx_t i = 1; i < token_text.size(); i++) { - if (token_text[i] == '.') { - // a number has at most one dot, and none in its exponent - if (has_dot || scientific_notation) { - return false; - } - has_dot = true; - } if (Tokenizer::CharacterIsScientific(token_text[i])) { if (scientific_notation) { throw ParserException("Already found scientific notation"); diff --git a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp index 7fa82e018..66714802d 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp @@ -165,8 +165,11 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.arena_ptr>", case_insensitive_map_t>); +DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.child_list_t", child_list_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.int64_t", int64_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.optional_idx", optional_idx); +DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair", + pair); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", pair>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", @@ -235,8 +238,6 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); -DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>>", - vector>>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>", vector>); @@ -1138,9 +1139,6 @@ class PEGTransformerFactory { static void InitializeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeNestedTypeTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1267,9 +1265,6 @@ class PEGTransformerFactory { static void InitializeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1974,17 +1969,6 @@ class PEGTransformerFactory { static void InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2647,9 +2631,6 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeIsCastTailTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2695,13 +2676,6 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeComparisonOperandTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2966,19 +2940,15 @@ class PEGTransformerFactory { static void InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeEndSliceValueTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -3829,9 +3799,6 @@ class PEGTransformerFactory { static void InitializeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4114,16 +4081,16 @@ class PEGTransformerFactory { static void InitializeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static void InitializeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4386,26 +4353,19 @@ class PEGTransformerFactory { const Identifier &reserved_type_name); static vector> TransformTypeModifiers(PEGTransformer &transformer, optional>> expression); - static unique_ptr TransformNestedType(PEGTransformer &transformer, - unique_ptr type_variations, - const optional> &array_bounds); - static unique_ptr - TransformRowType(PEGTransformer &transformer, - optional>>> col_id_type_list); - static unique_ptr TransformSetofType(PEGTransformer &transformer, - unique_ptr nested_type); - static unique_ptr - TransformUnionType(PEGTransformer &transformer, - vector>> col_id_type_list); - static vector>> - TransformColIdTypeList(PEGTransformer &transformer, - vector>> col_id_type); + static unique_ptr TransformRowType(PEGTransformer &transformer, + const optional> &col_id_type_list); + static unique_ptr TransformSetofType(PEGTransformer &transformer, const LogicalType &type); + static unique_ptr TransformUnionType(PEGTransformer &transformer, + const child_list_t &col_id_type_list); + static child_list_t TransformColIdTypeList(PEGTransformer &transformer, + const vector> &col_id_type); static unique_ptr TransformMapType(PEGTransformer &transformer, - optional>> nested_type); + const optional> &type); static unique_ptr TransformTupleType(PEGTransformer &transformer, - vector> nested_type); - static pair> - TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, unique_ptr nested_type); + const vector &type); + static pair TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, + const LogicalType &type); static int64_t TransformArrayKeyword(PEGTransformer &transformer); static int64_t TransformArrayKeywordWithBounds(PEGTransformer &transformer, const int64_t &square_brackets_array); static int64_t TransformSquareBracketsArray(PEGTransformer &transformer, @@ -4430,11 +4390,12 @@ class PEGTransformerFactory { const Identifier &catalog_name); static unique_ptr TransformCopyStatement(PEGTransformer &transformer, unique_ptr copy_variations); - static unique_ptr - TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, - const optional> &insert_column_list, const bool &from_or_to, - unique_ptr copy_file_name, const optional &legacy_option, - const optional> ©_options); + static unique_ptr TransformCopyTable(PEGTransformer &transformer, + unique_ptr base_table_name, + const optional> &insert_column_list, + const bool &from_or_to, + unique_ptr copy_file_name, + const optional> ©_options); static bool TransformCopyFrom(PEGTransformer &transformer); static bool TransformCopyTo(PEGTransformer &transformer); static unique_ptr TransformCopySelect(PEGTransformer &transformer, @@ -4449,8 +4410,6 @@ class PEGTransformerFactory { const Identifier &identifier_col_id); static Identifier TransformIdentifierColId(PEGTransformer &transformer, const Identifier &identifier, const vector ©_file_name_suffix); - static GenericCopyOption TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, - const string &delimiter); static vector TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list); static vector @@ -4757,8 +4716,7 @@ class PEGTransformerFactory { static unique_ptr TransformEnumSelectType(PEGTransformer &transformer, unique_ptr select_statement_internal); static unique_ptr TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal, - const optional> &array_bounds); + const optional> &string_literal); static unique_ptr TransformCreateViewStmt(PEGTransformer &transformer, const optional &create_secure, const optional &create_recursive, const optional &if_not_exists, @@ -4778,11 +4736,7 @@ class PEGTransformerFactory { optional> where_clause, optional>> returning_clause); static unique_ptr TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name, - optional truncate_options); - static bool TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, - optional truncate_drop_behavior); - static bool TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior); + unique_ptr base_table_name); static unique_ptr TransformTargetOptAlias(PEGTransformer &transformer, unique_ptr base_table_name, const optional &target_alias); @@ -4832,7 +4786,7 @@ class PEGTransformerFactory { static unique_ptr TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name); + const vector &table_function_name); static unique_ptr TransformDropFunction(PEGTransformer &transformer, const bool &function_type_macro, const optional &if_exists, const vector &function_identifier); @@ -5132,7 +5086,7 @@ class PEGTransformerFactory { TransformColDefAndExpressionTail(PEGTransformer &transformer, unique_ptr is_distinct_from_expression); static unique_ptr TransformLogicalNotExpression(PEGTransformer &transformer, - const optional> ¬_expression, + optional> not_expression, unique_ptr is_expression); static vector TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword); static bool TransformNotKeyword(PEGTransformer &transformer); @@ -5149,8 +5103,6 @@ class PEGTransformerFactory { ComparisonExpressionTail comparison_expression_tail); static IsExpressionTail TransformIsOtherOperatorTail(PEGTransformer &transformer, OtherOperatorTail other_operator_tail); - static IsExpressionTail TransformIsCastTail(PEGTransformer &transformer, - unique_ptr cast_operator); static unique_ptr TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value); static Value TransformUnknownLiteral(PEGTransformer &transformer); @@ -5169,12 +5121,10 @@ class PEGTransformerFactory { static unique_ptr TransformComparisonExpression(PEGTransformer &transformer, unique_ptr between_in_like_expression, optional> comparison_expression_tail); - static ComparisonExpressionTail TransformComparisonExpressionTail(PEGTransformer &transformer, - const ExpressionType &comparison_operator, - unique_ptr comparison_operand); - static unique_ptr TransformNotComparisonOperand(PEGTransformer &transformer, - const vector ¬_expression, - unique_ptr is_expression); + static ComparisonExpressionTail + TransformComparisonExpressionTail(PEGTransformer &transformer, const ExpressionType &comparison_operator, + optional> not_expression, + unique_ptr between_in_like_expression); static ExpressionType TransformOperatorEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorNotEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorLessThan(PEGTransformer &transformer); @@ -5185,8 +5135,7 @@ class PEGTransformerFactory { PEGTransformer &transformer, unique_ptr other_operator_expression, optional> in_predicate, optional between_like_op); static BetweenInLikeOperator TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause, - optional>> cast_operator); + unique_ptr in_clause); static BetweenInLikeOperator TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression); static unique_ptr TransformLikeClause(PEGTransformer &transformer, const string &like_variations, @@ -5297,14 +5246,11 @@ class PEGTransformerFactory { vector> slice_bound); static vector> TransformSliceBound(PEGTransformer &transformer, optional> expression, - optional>> slice_upper_bound); - static vector> - TransformMinusStepSliceBound(PEGTransformer &transformer, unique_ptr step_slice_bound); - static vector> - TransformEndStepSliceBound(PEGTransformer &transformer, unique_ptr end_slice_bound, - optional> step_slice_bound); + optional> end_slice_bound, + optional> step_slice_bound); static unique_ptr TransformEndSliceBound(PEGTransformer &transformer, - optional> expression); + optional> end_slice_value); + static unique_ptr TransformEndSliceMinus(PEGTransformer &transformer); static unique_ptr TransformStepSliceBound(PEGTransformer &transformer, optional> expression); static unique_ptr TransformPostfixOperator(PEGTransformer &transformer); @@ -5405,8 +5351,8 @@ class PEGTransformerFactory { TransformInsertStatement(PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, - const optional> &insert_column_list, bool has_overriding_clause, - InsertValues insert_values, optional> on_conflict_clause, + const optional> &insert_column_list, InsertValues insert_values, + optional> on_conflict_clause, optional>> returning_clause); static OnConflictAction TransformInsertOrReplace(PEGTransformer &transformer); static OnConflictAction TransformInsertOrIgnore(PEGTransformer &transformer); @@ -5611,10 +5557,11 @@ class PEGTransformerFactory { optional>> distinct_on_targets); static vector> TransformDistinctOnTargets(PEGTransformer &transformer, vector> expression); - static unique_ptr - TransformTableSubquery(PEGTransformer &transformer, const optional &table_alias_colon, - const optional &lateral, unique_ptr subquery_reference, - const optional &table_alias, optional> sample_clause); + static unique_ptr TransformTableSubquery(PEGTransformer &transformer, + const optional &table_alias_colon, + const optional &lateral, + unique_ptr subquery_reference, + const optional &table_alias); static unique_ptr TransformBaseTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr base_table_name, const optional &table_alias, @@ -5623,8 +5570,7 @@ class PEGTransformerFactory { static unique_ptr TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias, - optional> sample_clause); + const optional &table_alias); static unique_ptr TransformParensTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr table_ref, @@ -5669,10 +5615,12 @@ class PEGTransformerFactory { TransformCatalogReservedSchemaTable(PEGTransformer &transformer, const Identifier &catalog_qualification, const vector &reserved_schema_qualification, const Identifier &reserved_table_name); - static unique_ptr TransformTableFunctionLateralOpt( - PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, - vector table_function_arguments, const optional &with_ordinality, - const optional &table_alias, optional> sample_clause); + static unique_ptr TransformTableFunctionLateralOpt(PEGTransformer &transformer, + const optional &lateral, + const QualifiedName &qualified_table_function, + vector table_function_arguments, + const optional &with_ordinality, + const optional &table_alias); static unique_ptr TransformTableFunctionAliasColon(PEGTransformer &transformer, const Identifier &table_alias_colon, const QualifiedName &qualified_table_function, @@ -5686,9 +5634,7 @@ class PEGTransformerFactory { const Identifier &table_function_name); static vector TransformTableFunctionArguments(PEGTransformer &transformer, optional> function_argument); - static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, - const Identifier &type_func_name, - unique_ptr expression); + static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, MacroParameter named_parameter); static FunctionArgument TransformPositionalFunctionArgument(PEGTransformer &transformer, unique_ptr expression); static MacroParameter TransformNamedParameter(PEGTransformer &transformer, const Identifier &type_func_name, @@ -5766,8 +5712,6 @@ class PEGTransformerFactory { unique_ptr expression); static unique_ptr TransformSampleClause(PEGTransformer &transformer, unique_ptr sample_entry); - static unique_ptr TransformSelectSampleClause(PEGTransformer &transformer, - unique_ptr sample_entry); static vector> TransformWindowClause(PEGTransformer &transformer, vector> window_definition); static unique_ptr @@ -5893,19 +5837,20 @@ class PEGTransformerFactory { const optional &update_alias); static Identifier TransformUpdateAlias(PEGTransformer &transformer, const bool &has_result, const Identifier &col_id); - static unique_ptr TransformUpdateSetClauseList(PEGTransformer &transformer, - vector> update_set_info); static unique_ptr TransformUpdateSetTuple(PEGTransformer &transformer, const vector &column_name, unique_ptr expression); - static unique_ptr TransformUpdateSetElement(PEGTransformer &transformer, - const string &update_set_column_target, - unique_ptr expression); + static unique_ptr + TransformUpdateSetElementList(PEGTransformer &transformer, + vector>> update_set_element); + static pair> + TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, + unique_ptr expression); static string TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, const optional> &dot_identifier); static unique_ptr TransformUseStatement(PEGTransformer &transformer, const QualifiedName &use_target); - static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const string &schema_name); - static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const string &catalog_name); + static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const Identifier &schema_name); + static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const Identifier &catalog_name); static QualifiedName TransformUseTargetCatalogSchema(PEGTransformer &transformer, const Identifier &catalog_name, const Identifier &reserved_schema_name, const optional> &dot_identifier); diff --git a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp index 2745d3e32..28ab414a8 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp @@ -112,8 +112,6 @@ class ExpressionBinder { optional_ptr macro_binding; optional_ptr> lambda_bindings; - //! The number of (outer) lambda bindings whose parameters can not be referenced, e.g. inside an aggregate - idx_t inaccessible_lambda_binding_count = 0; public: unique_ptr Bind(unique_ptr &expr, optional_ptr result_type = nullptr, @@ -235,9 +233,6 @@ class ExpressionBinder { //! Returns true if the function name is an alias for the UNNEST function static bool IsUnnestFunction(const Identifier &function_name); - //! Continues the expression depth of the binder this one binds a nested expression for - void ContinueStackDepth(const ExpressionBinder &parent); - private: //! Current stack depth idx_t stack_depth = DConstants::INVALID_INDEX; diff --git a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp index ef1494222..bdd01b0fd 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp @@ -20,7 +20,7 @@ class BloomFilter; //! DEPRECATED - only preserved for backwards-compatible expression conversion class LegacyBFTableFilter final : public TableFilter { private: - shared_ptr filter; + optional_ptr filter; bool filters_null_values; string key_column_name; @@ -30,9 +30,9 @@ class LegacyBFTableFilter final : public TableFilter { static constexpr auto TYPE = TableFilterType::LEGACY_BLOOM_FILTER; public: - explicit LegacyBFTableFilter(shared_ptr filter_p, const bool filters_null_values_p, + explicit LegacyBFTableFilter(optional_ptr filter_p, const bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p) - : TableFilter(TYPE), filter(std::move(filter_p)), filters_null_values(filters_null_values_p), + : TableFilter(TYPE), filter(filter_p), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p) { } diff --git a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp index aa07f4491..0c2e3c147 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp @@ -9,7 +9,6 @@ #pragma once #include "duckdb/common/common.hpp" -#include "duckdb/common/mutex.hpp" #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/enums/expression_type.hpp" #include "duckdb/common/enums/filter_propagate_result.hpp" @@ -88,70 +87,44 @@ struct TableFilterFunctions { }; //! Runtime bloom-filter state used by join pushdown and internal tablefilter functions. -//! A filter is first built (Initialize, then InsertHashes) and afterwards sealed. Only a sealed filter -//! describes a complete key set, so only a sealed filter may be looked up or handed to a scan. class BloomFilter { public: BloomFilter() = default; void Initialize(ClientContext &context_p, idx_t number_of_rows); - //! VectorOperations::Hash writes at the source row positions, so the hashes to insert are the ones sel - //! points at rather than the first count entries - void InsertHashes(const Vector &hashes_v, const SelectionVector &sel, idx_t count); - //! Inserts every hash the vector carries - void InsertHashes(const Vector &hashes_v); + void InsertHashes(const Vector &hashes_v) const; idx_t LookupHashes(const Vector &hashes_v, SelectionVector &result_sel, idx_t count) const; //! result_sel contains local positions into sel rather than source row ids. idx_t LookupHashes(const Vector &hashes_v, const SelectionVector &sel, SelectionVector &result_sel, idx_t count) const; - void InsertOne(hash_t hash); + void InsertOne(hash_t hash) const; bool LookupOne(hash_t hash) const; - //! Folds the filter down to the smallest size that still meets the target false positive rate, releases the - //! memory that folding freed up, and marks the filter complete. - void Seal(ClientContext &context); + void Merge(const BloomFilter &other); + void Reset(); bool IsInitialized() const { return initialized; } - bool IsSealed() const { - return sealed; - } - //! Whether the sealed filter still rejects enough for pushing it into a scan to pay off - bool IsUseful() const { - D_ASSERT(sealed); - return useful; - } static idx_t GetNumberOfSectors(idx_t number_of_rows); -private: - //! Halves the filter until only new_num_sectors sectors remain. Bit-exact with a filter that was built at - //! the smaller size from the start, because the sector index is the low bits of the hash and the bits set - //! within a sector are taken from the high bits. - void Fold(idx_t new_num_sectors); - //! Fraction of bits that are set, over the sectors currently in use - double Density() const; - private: idx_t num_sectors; uint64_t bitmask; // num_sectors - 1 -> used to get the sector offset bool initialized = false; - bool sealed = false; - bool useful = false; AllocatedData buf_; uint64_t *bf; }; //! FunctionData for bloom filter internal function struct BloomFilterFunctionData : public FunctionData { - BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, + BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p); - //! Published filters are shared and immutable: the hash table may drop its own reference at any time - shared_ptr filter; + optional_ptr filter; bool filters_null_values; string key_column_name; LogicalType key_type; @@ -220,9 +193,9 @@ struct DynamicFilterData { public: DynamicFilterData(ExpressionType comparison_type, Value constant); - mutable annotated_shared_mutex lock; + mutex lock; ExpressionType comparison_type; - Value constant DUCKDB_GUARDED_BY(lock); + Value constant; atomic initialized = {false}; unique_ptr ToExpression(const Expression &column) const; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp index 3e0a7a6a4..7a67a26d9 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp @@ -31,10 +31,6 @@ class LogicalJoin : public LogicalOperator { static void GetExpressionBindings(const Expression &expr, unordered_set &bindings); //! Moves the state shared by all logical joins when replacing one join operator with another static void MoveJoinState(LogicalJoin &source, LogicalJoin &target); - //! Replaces a join by one of its sides while keeping the join's output layout: the side's projection map, if - //! any, is carried by a filter without conditions so that operators above the join keep seeing the same columns - static unique_ptr PromoteJoinSide(unique_ptr child, - vector projection_map); bool HasProjectionMap() const override { return !left_projection_map.empty() || !right_projection_map.empty(); diff --git a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp index 211a6158b..84e7d68cc 100644 --- a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp +++ b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp @@ -14,7 +14,6 @@ #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/common/type_visitor.hpp" -#include "duckdb/planner/expression.hpp" #include "duckdb/planner/logical_plan_verification_result.hpp" namespace duckdb { @@ -68,12 +67,6 @@ struct SQLExportHelpers { return issue; } - //! Constant ORDER BY keys have no effect, and a literal key would be bound as a position or rejected - static bool IsConstantOrderKey(const Expression &bound, const ParsedExpression &exported) { - return bound.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT || - exported.GetExpressionClass() == ExpressionClass::CONSTANT; - } - static unique_ptr OrderExpression(const LogicalType &type, unique_ptr expression) { if (type.id() == LogicalTypeId::VARCHAR) { diff --git a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp index 329c24704..0a5dff2c1 100644 --- a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp +++ b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp @@ -46,10 +46,10 @@ class BlockAllocator { bool SupportsFlush() const; void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count) const; void FlushAll(optional_idx extra_memory = optional_idx()) const; - //! Estimated memory freed but still held by the allocator - idx_t GetEstimatedUnflushedMemory() const; - //! Flush if at least threshold bytes were freed since the last reclamation check, returns whether a flush happened - bool TryFlushDeallocated(idx_t threshold, idx_t memory_limit, idx_t used_memory) const; + //! Memory freed since the last flush, which the allocator may still hold + idx_t GetDeallocatedSinceFlush() const; + //! Flush if at least threshold bytes were freed since the last flush, returns whether a flush happened + bool TryFlushDeallocated(idx_t threshold) const; private: bool IsActive() const; @@ -72,8 +72,8 @@ class BlockAllocator { const hugeint_t uuid; //! Fallback allocator Allocator &allocator; - //! Estimated memory freed but still held by the allocator - mutable atomic estimated_unflushed_memory {0}; + //! Memory freed since the last flush + mutable atomic deallocated_since_flush {0}; //! Block size (power of two) const idx_t block_size; diff --git a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp index 25a39088e..0af0858c3 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp @@ -26,7 +26,7 @@ struct CompressedStringScanState : public SegmentScanState { void ScanToDictionaryVector(ColumnSegment &segment, Vector &result, idx_t result_offset, idx_t start, idx_t scan_count); const SelectionVector &GetSelVec(idx_t start, idx_t scan_count); - void Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, idx_t sel_count); + void Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count); bool AllowDictionaryScan(idx_t scan_count); diff --git a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp index 099a2d2f6..3d13ccad0 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp @@ -98,7 +98,7 @@ class EmptyValidityCompression { TableFilterState &filter_state) { } static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + const SelectionVector &sel, idx_t sel_count) { } }; diff --git a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp index df0b6ac82..2befc2e99 100644 --- a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp +++ b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp @@ -120,7 +120,7 @@ struct UncompressedStringStorage { idx_t result_offset); static void StringScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); + const SelectionVector &sel, idx_t sel_count); static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp index 8456d63dd..47b8893ea 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp @@ -247,7 +247,7 @@ class ColumnData : public enable_shared_from_this { idx_t ScanVector(TransactionData transaction, idx_t vector_index, ColumnScanState &state, Vector &result, idx_t target_scan, UpdateScanType update_type); void SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count, ScanVectorType scan_type); + idx_t sel_count); void FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp index 25a5cf012..aac02c130 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp @@ -63,8 +63,7 @@ class ColumnSegment : public SegmentBase { //! Scan one vector from this segment void Scan(ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset, ScanVectorType scan_type); //! Scan a subset of a vector (defined by the selection vector) - void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count, - idx_t result_offset, ScanVectorType scan_type); + void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count); //! Scan one vector while applying a filter to the vector, returning only the matching elements void Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/logging/log_types.cpp b/src/duckdb/src/logging/log_types.cpp index bb25617f0..ec4f514db 100644 --- a/src/duckdb/src/logging/log_types.cpp +++ b/src/duckdb/src/logging/log_types.cpp @@ -3,7 +3,6 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/file_opener.hpp" #include "duckdb/main/http/http_util.hpp" -#include "duckdb/main/profiler/profiling_utils.hpp" #include "duckdb/execution/physical_operator.hpp" #include "duckdb/logging/file_system_logger.hpp" #include "duckdb/main/attached_database.hpp" @@ -363,19 +362,14 @@ LogicalType MetricsLogType::GetLogType() { child_list_t child_list = { {"metric", LogicalType::VARCHAR}, {"value", LogicalType::VARCHAR}, - {"start_time", LogicalType::TIMESTAMP_TZ}, - {"end_time", LogicalType::TIMESTAMP_TZ}, }; return LogicalType::STRUCT(child_list); } -string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value, - optional_ptr timing) { +string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value) { child_list_t child_list = { {"metric", metric}, {"value", value.ToString()}, - {"start_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->start_time) : Value()}, - {"end_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->end_time) : Value()}, }; return Value::STRUCT(std::move(child_list)).ToString(); } diff --git a/src/duckdb/src/main/capi/v1/arrow-c.cpp b/src/duckdb/src/main/capi/v1/arrow-c.cpp index d2432e60c..e07b834e4 100644 --- a/src/duckdb/src/main/capi/v1/arrow-c.cpp +++ b/src/duckdb/src/main/capi/v1/arrow-c.cpp @@ -138,18 +138,17 @@ duckdb_error_data duckdb_data_chunk_from_arrow(duckdb_connection connection, str auto &arrow_types = arrow_table->GetColumns(); dchunk->SetChildCardinality(duckdb::NumericCast(arrow_array->length)); - // All columns share ownership of the array, so it is only released once no column references it anymore - auto owned_data = duckdb::make_shared_ptr(); - owned_data->arrow_array = *arrow_array; - // We set it to nullptr to effectively transfer the ownership - arrow_array->release = nullptr; for (idx_t i = 0; i < dchunk->ColumnCount(); i++) { auto &parent_array = *arrow_array; auto &array = parent_array.children[i]; auto arrow_type = arrow_types.at(i); auto array_physical_type = arrow_type->GetPhysicalType(); auto array_state = duckdb::make_uniq(*conn->context); - array_state->owned_data = owned_data; + // We need to make sure that our chunk will hold the ownership + array_state->owned_data = duckdb::make_shared_ptr(); + array_state->owned_data->arrow_array = *arrow_array; + // We set it to nullptr to effectively transfer the ownership + arrow_array->release = nullptr; try { switch (array_physical_type) { case duckdb::ArrowArrayPhysicalType::DICTIONARY_ENCODED: diff --git a/src/duckdb/src/main/client_context.cpp b/src/duckdb/src/main/client_context.cpp index 62ae13551..7db374a9e 100644 --- a/src/duckdb/src/main/client_context.cpp +++ b/src/duckdb/src/main/client_context.cpp @@ -1106,16 +1106,7 @@ void ClientContext::InterruptCheck() const { throw InterruptException(); } // Only check timeout every N calls to avoid expensive steady_clock::now() syscall - if (query_deadline.IsValid() && ++timeout_check_counter % TIMEOUT_CHECK_INTERVAL == 0) { - BlockingInterruptCheck(); - } -} - -void ClientContext::BlockingInterruptCheck() const { - if (IsInterrupted()) { - throw InterruptException(); - } - if (QueryDeadlinePassed()) { + if (query_deadline.IsValid() && ++timeout_check_counter % TIMEOUT_CHECK_INTERVAL == 0 && QueryDeadlinePassed()) { throw InterruptException(QUERY_DEADLINE_ERROR); } } diff --git a/src/duckdb/src/main/client_context_execution.cpp b/src/duckdb/src/main/client_context_execution.cpp index 2b204776e..147fcfd40 100644 --- a/src/duckdb/src/main/client_context_execution.cpp +++ b/src/duckdb/src/main/client_context_execution.cpp @@ -467,7 +467,6 @@ bool ClientContext::IsActiveResult(ClientContextLock &lock, BaseQueryResult &res } bool ClientContext::ExecutionIsFinished() { - lock_guard guard(context_lock); if (!active_query || !active_query->executor || active_query->HasNextStatement()) { return false; } diff --git a/src/duckdb/src/main/config.cpp b/src/duckdb/src/main/config.cpp index 83c4bf9ba..66f3ed20b 100644 --- a/src/duckdb/src/main/config.cpp +++ b/src/duckdb/src/main/config.cpp @@ -922,8 +922,8 @@ const ExtensionCallbackManager &DBConfig::GetCallbackManager() const { return *callback_manager; } -string DBConfig::SanitizeAllowedPath(const string &path_p, optional_ptr opener) const { - auto result = file_system->CanonicalizePath(FileSystem::ExpandPath(path_p, opener)); +string DBConfig::SanitizeAllowedPath(const string &path_p) const { + auto result = file_system->CanonicalizePath(path_p); // allowed_directories/allowed_path always uses forward slashes regardless of the OS auto path_sep = file_system->PathSeparator(path_p); if (path_sep != "/") { @@ -976,21 +976,6 @@ void DBConfig::AddAllowedDirectory(const string &path) { options.allowed_directories.insert(allowed_directory); } -bool DBConfig::IsOptimizerDisabled(OptimizerType type) const { - lock_guard guard(disabled_optimizers_lock); - return options.disabled_optimizers.find(type) != options.disabled_optimizers.end(); -} - -set DBConfig::GetDisabledOptimizers() const { - lock_guard guard(disabled_optimizers_lock); - return options.disabled_optimizers; -} - -void DBConfig::SetDisabledOptimizers(set disabled_optimizers) { - lock_guard guard(disabled_optimizers_lock); - options.disabled_optimizers = std::move(disabled_optimizers); -} - void DBConfig::AddAllowedPath(const string &path) { auto allowed_path = SanitizeAllowedPath(path); lock_guard guard(allowed_paths_lock); @@ -1014,12 +999,12 @@ vector DBConfig::GetAllowedPaths() const { return vector(options.allowed_paths.begin(), options.allowed_paths.end()); } -bool DBConfig::CanAccessFile(const string &input_path, FileType type, optional_ptr opener) { +bool DBConfig::CanAccessFile(const string &input_path, FileType type) { if (Settings::Get(*this)) { // all external access is allowed return true; } - string path = SanitizeAllowedPath(input_path, opener); + string path = SanitizeAllowedPath(input_path); lock_guard guard(allowed_paths_lock); if (options.allowed_paths.count(path) > 0) { diff --git a/src/duckdb/src/main/extension/extension_helper.cpp b/src/duckdb/src/main/extension/extension_helper.cpp index 2835029e8..7ab8b9303 100644 --- a/src/duckdb/src/main/extension/extension_helper.cpp +++ b/src/duckdb/src/main/extension/extension_helper.cpp @@ -237,9 +237,6 @@ bool ExtensionHelper::TryAutoLoadAvailableExtension(DatabaseInstance &instance, return true; } try { - if (!Settings::Get(instance)) { - return false; - } auto &fs = FileSystem::GetFileSystem(instance); ExtensionHelper::LoadExternalExtension(instance, fs, AutoLoadOptions(extension_name)); return true; diff --git a/src/duckdb/src/main/extension_manager.cpp b/src/duckdb/src/main/extension_manager.cpp index ee186a7fa..c888a2405 100644 --- a/src/duckdb/src/main/extension_manager.cpp +++ b/src/duckdb/src/main/extension_manager.cpp @@ -56,7 +56,7 @@ void ExtensionManager::RemoveExtensionInfo(const string &name) { loaded_extensions_info.erase(Identifier(name)); } -shared_ptr ExtensionManager::GetExtensionInfo(const string &name) { +optional_ptr ExtensionManager::GetExtensionInfo(const string &name) { auto extension_name = ExtensionHelper::GetExtensionName(name); lock_guard guard(lock); @@ -64,7 +64,7 @@ shared_ptr ExtensionManager::GetExtensionInfo(const string &name) if (entry == loaded_extensions_info.end()) { return nullptr; } - return entry->second; + return entry->second.get(); } vector ExtensionManager::GetExtensions() { @@ -101,7 +101,7 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO extension_name = ExtensionHelper::GetExtensionName(options.extension_name); string original_extension_name = extension_name; - shared_ptr info; + optional_ptr info; auto entry = loaded_extensions_info.end(); if (options.alias.empty()) { @@ -113,9 +113,9 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO // check if the extension is not already registered under an alias // we don't have an entry yet - create one - auto extension_info = make_shared_ptr(); + auto extension_info = make_uniq(); extension_info->orig_ext_name = original_extension_name; - info = extension_info; + info = extension_info.get(); if (!options.alias.empty()) { // if aliasing is used, we need to register the alias instead of extension name @@ -142,13 +142,13 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO return nullptr; } // it is not loaded yet - try to load it - info = entry->second; + info = entry->second.get(); } extension_list_lock.unlock(); // we have an extension and we want to try to load it - instantiate the load // we instantiate the ExtensionActiveLoad which also grabs the lock for loading the specific extension - auto result = make_uniq(db, info, Identifier(original_extension_name), options.alias); + auto result = make_uniq(db, *info, Identifier(original_extension_name), options.alias); // we now have a lock for loading the extension // HOWEVER - another thread might have finished loading in the meantime - double check to avoid a double load diff --git a/src/duckdb/src/main/http/http_retry_budget.cpp b/src/duckdb/src/main/http/http_retry_budget.cpp index 7ba67f2a8..3fb00e874 100644 --- a/src/duckdb/src/main/http/http_retry_budget.cpp +++ b/src/duckdb/src/main/http/http_retry_budget.cpp @@ -3,14 +3,11 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/random_engine.hpp" -#include "duckdb/common/string_util.hpp" -#include "duckdb/common/types/timestamp.hpp" -#include "duckdb/function/scalar/strftime_format.hpp" #include "duckdb/main/http/http_util.hpp" -#include #ifndef DUCKDB_NO_THREADS #include +#include #include #endif @@ -20,226 +17,22 @@ HTTPRetryDecision HTTPRetryDecision::Finish() { return HTTPRetryDecision(Type::FINISH); } -HTTPRetryDecision HTTPRetryDecision::Success() { - return HTTPRetryDecision(Type::SUCCESS); -} - -HTTPRetryDecision HTTPRetryDecision::Failure() { - return HTTPRetryDecision(Type::FAILURE); -} - -HTTPRetryDecision HTTPRetryDecision::Correction() { - return HTTPRetryDecision(Type::CORRECTION); -} - HTTPRetryDecision HTTPRetryDecision::Retry() { return HTTPRetryDecision(Type::RETRY); } -HTTPRetryDecision HTTPRetryDecision::Transient() { - return HTTPRetryDecision(Type::TRANSIENT); -} - HTTPRetryDecision HTTPRetryDecision::Throttled(const string &retry_after) { return HTTPRetryDecision(Type::THROTTLED, retry_after); } -void HTTPRetryQuota::Touch(int64_t now_ms) { - if (now_ms - last_used_ms >= IDLE_RESET_MS) { - capacity = INITIAL_CAPACITY; - } - last_used_ms = now_ms; -} - -bool HTTPRetryQuota::TryAcquire(int64_t cost, int64_t now_ms) { - lock_guard guard(lock); - Touch(now_ms); - if (cost > capacity) { - return false; - } - capacity -= cost; - return true; -} - -void HTTPRetryQuota::Release(int64_t amount, int64_t now_ms) { - lock_guard guard(lock); - Touch(now_ms); - capacity = MinValue(capacity + amount, INITIAL_CAPACITY); -} - -int64_t HTTPRetryQuota::Available() const { - lock_guard guard(lock); - return capacity; -} - -int64_t HTTPRetryQuota::IdleMs(int64_t now_ms) const { - lock_guard guard(lock); - return now_ms - last_used_ms; -} - -HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms, HTTPRetryOutcomeOwner owner_p) - : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff), - owner(owner_p) { -#ifdef DUCKDB_NO_THREADS - // Without threads there is no back-off sleep, so the schedule settings go unused. - (void)retry_wait_ms; - (void)retry_backoff; -#endif +HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms) + : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff) { } uint64_t HTTPRetryBudget::ThrottledRetries() const { return throttled_retries; } -uint64_t HTTPRetryBudget::RetriesUsed() const { - return retries_used; -} - -uint64_t HTTPRetryBudget::BeginAttempt() { - return ++attempts; -} - -uint64_t HTTPRetryBudget::ThrottleExtraRetries() const { -#ifndef DUCKDB_NO_THREADS - // http_retries=0 switches retries off entirely, throttled or not. - return retries > 0 ? THROTTLE_EXTRA_RETRIES : 0; -#else - // Without threads we cannot sleep between retries, so do not add zero-delay retries. - return 0; -#endif -} - -uint64_t HTTPRetryBudget::Corrections() const { - // The first send and every admitted retry are accounted for; any other send was a corrected re-send. - const auto accounted = 1 + retries_used; - return attempts > accounted ? attempts - accounted : 0; -} - -uint64_t HTTPRetryBudget::MaxAttempts() const { - return 1 + retries + ThrottleExtraRetries() + Corrections(); -} - -bool HTTPRetryBudget::InRun() const { - return run_depth > 0; -} - -bool HTTPRetryBudget::QuotaExhausted() const { - return quota_exhausted; -} - -bool HTTPRetryBudget::CorrectionsExhausted() const { - return corrections_exhausted; -} - -void HTTPRetryBudget::BindQuota(shared_ptr quota_p) { - quota = std::move(quota_p); -} - -HTTPRetryOutcomeOwner HTTPRetryBudget::Owner() const { - return owner; -} - -void HTTPRetryBudget::RequireCoordinator(const char *what) const { - if (owner != HTTPRetryOutcomeOwner::COORDINATOR) { - throw InternalException( - "HTTP retry budget: %s is the coordinator's decision, but this operation's outcome is " - "owned by the HTTP status; construct the budget with HTTPRetryOutcomeOwner::COORDINATOR", - what); - } -} - -void HTTPRetryBudget::ReportSuccess() { - RequireCoordinator("ReportSuccess"); - Settle(true); -} - -void HTTPRetryBudget::ReportFailure() { - RequireCoordinator("ReportFailure"); - Settle(false); -} - -void HTTPRetryBudget::RecordStatusOutcome(bool success) { - if (owner != HTTPRetryOutcomeOwner::STATUS) { - // The coordinator validates the response and reports; what the status says is never final here. - return; - } - status_outcome = success ? RecordedOutcome::SUCCESS : RecordedOutcome::FAILURE; - if (!InRun()) { - // Nobody else is going to judge this response. - Settle(success); - } -} - -void HTTPRetryBudget::Settle(bool success) { - status_outcome = RecordedOutcome::NONE; - if (outcome_settled) { - // Refunded or charged already: the operation has one outcome. - return; - } - outcome_settled = true; - if (success) { - if (last_retry_quota) { - last_retry_quota->Release(last_retry_cost); - } else if (quota) { - quota->Release(HTTPRetryQuota::NO_RETRY_REFUND); - } - } - ForgetLastRetry(); -} - -void HTTPRetryBudget::ForgetLastRetry() { - last_retry_quota.reset(); - last_retry_cost = 0; -} - -uint64_t HTTPRetryBudget::BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, - uint64_t random_value, bool throttled) { - D_ASSERT(retry > 0); - const auto exponent = static_cast(retry - 1); - // Cap in the double domain so the exponential cannot overflow the cast. - const auto base_ms = - MinValue(static_cast(retry_wait_ms) * pow(static_cast(retry_backoff), exponent), - static_cast(MAX_BACKOFF_MS)); - const auto base = static_cast(base_ms); - // Throttled retries never collapse onto the moment the server asked us to slow down. - const auto lower = throttled ? base / 2 : 0; - const auto jittered = lower + random_value % (base - lower + 1); - return MaxValue(jittered, MinValue(floor_ms, MAX_BACKOFF_MS)); -} - -static const StrpTimeFormat &RetryAfterDateFormat() { - // RFC 7231 IMF-fixdate, e.g. "Sun, 06 Nov 1994 08:49:37 GMT". - static const StrpTimeFormat format = []() { - StrpTimeFormat result; - auto error = StrTimeFormat::ParseFormatSpecifier("%a, %d %b %Y %H:%M:%S GMT", result); - D_ASSERT(error.empty()); - (void)error; - return result; - }(); - return format; -} - -bool HTTPRetryBudget::TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms) { - string value = retry_after; - StringUtil::Trim(value); - if (value.empty()) { - return false; - } - uint64_t seconds = 0; - if (TryCast::Operation(string_t(value), seconds)) { - delay_ms = seconds > MAX_BACKOFF_MS / 1000 ? MAX_BACKOFF_MS : seconds * 1000; - return true; - } - timestamp_t target; - if (!RetryAfterDateFormat().TryParseTimestamp(value.c_str(), value.size(), target)) { - return false; - } - const auto now_ms = Timestamp::GetEpochMs(Timestamp::GetCurrentTimestamp()); - const auto target_ms = Timestamp::GetEpochMs(target); - delay_ms = target_ms > now_ms ? static_cast(target_ms - now_ms) : 0; - return true; -} - void HTTPRetryBudget::Run(const std::function &attempt) { Run(attempt, {}); } @@ -249,110 +42,60 @@ void HTTPRetryBudget::Run(const std::function &attempt, if (!attempt) { throw InternalException("HTTP retry loop requires an attempt callback"); } - // A refusal belongs to the run that hit it, not to later requests sharing the budget. - quota_exhausted = false; - corrections_exhausted = false; - const bool outermost = !InRun(); - if (outermost) { - // A new operation leg: whatever an earlier leg settled is history. - status_outcome = RecordedOutcome::NONE; - outcome_settled = false; - } - struct RunDepthGuard { - explicit RunDepthGuard(uint64_t &depth_p) : depth(depth_p) { - depth++; - } - ~RunDepthGuard() { - depth--; - } - uint64_t &depth; - } depth_guard(run_depth); for (;;) { auto decision = attempt(); - switch (decision.type) { - case HTTPRetryDecision::Type::SUCCESS: - // The coordinator validated the response: the explicit outcome is final wherever it is decided. - RequireCoordinator("a Success decision"); - Settle(true); - return; - case HTTPRetryDecision::Type::FAILURE: - RequireCoordinator("a Failure decision"); - Settle(false); + if (decision.type == HTTPRetryDecision::Type::FINISH || !ConsumeAndWait(decision)) { return; - case HTTPRetryDecision::Type::FINISH: - if (outermost && status_outcome != RecordedOutcome::NONE) { - // A status-owned operation is over and nobody judges it: the outcome core recorded from the status - // is settled now. Without one the caller records the outcome itself after the run. A - // coordinator-owned budget never records a status and waits for the coordinator's report. - Settle(status_outcome == RecordedOutcome::SUCCESS); - } - return; - case HTTPRetryDecision::Type::CORRECTION: - if (!AdmitCorrection()) { - return; - } - continue; - default: - if (!ConsumeAndWait(decision)) { - return; - } - if (before_retry) { - before_retry(); - } + } + if (before_retry) { + before_retry(); } } } -bool HTTPRetryBudget::AdmitCorrection() { - // The corrected request is a new send of the same operation: the last response does not count. - status_outcome = RecordedOutcome::NONE; - if (corrections_admitted >= MAX_CORRECTIONS) { - // The operation ends with the response it has, and its retries did not get it through. - corrections_exhausted = true; - ForgetLastRetry(); - return false; - } - corrections_admitted++; - return true; -} - bool HTTPRetryBudget::ConsumeAndWait(const HTTPRetryDecision &decision) { D_ASSERT(decision.type != HTTPRetryDecision::Type::FINISH); const bool throttled = decision.type == HTTPRetryDecision::Type::THROTTLED; - const auto extra_retries = throttled ? ThrottleExtraRetries() : 0; - // Asking for a retry means the last attempt did not get the operation through, whatever a nested loop recorded. - status_outcome = RecordedOutcome::NONE; +#ifndef DUCKDB_NO_THREADS + static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; +#else + // Without threads we cannot sleep between retries, so do not add zero-delay retries. + static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 0; +#endif + const auto extra_retries = throttled ? THROTTLE_EXTRA_RETRIES : 0; if (retries_used >= retries && retries_used - retries >= extra_retries) { - // Nothing left to refund: the retries already spent did not get this request through. - ForgetLastRetry(); - return false; - } - const auto cost = decision.type == HTTPRetryDecision::Type::TRANSIENT ? HTTPRetryQuota::TRANSIENT_RETRY_COST - : HTTPRetryQuota::RETRY_COST; - if (quota && !quota->TryAcquire(cost)) { - quota_exhausted = true; - ForgetLastRetry(); return false; } - // The admitted retry is the one a later success refunds; its outcome is still open. - last_retry_quota = quota; - last_retry_cost = cost; - outcome_settled = false; retries_used++; if (throttled) { throttled_retries++; } #ifndef DUCKDB_NO_THREADS - uint64_t floor_ms = 0; - if (throttled && !decision.retry_after.empty()) { - TryParseRetryAfterMs(decision.retry_after, floor_ms); - } - RandomEngine random; - const auto sleep_ms = - BackoffMs(retry_wait_ms, retry_backoff, retries_used, floor_ms, random.NextRandomInteger64(), throttled); - if (sleep_ms > 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + if (retries_used > 1 || throttled) { + static constexpr uint64_t THROTTLE_MAX_BACKOFF_MS = 10000; + const auto backoff_exp = static_cast(throttled ? retries_used - 1 : retries_used - 2); + const auto backoff_ms = (double)retry_wait_ms * pow(retry_backoff, backoff_exp); + // Cap in the double domain to avoid overflow in the cast. + uint64_t sleep_amount = (uint64_t)MinValue(backoff_ms, (double)NumericLimits::Maximum()); + if (throttled) { + sleep_amount = MinValue(sleep_amount, THROTTLE_MAX_BACKOFF_MS); + if (!decision.retry_after.empty()) { + // Honor a numeric Retry-After (seconds), capped like the backoff. + uint64_t retry_after_s = 0; + if (TryCast::Operation(string_t(decision.retry_after), retry_after_s)) { + retry_after_s = MinValue(retry_after_s, THROTTLE_MAX_BACKOFF_MS / 1000); + sleep_amount = MaxValue(sleep_amount, retry_after_s * 1000); + } + } + // Subtractive jitter ([base/2, base]) de-synchronizes retry bursts while honoring the cap. + RandomEngine random; + sleep_amount -= random.NextRandomInteger64() % (sleep_amount / 2 + 1); + } + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_amount)); } +#else + (void)retry_wait_ms; + (void)retry_backoff; #endif return true; } diff --git a/src/duckdb/src/main/http/http_util.cpp b/src/duckdb/src/main/http/http_util.cpp index 435977999..cc7896ca2 100644 --- a/src/duckdb/src/main/http/http_util.cpp +++ b/src/duckdb/src/main/http/http_util.cpp @@ -123,26 +123,17 @@ bool HTTPResponse::ShouldRetry() const { // request errors are eligible for retry return true; } - // Core judges by status only; what a service says in the body is the provider's call (HTTPUtil::ShouldRetry). - return HTTPUtil::IsRetryableStatus(status); -} - -bool HTTPUtil::IsRetryableStatus(HTTPStatusCode status) { switch (status) { case HTTPStatusCode::RequestTimeout_408: case HTTPStatusCode::ImATeapot_418: case HTTPStatusCode::TooManyRequests_429: + case HTTPStatusCode::InternalServerError_500: + case HTTPStatusCode::BadGateway_502: + case HTTPStatusCode::ServiceUnavailable_503: + case HTTPStatusCode::GatewayTimeout_504: return true; - // Server errors that describe the request or the server's abilities, not a passing condition. - case HTTPStatusCode::NotImplemented_501: - case HTTPStatusCode::HttpVersionNotSupported_505: - case HTTPStatusCode::NetworkAuthenticationRequired_511: + default: return false; - default: { - // Any other server error is eligible, as in the AWS SDKs. - const auto code = static_cast(status); - return code >= 500 && code < 600; - } } } @@ -154,32 +145,6 @@ bool HTTPUtil::ShouldRetry(const BaseRequest &request, const HTTPResponse &respo return response.ShouldRetry(); } -void HTTPUtil::PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts) { - // default: requests are sent as-is -} - -shared_ptr HTTPUtil::GetRetryQuota(const string &proto_host_port) { - lock_guard guard(retry_quota_lock); - auto entry = retry_quotas.find(proto_host_port); - if (entry != retry_quotas.end()) { - return entry->second; - } - if (retry_quotas.size() >= MAX_RETRY_QUOTAS) { - // Forget the host that has been quiet for longest; an idle quota would have been reset anyway. - const auto now_ms = TimePoint::GetTickMs(); - auto victim = retry_quotas.begin(); - for (auto it = retry_quotas.begin(); it != retry_quotas.end(); ++it) { - if (it->second->IdleMs(now_ms) > victim->second->IdleMs(now_ms)) { - victim = it; - } - } - retry_quotas.erase(victim); - } - auto quota = make_shared_ptr(); - retry_quotas.emplace(proto_host_port, quota); - return quota; -} - unique_ptr HTTPUtil::Request(BaseRequest &request) { unique_ptr client; return Request(request, client); @@ -403,61 +368,28 @@ void HTTPUtil::DecomposeURL(const string &input, string &path_out, string &proto } } -// Handlers turn error statuses into exceptions; rebuild the response they saw so it can be judged like a received one. -static unique_ptr ResponseFromError(const ErrorData &error) { - auto &extra_info = error.ExtraInfo(); - auto status_entry = extra_info.find("status_code"); - int32_t status_code = 0; - if (status_entry == extra_info.end() || - !TryCast::Operation(string_t(status_entry->second), status_code)) { - return nullptr; - } - auto response = make_uniq(HTTPUtil::ToStatusCode(status_code)); - for (auto &entry : extra_info) { - if (entry.first == "response_body") { - response->body = entry.second; - } else if (entry.first == "reason") { - response->reason = entry.second; - } else if (StringUtil::StartsWith(entry.first, "header_")) { - response->headers.Insert(entry.first.substr(7), entry.second); - } - } - return response; -} - -// Retry eligible requests using an operation-local budget, the host's retry quota and the transport retry hook. -// This loop is not the operation's coordinator: it judges statuses (with the provider's ShouldRetry), returns Finish() -// once it stops and records what the status says. A coordinator-owned budget shared by a provider loop -// (request.retry_budget) ignores that record and settles the quota from the provider's validated outcome; the local -// status-owned budget settles it from the status. +// Retry eligible requests using an operation-local budget and the existing transport retry hook. duckdb::unique_ptr -HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, - const std::function &retry_cb) { +HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, + const BaseRequest &request, const std::function &retry_cb) { auto ¶ms = request.params; HTTPRetryBudget local_retry_budget(params); auto retry_budget = request.retry_budget; if (!retry_budget) { retry_budget = local_retry_budget; } - // Charge this request's host, even when a shared budget was bound to another host before. - retry_budget->BindQuota(params.http_util.GetRetryQuota(request.proto_host_port)); unique_ptr response; std::exception_ptr caught_e; - ErrorData caught_error; string exception_error; bool should_retry = false; retry_budget->Run( [&]() { response.reset(); caught_e = nullptr; - caught_error = ErrorData(); exception_error.clear(); - unique_ptr caught_response; - // Attempts are numbered per operation, so a re-send after a correction counts as the attempt it is; the - // bound grows with such re-sends, which do not consume retries. - const auto attempt = retry_budget->BeginAttempt(); - params.http_util.PrepareAttempt(request, attempt, retry_budget->MaxAttempts()); + string caught_status; + string caught_retry_after; try { response = on_request(); if (response) { @@ -466,97 +398,66 @@ HTTPUtil::RunRequestWithRetry(const std::function(void) } catch (IOException &e) { exception_error = e.what(); caught_e = std::current_exception(); - caught_error = ErrorData(e); } catch (HTTPException &e) { exception_error = e.what(); caught_e = std::current_exception(); - caught_error = ErrorData(e); - caught_response = ResponseFromError(caught_error); + // Handlers turn error statuses into exceptions; recover the throttle information. + ErrorData error_data(e); + auto entry = error_data.ExtraInfo().find("status_code"); + if (entry != error_data.ExtraInfo().end()) { + caught_status = entry->second; + } + auto retry_entry = error_data.ExtraInfo().find("header_Retry-After"); + if (retry_entry != error_data.ExtraInfo().end()) { + caught_retry_after = retry_entry->second; + } } - if (response) { - should_retry = params.http_util.ShouldRetry(request, *response); - } else if (caught_response) { - // A thrown status is judged like a received one, provider rules included. - should_retry = params.http_util.ShouldRetry(request, *caught_response); - } else { - // Without a status the transport failed and nothing is known about the server. - should_retry = true; - } + // Request errors and caught exceptions are eligible without a response status. + should_retry = !response || params.http_util.ShouldRetry(request, *response); if (!should_retry) { - if (response) { - auto response_code = static_cast(response->status); - response->success = (response_code >= 200 && response_code < 300) || - response->status == HTTPStatusCode::NotModified_304; - } + auto response_code = static_cast(response->status); + response->success = (response_code >= 200 && response_code < 300) || + response->status == HTTPStatusCode::NotModified_304; return HTTPRetryDecision::Finish(); } if (!HTTPUtil::IsIdempotent(request.type)) { return HTTPRetryDecision::Finish(); } - if (!response && !caught_response) { - // The transport failed before any status arrived: nothing is known about the server. - return HTTPRetryDecision::Transient(); - } - auto &judged = response ? *response : *caught_response; - if (judged.HasRequestError()) { - return HTTPRetryDecision::Transient(); - } - if (judged.status == HTTPStatusCode::TooManyRequests_429 || - judged.status == HTTPStatusCode::ServiceUnavailable_503) { - return HTTPRetryDecision::Throttled( - judged.headers.HasHeader("Retry-After") ? judged.headers.GetHeaderValue("Retry-After") : string()); + const bool throttled = (response && (response->status == HTTPStatusCode::TooManyRequests_429 || + response->status == HTTPStatusCode::ServiceUnavailable_503)) || + caught_status == "429" || caught_status == "503"; + if (throttled) { + if (response && response->headers.HasHeader("Retry-After")) { + caught_retry_after = response->headers.GetHeaderValue("Retry-After"); + } + return HTTPRetryDecision::Throttled(caught_retry_after); } return HTTPRetryDecision::Retry(); }, retry_cb); - // With a coordinator-owned budget the outcome is settled by the coordinator once it has validated the response: - // an error document behind HTTP 200 is a failure or a retry there and must not refund the retry it consumed. - if (!should_retry && response) { - retry_budget->RecordStatusOutcome(response->success); + if (!should_retry) { return response; } - // The request failed: it threw, could not be retried any further, or is not replay-safe. - retry_budget->RecordStatusOutcome(false); - string quota_note; - if (retry_budget->QuotaExhausted()) { - quota_note = StringUtil::Format(" (retry quota for '%s' exhausted: requests to this host keep failing)", - request.proto_host_port); - } if (request.try_request) { if (!response) { response = make_uniq(HTTPStatusCode::INVALID); - response->request_error = - (exception_error.empty() ? "Unknown error" : std::move(exception_error)) + quota_note; - } else if (!quota_note.empty()) { - // A received error keeps its status; the note goes to the field GetError() reports. - if (response->HasRequestError()) { - response->request_error += quota_note; - } else { - response->reason += quota_note; - } + response->request_error = exception_error.empty() ? "Unknown error" : std::move(exception_error); } response->success = false; return response; } auto method = EnumUtil::ToString(request.type); if (caught_e) { - if (quota_note.empty()) { - std::rethrow_exception(caught_e); - } - // Same type and extra info as the caught exception, with the quota note appended. - if (caught_error.Type() == ExceptionType::HTTP) { - throw HTTPException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); - } - throw IOException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); + std::rethrow_exception(caught_e); } if (response && !response->HasRequestError()) { - throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'%s", - static_cast(response->status), method, request.url, quota_note); + throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'", + static_cast(response->status), method, request.url); } string error = response ? response->GetError() : "Unknown error"; - throw IOException("%s error for HTTP %s to '%s'%s", error, method, request.url, quota_note); + throw IOException("%s error for HTTP %s to '%s'", error, method, request.url); } void HTTPParams::Initialize(optional_ptr opener) { diff --git a/src/duckdb/src/main/parser_context.cpp b/src/duckdb/src/main/parser_context.cpp index ea064a47c..98183a219 100644 --- a/src/duckdb/src/main/parser_context.cpp +++ b/src/duckdb/src/main/parser_context.cpp @@ -5,7 +5,6 @@ #include "duckdb/main/client_config.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_callback_manager.hpp" -#include "duckdb/parser/grammar_extension.hpp" #include "duckdb/parser/peg/passthrough_dialect.hpp" namespace duckdb { @@ -40,7 +39,15 @@ shared_ptr CompiledGrammar::Get(ClientContext &context) { shared_ptr CompiledGrammar::Create(const ClientContext &context, const vector &active_extensions) { - return context.db->GetParserCache().GetMatcher(context, active_extensions); + vector> selected_extensions; + auto &callback_manager = ExtensionCallbackManager::Get(context); + for (auto &name : active_extensions) { + auto grammar_extension = callback_manager.FindGrammarExtension(name); + if (grammar_extension) { + selected_extensions.emplace_back(*grammar_extension); + } + } + return Create(selected_extensions); } ParserCache::ParserCache() { @@ -65,39 +72,6 @@ shared_ptr ParserCache::GetMatcher() { return matcher; } -shared_ptr ParserCache::GetMatcher(const ClientContext &context, - const vector &active_extensions) { - vector> selected_extensions; - vector extension_names; - auto &callback_manager = ExtensionCallbackManager::Get(context); - for (auto &name : active_extensions) { - auto grammar_extension = callback_manager.FindGrammarExtension(name); - if (grammar_extension) { - selected_extensions.emplace_back(*grammar_extension); - extension_names.push_back(grammar_extension->Name()); - } - } - // compile while holding the lock, so concurrent first uses of the same extensions only compile once - lock_guard lock(extension_mutex); - for (idx_t i = 0; i < extension_matchers.size(); i++) { - if (extension_matchers[i].first == extension_names) { - // move the entry to the back, which holds the most recently used grammar - auto entry = std::move(extension_matchers[i]); - extension_matchers.erase(extension_matchers.begin() + NumericCast(i)); - extension_matchers.push_back(std::move(entry)); - return extension_matchers.back().second; - } - } - // compile before evicting, so a grammar that fails to compile does not evict a valid one - auto new_matcher = CompiledGrammar::Create(selected_extensions); - if (extension_matchers.size() >= MAX_CACHED_EXTENSION_MATCHERS) { - // connections that still use the evicted grammar keep their own reference to it - extension_matchers.erase(extension_matchers.begin()); - } - extension_matchers.emplace_back(std::move(extension_names), std::move(new_matcher)); - return extension_matchers.back().second; -} - shared_ptr ParserCache::GetPassthroughMatcher(const ClientContext &context) { lock_guard lock(passthrough_mutex); if (!passthrough_dialect) { diff --git a/src/duckdb/src/main/profiler/gathered_metrics.cpp b/src/duckdb/src/main/profiler/gathered_metrics.cpp index e58f4e541..dc8f5f98f 100644 --- a/src/duckdb/src/main/profiler/gathered_metrics.cpp +++ b/src/duckdb/src/main/profiler/gathered_metrics.cpp @@ -105,15 +105,12 @@ void GatheredMetrics::SetMetric(const string &key, const string &value) { metrics[key] = Value(value); } -void GatheredMetrics::WriteMetricsToLog(ClientContext &context, - const unordered_map &timings) const { +void GatheredMetrics::WriteMetricsToLog(ClientContext &context) const { auto &logger = Logger::Get(context); if (logger.ShouldLog(MetricsLogType::NAME, MetricsLogType::LEVEL)) { for (const auto &entry : metrics) { - auto timing = timings.find(entry.first); logger.WriteLog(MetricsLogType::NAME, MetricsLogType::LEVEL, - MetricsLogType::ConstructLogMessage(entry.first, entry.second, - timing == timings.end() ? nullptr : &timing->second)); + MetricsLogType::ConstructLogMessage(entry.first, entry.second)); } } } diff --git a/src/duckdb/src/main/profiler/profiling_utils.cpp b/src/duckdb/src/main/profiler/profiling_utils.cpp index 7887993f6..29a40645c 100644 --- a/src/duckdb/src/main/profiler/profiling_utils.cpp +++ b/src/duckdb/src/main/profiler/profiling_utils.cpp @@ -5,8 +5,8 @@ namespace duckdb { void QueryMetrics::FinalizeMetrics(GatheredMetrics &info) { info.SetMetric(query_sql); - for (const auto &[key, timing] : string_timings) { - info.SetMetric(key, static_cast(timing.duration) / 1e9); + for (const auto &[key, ns] : string_timings) { + info.SetMetric(key, static_cast(ns) / 1e9); } for (const auto &[key, count] : string_counters) { info.SetMetric(key, count); diff --git a/src/duckdb/src/main/query_profiler.cpp b/src/duckdb/src/main/query_profiler.cpp index 7d9010747..4325e7b62 100644 --- a/src/duckdb/src/main/query_profiler.cpp +++ b/src/duckdb/src/main/query_profiler.cpp @@ -159,7 +159,7 @@ void QueryProfiler::StartQuery(const string &query, bool is_explain_analyze_p, b } Start(query); } -void QueryProfiler::AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time) { +void QueryProfiler::AddParserTime(const Profiler &parser_timer) { if (!running || !IsEnabled()) { return; } @@ -167,7 +167,7 @@ void QueryProfiler::AddParserTime(const Profiler &parser_timer, timestamp_t star if (!parser_time_ns) { return; } - query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns, start_time, end_time); + query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns); } bool QueryProfiler::OperatorRequiresProfiling(const PhysicalOperatorType op_type) { @@ -843,7 +843,7 @@ void QueryProfiler::ToLogInternal() const { if (!root) { return; } - metrics->WriteMetricsToLog(context, query_metrics.GetMetricTimings()); + metrics->WriteMetricsToLog(context); } void QueryProfiler::ToLog() const { diff --git a/src/duckdb/src/main/secret/default_secrets.cpp b/src/duckdb/src/main/secret/default_secrets.cpp index 4fa756253..f0dce5f5d 100644 --- a/src/duckdb/src/main/secret/default_secrets.cpp +++ b/src/duckdb/src/main/secret/default_secrets.cpp @@ -3,10 +3,6 @@ namespace duckdb { -identifier_set_t CreateHTTPSecretFunctions::GetRedactedKeys() { - return {"http_proxy_password", "bearer_token", "extra_http_headers"}; -} - vector CreateHTTPSecretFunctions::GetDefaultSecretTypes() { vector result; @@ -93,7 +89,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromEnv(Client secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = GetRedactedKeys(); + secret->redact_keys = {"http_proxy_password", "bearer_token"}; return std::move(secret); } @@ -111,7 +107,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromConfig(Cli secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = GetRedactedKeys(); + secret->redact_keys = {"http_proxy_password", "bearer_token"}; return std::move(secret); } diff --git a/src/duckdb/src/main/secret/secret.cpp b/src/duckdb/src/main/secret/secret.cpp index 32e5cc921..cf6bf6538 100644 --- a/src/duckdb/src/main/secret/secret.cpp +++ b/src/duckdb/src/main/secret/secret.cpp @@ -101,41 +101,6 @@ void KeyValueSecret::Serialize(Serializer &serializer) const { serializer.WriteProperty(202, "redact_keys", list); } -void KeyValueSecret::ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result) { - deserializer.ReadObject(201, "secret_map", [&](Deserializer &map) { - auto stored_type = map.ReadProperty(100, "type"); - // Before DuckDB 1.1.3, secrets declared VARCHAR values even when storing other types. - if (stored_type.id() != LogicalTypeId::MAP || !stored_type.HasParameters() || - MapType::KeyType(stored_type) != LogicalType::VARCHAR) { - throw SerializationException("Secret map must be a MAP with VARCHAR keys"); - } - if (map.ReadProperty(101, "is_null")) { - throw SerializationException("Secret map cannot be NULL"); - } - - map.Set(ListType::GetChildType(stored_type)); - map.ReadObject(102, "value", [&](Deserializer &obj) { - obj.ReadList(100, "children", [&](Deserializer::List &list, idx_t i) { - // Before DuckDB 1.2.0, entries carried their actual value types, even when the map declared VARCHAR. - // Deserialize them separately so their payloads still undergo normal Value validation. - auto entry = list.ReadElement(); - if (entry.IsNull() || entry.type().id() != LogicalTypeId::STRUCT) { - throw SerializationException("Secret map entry must be a non-NULL struct"); - } - const auto &children = StructValue::GetChildren(entry); - if (children.size() != 2) { - throw SerializationException("Secret map entry must have two fields"); - } - if (children[0].IsNull() || children[0].type() != LogicalType::VARCHAR) { - throw SerializationException("Secret map key must be a non-NULL VARCHAR"); - } - result[Identifier(StringValue::Get(children[0]))] = children[1]; - }); - }); - map.Unset(); - }); -} - Value KeyValueSecret::TryGetValue(const Identifier &key, bool error_on_missing) const { auto lookup = secret_map.find(key); if (lookup == secret_map.end()) { diff --git a/src/duckdb/src/main/secret/secret_manager.cpp b/src/duckdb/src/main/secret/secret_manager.cpp index 16ca921b5..5eb49e3b8 100644 --- a/src/duckdb/src/main/secret/secret_manager.cpp +++ b/src/duckdb/src/main/secret/secret_manager.cpp @@ -113,17 +113,8 @@ unique_ptr SecretManager::DeserializeSecret(Deserializer &deserializ switch (serialization_type) { // This allows us to skip looking up the secret type for deserialization altogether - case SecretSerializationType::KEY_VALUE_SECRET: { - auto result = - KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); - if (type == "http") { - // Preserve stored keys and apply current HTTP redaction rules to older secrets. - const auto keys = CreateHTTPSecretFunctions::GetRedactedKeys(); - auto &secret = result->Cast(); - secret.redact_keys.insert(keys.begin(), keys.end()); - } - return result; - } + case SecretSerializationType::KEY_VALUE_SECRET: + return KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); // Continues below: we need to do a type lookup to find the secret deserialize method case SecretSerializationType::CUSTOM: break; @@ -551,8 +542,8 @@ vector SecretManager::AllSecrets(CatalogTransaction transaction) { } // Add results from all backends to the result set - for (const auto &backend : GetSecretStorages()) { - auto backend_result = backend.get().AllSecrets(&transaction); + for (const auto &backend : secret_storages) { + auto backend_result = backend.second->AllSecrets(&transaction); for (const auto &it : backend_result) { result.push_back(it); } diff --git a/src/duckdb/src/main/settings/custom_settings.cpp b/src/duckdb/src/main/settings/custom_settings.cpp index 3b63cefb3..cf9449843 100644 --- a/src/duckdb/src/main/settings/custom_settings.cpp +++ b/src/duckdb/src/main/settings/custom_settings.cpp @@ -619,17 +619,17 @@ void DisabledOptimizersSetting::SetGlobal(DatabaseInstance *db, DBConfig &config } disabled_optimizers.insert(OptimizerTypeFromString(param)); } - config.SetDisabledOptimizers(std::move(disabled_optimizers)); + config.options.disabled_optimizers = std::move(disabled_optimizers); } void DisabledOptimizersSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) { - config.SetDisabledOptimizers(DBConfigOptions().disabled_optimizers); + config.options.disabled_optimizers = DBConfigOptions().disabled_optimizers; } Value DisabledOptimizersSetting::GetSetting(const ClientContext &context) { auto &config = DBConfig::GetConfig(context); string result; - for (auto &optimizer : config.GetDisabledOptimizers()) { + for (auto &optimizer : config.options.disabled_optimizers) { if (!result.empty()) { result += ","; } @@ -1417,10 +1417,6 @@ void ProfilingRendererSettingsSetting::SetLocal(ClientContext &context, const Va unordered_map new_settings; for (auto &entry : MapValue::GetChildren(input)) { auto &key_value = StructValue::GetChildren(entry); - if (key_value[1].IsNull()) { - throw InvalidInputException("profiling_renderer_settings: the value of \"%s\" cannot be NULL", - key_value[0].ToString()); - } new_settings[StringUtil::Lower(key_value[0].ToString())] = key_value[1]; } // eagerly configure the renderer of the current profiler format to validate the setting values @@ -1979,8 +1975,7 @@ void ActiveGrammarExtensionsSetting::SetLocal(ClientContext &context, const Valu return; } - auto compiled_grammar = - DatabaseInstance::GetDatabase(context).GetParserCache().GetMatcher(context, selected_extensions); + auto compiled_grammar = CompiledGrammar::Create(context, selected_extensions); client_config.active_grammar_extensions = std::move(selected_extensions); client_config.cached_grammar = std::move(compiled_grammar); } diff --git a/src/duckdb/src/main/statement_iterator.cpp b/src/duckdb/src/main/statement_iterator.cpp index 5a49b4d2c..cf66543a4 100644 --- a/src/duckdb/src/main/statement_iterator.cpp +++ b/src/duckdb/src/main/statement_iterator.cpp @@ -26,14 +26,12 @@ bool StatementIterator::Peek() { if (pending_statement) { return true; } - parser_start_time = Timestamp::GetCurrentTimestamp(); parser_timer.Start(); if (!source.Peek()) { parser_timer.Reset(); return false; } parser_timer.End(); - parser_end_time = Timestamp::GetCurrentTimestamp(); pending_statement = source.GetStatement(); if (!pending_statement) { parser_timer.Reset(); @@ -57,7 +55,7 @@ unique_ptr StatementIterator::GetStatementInternal(bool profile_st if (profile_statement) { auto &profiler = QueryProfiler::Get(context); profiler.StartQuery(statement->query, StatementIteratorIsExplainAnalyze(statement.get())); - profiler.AddParserTime(parser_timer, parser_start_time, parser_end_time); + profiler.AddParserTime(parser_timer); } parser_timer.Reset(); return statement; diff --git a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp index b8b82fe8f..19a1871ce 100644 --- a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp +++ b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp @@ -19,54 +19,6 @@ namespace duckdb { //===--------------------------------------------------------------------===// // Subplan Signature/Info //===--------------------------------------------------------------------===// -static vector GetExpandedColumnBindings(LogicalOperator &op) { - switch (op.type) { - case LogicalOperatorType::LOGICAL_FILTER: - case LogicalOperatorType::LOGICAL_ORDER_BY: - case LogicalOperatorType::LOGICAL_LIMIT: - case LogicalOperatorType::LOGICAL_TOP_N: - case LogicalOperatorType::LOGICAL_DISTINCT: - return GetExpandedColumnBindings(*op.children[0]); - case LogicalOperatorType::LOGICAL_WINDOW: - case LogicalOperatorType::LOGICAL_UNNEST: { - auto bindings = GetExpandedColumnBindings(*op.children[0]); - const auto table_index = op.GetTableIndex()[0]; - for (auto col_idx : ProjectionIndex::GetIndexes(op.expressions.size())) { - bindings.emplace_back(table_index, col_idx); - } - return bindings; - } - case LogicalOperatorType::LOGICAL_ANY_JOIN: - case LogicalOperatorType::LOGICAL_ASOF_JOIN: - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { - auto &join = op.Cast(); - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { - return left_bindings; - } - if (join.join_type == JoinType::MARK) { - left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); - return left_bindings; - } - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { - return right_bindings; - } - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: - case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - default: - return op.GetColumnBindings(); - } -} - enum class ConversionType { TO_CANONICAL, RESTORE_ORIGINAL, @@ -148,7 +100,7 @@ class PlanSignatureTableIndexMap { // Store temporary mapping for (const auto &child_op : op.children) { - for (const auto &child_cb : GetExpandedColumnBindings(*child_op)) { + for (const auto &child_cb : child_op->GetColumnBindings()) { const auto &original = child_cb.table_index; auto it = to_canonical_table_index.find(original); if (it != to_canonical_table_index.end()) { @@ -1156,6 +1108,42 @@ class CommonSubplanFinder { return a.get(); } + vector GetExpandedColumnBindings(LogicalOperator &op) { + switch (op.type) { + case LogicalOperatorType::LOGICAL_FILTER: + case LogicalOperatorType::LOGICAL_ORDER_BY: + return GetExpandedColumnBindings(*op.children[0]); + case LogicalOperatorType::LOGICAL_ANY_JOIN: + case LogicalOperatorType::LOGICAL_ASOF_JOIN: + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { + auto &join = op.Cast(); + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { + return left_bindings; + } + if (join.join_type == JoinType::MARK) { + left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); + return left_bindings; + } + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { + return right_bindings; + } + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: + case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + default: + return op.GetColumnBindings(); + } + } + arena_vector GetCanonicalBindings(const vector &original_bindings) { const auto &table_index_map = state.table_index_map.GetMap(); arena_vector canonical_bindings(state.allocator); diff --git a/src/duckdb/src/optimizer/filter_combiner.cpp b/src/duckdb/src/optimizer/filter_combiner.cpp index 3477a0082..696bad84d 100644 --- a/src/duckdb/src/optimizer/filter_combiner.cpp +++ b/src/duckdb/src/optimizer/filter_combiner.cpp @@ -799,19 +799,7 @@ static bool IsLessThan(ExpressionType type) { //! Margins: 2h for DST ambiguity (TIMESTAMP<->TIMESTAMPTZ), 24h for time-of-day loss, 1s for rounding loss. static int64_t GetTemporalCastMargin(LogicalTypeId source, LogicalTypeId target) { if (source == target) { - switch (source) { - case LogicalTypeId::DATE: - case LogicalTypeId::TIMESTAMP: - case LogicalTypeId::TIMESTAMP_TZ: - case LogicalTypeId::TIMESTAMP_NS: - case LogicalTypeId::TIMESTAMP_TZ_NS: - case LogicalTypeId::TIMESTAMP_MS: - case LogicalTypeId::TIMESTAMP_SEC: - return 0; - default: - // non-temporal types with the same id can differ (e.g. UNION or DECIMAL types) - return -1; - } + return 0; } switch (source) { case LogicalTypeId::TIMESTAMP: @@ -1147,6 +1135,7 @@ FilterResult FilterCombiner::AddBoundComparisonFilter(Expression &expr) { return ret; } else { // comparison between two non-scalars + // only handle comparisons for now if (expr.GetExpressionType() != ExpressionType::COMPARE_EQUAL) { return FilterResult::UNSUPPORTED; } @@ -1301,18 +1290,47 @@ FilterResult FilterCombiner::AddFilter(Expression &expr) { return FilterResult::UNSUPPORTED; } -// Infer constant bounds from comparisons between non-scalar expressions. +/* + * Create and add new transitive filters from a two non-scalar filter such as j > i, j >= i, j < i, and j <= i + * It's missing to create another method to add transitive filters from scalar filters, e.g, i > 10 + */ FilterResult FilterCombiner::AddTransitiveFilters(BoundFunctionExpression &comparison, bool is_root) { if (!IsGreaterThan(comparison.GetExpressionType()) && !IsLessThan(comparison.GetExpressionType())) { return FilterResult::UNSUPPORTED; } - // Derived bounds must not duplicate volatile or throwing expressions. - if (comparison.IsVolatile() || comparison.CanThrow()) { - return FilterResult::UNSUPPORTED; - } // get the LHS and RHS nodes auto &left_node = GetNode(*BoundComparisonExpression::LeftMutable(comparison)); - auto &right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); + reference right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); + // In case with filters like CAST(i) = j and i = 5 we replace the COLUMN_REF i with the constant 5 + do { + if (right_node.get().GetExpressionType() != ExpressionType::OPERATOR_CAST) { + break; + } + auto &bound_cast_expr = right_node.get().Cast(); + if (BoundCastExpression::Child(bound_cast_expr).GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { + break; + } + auto &col_ref = BoundCastExpression::Child(bound_cast_expr).Cast(); + for (auto &stored_exp : stored_expressions) { + const_reference expr = stored_exp.first; + if (expr.get().GetExpressionType() == ExpressionType::OPERATOR_CAST) { + expr = BoundCastExpression::Child(right_node.get().Cast()); + } + if (expr.get().GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { + continue; + } + auto &st_col_ref = expr.get().Cast(); + if (st_col_ref.Binding() != col_ref.Binding()) { + continue; + } + if (bound_cast_expr.GetReturnType() != stored_exp.second->GetReturnType()) { + continue; + } + BoundCastExpression::ChildMutable(bound_cast_expr) = stored_exp.second->Copy(); + right_node = GetNode(*BoundCastExpression::ChildMutable(bound_cast_expr)); + break; + } + } while (false); if (left_node.Equals(right_node)) { return FilterResult::UNSUPPORTED; diff --git a/src/duckdb/src/optimizer/filter_pushdown.cpp b/src/duckdb/src/optimizer/filter_pushdown.cpp index d262b32a9..b9484e797 100644 --- a/src/duckdb/src/optimizer/filter_pushdown.cpp +++ b/src/duckdb/src/optimizer/filter_pushdown.cpp @@ -172,34 +172,62 @@ unique_ptr FilterPushdown::PushdownJoin(unique_ptrtype == LogicalOperatorType::LOGICAL_DELIM_JOIN); auto &join = op->Cast(); + const auto restore_projection_maps = join.HasProjectionMap(); + auto left_projection_map = join.left_projection_map; + auto right_projection_map = join.right_projection_map; + unordered_set left_bindings, right_bindings; LogicalJoin::GetTableReferences(*op->children[0], left_bindings); LogicalJoin::GetTableReferences(*op->children[1], right_bindings); - // every branch keeps the join's projection maps on the operator it returns: the join itself when it survives, - // the re-created join in PushdownInnerJoin, or a filter carrying the map when a side replaces the join + unique_ptr result; switch (join.join_type) { case JoinType::OUTER: - return PushdownOuterJoin(std::move(op), left_bindings, right_bindings); + result = PushdownOuterJoin(std::move(op), left_bindings, right_bindings); + break; case JoinType::INNER: // AsOf joins can't push anything into the RHS, so treat it as a left join if (op->type == LogicalOperatorType::LOGICAL_ASOF_JOIN) { - return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); + result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); + break; } - return PushdownInnerJoin(std::move(op), left_bindings, right_bindings); + result = PushdownInnerJoin(std::move(op), left_bindings, right_bindings); + break; case JoinType::LEFT: - return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); + result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); + break; case JoinType::MARK: - return PushdownMarkJoin(std::move(op), left_bindings, right_bindings); + result = PushdownMarkJoin(std::move(op), left_bindings, right_bindings); + break; case JoinType::SINGLE: - return PushdownSingleJoin(std::move(op), left_bindings, right_bindings); + result = PushdownSingleJoin(std::move(op), left_bindings, right_bindings); + break; case JoinType::SEMI: case JoinType::ANTI: - return PushdownSemiAntiJoin(std::move(op)); + result = PushdownSemiAntiJoin(std::move(op)); + break; default: // unsupported join type: stop pushing down return FinishPushdown(std::move(op)); } + + if (restore_projection_maps) { + switch (result->type) { + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: + case LogicalOperatorType::LOGICAL_ASOF_JOIN: + case LogicalOperatorType::LOGICAL_ANY_JOIN: + case LogicalOperatorType::LOGICAL_DELIM_JOIN: { + // Pushing down filter through join didn't change operator type (e.g., LogicalEmptyResult), restore maps + auto &result_join = result->Cast(); + result_join.left_projection_map = std::move(left_projection_map); + result_join.right_projection_map = std::move(right_projection_map); + break; + } + default: + break; + } + } + return result; } FilterResult FilterPushdown::PushFilters() { for (auto &f : filters) { diff --git a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp index 51f2247d7..2549eac84 100644 --- a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp +++ b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp @@ -50,9 +50,6 @@ struct GroupingSetLevel { } // namespace -//! The maximum number of grouping sets that are rewritten into separate aggregates -static constexpr idx_t MAX_REWRITE_GROUPING_SETS = 1024; - static bool CanRewriteAggregate(const BoundAggregateExpression &aggregate) { if (aggregate.IsDistinct() || aggregate.GetOrderBys()) { // DISTINCT / ORDER BY aggregates cannot be computed by combining states of a finer aggregation @@ -252,10 +249,6 @@ bool GroupingSetsOptimizer::TryRewriteGroupingSets(unique_ptr & can_cascade = false; } } - if (aggr.grouping_sets.size() > MAX_REWRITE_GROUPING_SETS) { - // the rewritten plan nests a materialized CTE per grouping set - leave large grouping sets to the aggregate - return false; - } vector levels; if (!can_cascade || !FindCascade(aggr.grouping_sets, levels)) { // Explicit branches support arbitrary aggregates and expose single grouping sets to subsequent rewrites. diff --git a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp index 1ed6428e9..98a8f2b33 100644 --- a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp +++ b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp @@ -8,6 +8,7 @@ #include "duckdb/optimizer/join_order/plan_enumerator.hpp" #include "duckdb/planner/expression/list.hpp" #include "duckdb/planner/operator/list.hpp" +#include "duckdb/main/settings.hpp" namespace duckdb { @@ -26,12 +27,14 @@ static optional CombineReorderableStats(const vector source; + for (auto &stats : relation_stats) { + source = stats.GetColumnStats(binding); + if (source) { + break; + } + } if (!source) { return {}; } @@ -56,7 +59,8 @@ JoinOrderOptimizer JoinOrderOptimizer::CreateChildOptimizer() { unique_ptr JoinOrderOptimizer::Optimize(unique_ptr plan, optional_ptr stats) { - if (depth > MAX_RECURSION_DEPTH) { + auto max_expression_depth = Settings::Get(query_graph_manager.context); + if (depth > max_expression_depth) { // Very deep plans will eventually consume quite some stack space // Returning the current plan is always a valid choice return plan; diff --git a/src/duckdb/src/optimizer/join_order/relation_manager.cpp b/src/duckdb/src/optimizer/join_order/relation_manager.cpp index 8b6139cae..f963cb088 100644 --- a/src/duckdb/src/optimizer/join_order/relation_manager.cpp +++ b/src/duckdb/src/optimizer/join_order/relation_manager.cpp @@ -362,12 +362,14 @@ static optional CombineNonReorderableStats(LogicalOperator &op, c auto ordinal_set_output = op.type == LogicalOperatorType::LOGICAL_UNION || op.type == LogicalOperatorType::LOGICAL_INTERSECT || op.type == LogicalOperatorType::LOGICAL_EXCEPT; - RelationColumnStatsLookup lookup; - for (auto &stats : child_stats) { - lookup.AddRelation(stats); - } for (idx_t column_idx = 0; column_idx < bindings.size(); column_idx++) { - auto source = lookup.Find(bindings[column_idx]); + optional_ptr source; + for (auto &stats : child_stats) { + source = stats.GetColumnStats(bindings[column_idx]); + if (source) { + break; + } + } if (!source && ordinal_set_output && !child_stats.empty() && column_idx < child_stats[0].columns.size()) { source = child_stats[0].columns[column_idx]; } diff --git a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp index ff8524f89..00825612b 100644 --- a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp +++ b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp @@ -1,8 +1,6 @@ #include "duckdb/optimizer/multi_stage_aggregate_rewriter.hpp" #include "duckdb/common/string_util.hpp" -#include "duckdb/function/aggregate/distributive_function_utils.hpp" -#include "duckdb/function/function_binder.hpp" #include "duckdb/optimizer/aggregate_rewrite_helper.hpp" #include "duckdb/optimizer/aggregate_rewrite.hpp" #include "duckdb/optimizer/optimizer.hpp" @@ -11,7 +9,6 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" -#include "duckdb/planner/expression_binder.hpp" #include "duckdb/planner/expression_iterator.hpp" #include "duckdb/planner/operator/logical_aggregate.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" @@ -30,15 +27,12 @@ MultiStageAggregateRewriter::MultiStageAggregateRewriter( namespace { struct DistinctAggregateSet { - DistinctAggregateSet(idx_t source_index, vector> keys_p) - : source_index(source_index), keys(std::move(keys_p)) { + explicit DistinctAggregateSet(idx_t source_index) : source_index(source_index) { aggregate_indices.push_back(source_index); } idx_t source_index; vector aggregate_indices; - //! Actual deduplication keys, including collations unless the binder already applied them. - vector> keys; vector> order_expressions; }; @@ -68,24 +62,6 @@ static optional_idx FindExpression(const vector> &express return optional_idx(); } -//! Expression equality ignores the return type, but a collation lives in the return type and decides which -//! argument values are duplicates of each other. Two distinct aggregates can only share a deduplication branch -//! if their arguments agree on that as well. -static bool ArgumentsEqualIncludingCollation(const BoundAggregateExpression &lhs, const BoundAggregateExpression &rhs) { - auto &left = lhs.GetChildren(); - auto &right = rhs.GetChildren(); - if (left.size() != right.size()) { - return false; - } - for (idx_t i = 0; i < left.size(); i++) { - if (!Expression::Equals(*left[i], *right[i]) || - !left[i]->GetReturnType().EqualsIncludingCollation(right[i]->GetReturnType())) { - return false; - } - } - return true; -} - static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateExpression &aggregate) { if (!aggregate.GetOrderBys()) { return; @@ -102,34 +78,33 @@ static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateE static unique_ptr CreateFinalAggregate(const BoundAggregateExpression &source, const DistinctAggregateSet &set, - TableIndex input_table, idx_t order_column_offset, - const vector &value_columns, + TableIndex input_table, idx_t input_column_offset, + idx_t order_column_offset, optional_idx filter_column_offset = optional_idx()) { auto result = unique_ptr_cast(source.Copy()); result->GetAggregateTypeMutable() = AggregateType::NON_DISTINCT; result->GetChildrenMutable().clear(); for (idx_t child_idx = 0; child_idx < source.GetChildren().size(); child_idx++) { auto &child = source.GetChildren()[child_idx]; - auto &value_column = value_columns[child_idx]; - result->GetChildrenMutable().push_back( - make_uniq(child->GetReturnType(), value_column)); + result->GetChildrenMutable().push_back(make_uniq( + child->GetReturnType(), ColumnBinding(input_table, ProjectionIndex(input_column_offset + child_idx)))); } if (source.GetOrderBys()) { result->GetOrderBysMutable() = make_uniq(); for (auto &order : source.GetOrderBys()->orders) { auto order_idx = FindExpression(source.GetChildren(), *order.expression); - ColumnBinding order_binding; + idx_t column_offset; if (order_idx.IsValid()) { - // the ORDER BY is one of the arguments - sort on the value of that argument - order_binding = value_columns[order_idx.GetIndex()]; + column_offset = input_column_offset + order_idx.GetIndex(); } else { order_idx = FindExpression(set.order_expressions, *order.expression); D_ASSERT(order_idx.IsValid()); - order_binding = ColumnBinding(input_table, ProjectionIndex(order_column_offset + order_idx.GetIndex())); + column_offset = order_column_offset + order_idx.GetIndex(); } result->GetOrderBysMutable()->orders.emplace_back( order.type, order.null_order, - make_uniq(order.expression->GetReturnType(), order_binding)); + make_uniq(order.expression->GetReturnType(), + ColumnBinding(input_table, ProjectionIndex(column_offset)))); } } else { result->GetOrderBysMutable().reset(); @@ -178,30 +153,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate for (auto &group : aggr.groups) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*group, input_replacements)); } - auto distinct_group_index = optimizer.binder.GenerateTableIndex(); - auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); - - // Group on comparison keys, but pass original values to the final aggregate. When these differ, - // FIRST carries a representative, just as for collated GROUP BY. - vector> distinct_aggregates; - vector value_columns; - value_columns.reserve(source_aggregate.GetChildren().size()); - FunctionBinder function_binder(optimizer.GetContext()); - for (idx_t child_idx = 0; child_idx < source_aggregate.GetChildren().size(); child_idx++) { - auto &child = source_aggregate.GetChildren()[child_idx]; - auto &key = set.keys[child_idx]; - auto value_binding = ColumnBinding(distinct_group_index, ProjectionIndex(distinct_groups.size())); - if (!Expression::Equals(*child, *key)) { - vector> first_children; - first_children.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); - auto first_aggregate = - function_binder.BindAggregateFunction(FirstFunctionGetter::GetFunction(child->GetReturnType()), - std::move(first_children), nullptr, AggregateType::NON_DISTINCT); - value_binding = ColumnBinding(distinct_aggregate_index, ProjectionIndex(distinct_aggregates.size())); - distinct_aggregates.push_back(std::move(first_aggregate)); - } - value_columns.push_back(value_binding); - distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*key, input_replacements)); + for (auto &child : source_aggregate.GetChildren()) { + distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); } for (auto &order_expr : set.order_expressions) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*order_expr, input_replacements)); @@ -214,8 +167,10 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate AggregateRewriteHelper::CopyAndRebind(*source_aggregate.GetFilter(), input_replacements)); } + auto distinct_group_index = optimizer.binder.GenerateTableIndex(); + auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); auto distinct = - make_uniq(distinct_group_index, distinct_aggregate_index, std::move(distinct_aggregates)); + make_uniq(distinct_group_index, distinct_aggregate_index, vector>()); distinct->groups = std::move(distinct_groups); distinct->children.push_back(std::move(input)); @@ -231,8 +186,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate const auto order_column_offset = group_count + source_aggregate.GetChildren().size(); for (auto aggregate_idx : set.aggregate_indices) { auto &aggregate = aggr.expressions[aggregate_idx]->Cast(); - final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, order_column_offset, - value_columns, filter_column_offset)); + final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, group_count, + order_column_offset, filter_column_offset)); } auto final_group_index = optimizer.binder.GenerateTableIndex(); @@ -733,20 +688,10 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { regular_aggregates.push_back(aggregate_idx); continue; } - // Resolve keys before sharing branches: COUNT may already consume keys from the binder, while a - // value-returning aggregate with the same argument still needs its collation applied. - vector> keys; - for (auto &child : aggregate.GetChildren()) { - auto key = child->Copy(); - if (aggregate.Function().GetCollationHandling() != FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { - ExpressionBinder::PushCollation(optimizer.GetContext(), key, key->GetReturnType()); - } - keys.push_back(std::move(key)); - } bool found_match = false; for (auto &set : distinct_sets) { auto &other = aggr.expressions[set.source_index]->Cast(); - if (ArgumentsEqualIncludingCollation(aggregate, other) && Expression::ListEquals(keys, set.keys) && + if (Expression::ListEquals(aggregate.GetChildren(), other.GetChildren()) && Expression::Equals(aggregate.GetFilter(), other.GetFilter())) { set.aggregate_indices.push_back(aggregate_idx); AddOrderExpressions(set, aggregate); @@ -755,7 +700,7 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { } } if (!found_match) { - distinct_sets.emplace_back(aggregate_idx, std::move(keys)); + distinct_sets.emplace_back(aggregate_idx); AddOrderExpressions(distinct_sets.back(), aggregate); } } diff --git a/src/duckdb/src/optimizer/optimizer.cpp b/src/duckdb/src/optimizer/optimizer.cpp index e9ef3cb82..a068c5fba 100644 --- a/src/duckdb/src/optimizer/optimizer.cpp +++ b/src/duckdb/src/optimizer/optimizer.cpp @@ -130,11 +130,13 @@ bool Optimizer::OptimizerDisabled(ClientContext &context_p, OptimizerType type) return true; } auto &config = DBConfig::GetConfig(context_p); - return config.IsOptimizerDisabled(type); + return config.options.disabled_optimizers.find(type) != config.options.disabled_optimizers.end(); } void Optimizer::RunOptimizer(OptimizerType type, const std::function &callback) { - context.InterruptCheck(); + if (context.IsInterrupted()) { + throw InterruptException(); + } if (OptimizerDisabled(type)) { // optimizer is marked as disabled: skip diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp index 84893ecf6..b8a626121 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp @@ -1,60 +1,13 @@ #include "duckdb/optimizer/filter_pushdown.hpp" #include "duckdb/optimizer/in_clause_rewriter.hpp" -#include "duckdb/planner/expression/bound_between_expression.hpp" -#include "duckdb/planner/expression/bound_columnref_expression.hpp" -#include "duckdb/planner/expression/bound_comparison_expression.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" #include "duckdb/planner/operator/logical_cross_product.hpp" #include "duckdb/planner/operator/logical_filter.hpp" -#include "duckdb/planner/operator/logical_empty_result.hpp" namespace duckdb { using Filter = FilterPushdown::Filter; -static bool HasTransitiveJoinBounds(const vector> &filters) { - unordered_set bound_tables; - for (auto &filter : filters) { - if (filter->bindings.size() != 1) { - continue; - } - auto &expr = *filter->filter; - bool has_bound = false; - if (BoundComparisonExpression::IsComparison(expr)) { - auto &comparison = expr.Cast(); - has_bound = BoundComparisonExpression::Left(comparison).IsFoldable() || - BoundComparisonExpression::Right(comparison).IsFoldable(); - } else if (expr.GetExpressionType() == ExpressionType::COMPARE_BETWEEN) { - auto &between = expr.Cast(); - has_bound = BoundBetweenExpression::LowerBound(between).IsFoldable() || - BoundBetweenExpression::UpperBound(between).IsFoldable(); - } - if (has_bound) { - bound_tables.insert(*filter->bindings.begin()); - } - } - if (bound_tables.empty()) { - return false; - } - for (auto &filter : filters) { - if (filter->bindings.size() < 2 || !BoundComparisonExpression::IsComparison(*filter->filter)) { - continue; - } - auto &comparison = filter->filter->Cast(); - auto type = comparison.GetExpressionType(); - if (type != ExpressionType::COMPARE_GREATERTHAN && type != ExpressionType::COMPARE_GREATERTHANOREQUALTO && - type != ExpressionType::COMPARE_LESSTHAN && type != ExpressionType::COMPARE_LESSTHANOREQUALTO) { - continue; - } - auto &right = BoundComparisonExpression::Right(comparison); - if (right.GetExpressionType() == ExpressionType::BOUND_COLUMN_REF && - bound_tables.count(right.Cast().Binding().table_index)) { - return true; - } - } - return false; -} - unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr op) { D_ASSERT(op->children.size() > 1); FilterPushdown left_pushdown(optimizer, convert_mark_joins, projection_mode); @@ -70,13 +23,6 @@ unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr left_bindings, right_bindings; if (!filters.empty()) { - // Recombine useful join bounds before distributing filters to the inputs. - if (HasTransitiveJoinBounds(filters)) { - if (PushFilters() == FilterResult::UNSATISFIABLE) { - return make_uniq(std::move(op)); - } - GenerateFilters(); - } // check to see into which side we should push the filters // first get the LHS and RHS bindings LogicalJoin::GetTableReferences(*op->children[0], left_bindings); diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp index 045497783..f0acab67e 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp @@ -49,21 +49,7 @@ unique_ptr FilterPushdown::PushdownInnerJoin(unique_ptrtype) { - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: - case LogicalOperatorType::LOGICAL_ANY_JOIN: { - auto &result_join = result->Cast(); - result_join.left_projection_map = std::move(join.left_projection_map); - result_join.right_projection_map = std::move(join.right_projection_map); - break; - } - default: - break; - } - return result; + return PushdownCrossProduct(std::move(cross_product)); } } // namespace duckdb diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp index 4c375253f..bbe7685d2 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp @@ -157,30 +157,18 @@ unique_ptr FilterPushdown::PushdownProjection(unique_ptr> remain_expressions; vector> split_expressions; - auto stays_on_top = [&](const Filter &f) { - return IsVolatile(proj, *f.filter) || (f.filter->CanThrow() && !f.has_barrier); - }; - auto splits_off = [&](const Filter &f) { - return projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && - ReferencesComputedExpression(proj, *f.filter); - }; - // barred expressions may only cross the projection if every other filter does: a filter kept on top of the - // projection, or split off below it, would otherwise be evaluated after them - bool hold_back_barriers = false; - for (auto &filter : filters) { - if (!filter->has_barrier && (stays_on_top(*filter) || splits_off(*filter))) { - hold_back_barriers = true; - } - } for (auto &filter : filters) { auto &f = *filter; D_ASSERT(f.bindings.size() <= 1); - if (stays_on_top(f) || (f.has_barrier && hold_back_barriers)) { + bool is_volatile = IsVolatile(proj, *f.filter); + bool preserve_computed_expression = projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && + ReferencesComputedExpression(proj, *f.filter); + if (is_volatile || (f.filter->CanThrow() && !f.has_barrier)) { // Volatile and throwing expressions cannot move across the projection. Expressions carrying a barrier // are the exception: a projection does not remove any rows, so pushing them through it does not - // change which rows they are evaluated on - unless another filter stays behind, see above. + // change which rows they are evaluated on. remain_expressions.push_back(std::move(f.filter)); - } else if (splits_off(f)) { + } else if (preserve_computed_expression) { // Compute filter-dependent projection expressions once, below the filter. split_expressions.push_back(std::move(f.filter)); } else { diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp index 21009431e..9d4ba02b7 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp @@ -39,8 +39,8 @@ unique_ptr FilterPushdown::PushdownSemiAntiJoin(unique_ptrchildren[0]), std::move(join.left_projection_map)); + // just return the left child. + return std::move(op->children[0]); case JoinType::SEMI: return make_uniq(std::move(op)); default: diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp index fbc8738a7..7426e56e5 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp @@ -42,24 +42,6 @@ bool RelationStats::MatchesBindings(const vector &bindings) const return true; } -RelationColumnStatsLookup::RelationColumnStatsLookup(const RelationStats &stats) { - AddRelation(stats); -} - -void RelationColumnStatsLookup::AddRelation(const RelationStats &stats) { - for (auto &column : stats.columns) { - columns.emplace(column.binding, column); - } -} - -optional_ptr RelationColumnStatsLookup::Find(ColumnBinding binding) const { - auto entry = columns.find(binding); - if (entry == columns.end()) { - return nullptr; - } - return entry->second.get(); -} - void RelationStats::Verify(const vector &bindings) const { D_ASSERT(!stats_initialized || MatchesBindings(bindings)); } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp index b4a983207..f32bab0e8 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp @@ -71,7 +71,6 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical result.stats_initialized = true; auto bindings = projection.GetColumnBindings(); D_ASSERT(bindings.size() == projection.expressions.size()); - RelationColumnStatsLookup child_columns(child_stats); for (idx_t expression_idx = 0; expression_idx < projection.expressions.size(); expression_idx++) { auto &expression = *projection.expressions[expression_idx]; auto expression_binding = GetChildColumnBinding(expression); @@ -79,7 +78,7 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical if (expression_binding.expression_is_constant) { distinct_count = DistinctCount(MinValue(result.cardinality, 1), DistinctCountSource::EXACT); } else if (expression_binding.FoundColumnRef()) { - auto child_column = child_columns.Find(expression_binding.child_binding); + auto child_column = child_stats.GetColumnStats(expression_binding.child_binding); if (!child_column) { return {}; } @@ -109,7 +108,6 @@ idx_t RelationStatisticsHelper::EstimateDistinctCardinality(const vector RelationStatisticsHelper::ExtractAggregationStats(LogicalAggregate &aggregate, const RelationStats &child_stats) { - RelationColumnStatsLookup child_columns(child_stats); vector cardinality_counts; for (auto &grouping_set : aggregate.grouping_sets) { vector set_counts; @@ -118,7 +116,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica if (group.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) { continue; } - auto column = child_columns.Find(group.Cast().Binding()); + auto column = child_stats.GetColumnStats(group.Cast().Binding()); if (!column) { return {}; } @@ -141,7 +139,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica auto &group = *aggregate.groups[group_idx]; DistinctCount distinct_count(result.cardinality, DistinctCountSource::CARDINALITY); if (group.GetExpressionClass() == ExpressionClass::BOUND_COLUMN_REF) { - auto child_column = child_columns.Find(group.Cast().Binding()); + auto child_column = child_stats.GetColumnStats(group.Cast().Binding()); if (!child_column) { return {}; } @@ -170,9 +168,8 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind result.cardinality = child_stats.cardinality; result.table_name = Identifier(window.GetName()); result.stats_initialized = true; - RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : window.GetColumnBindings()) { - auto child_column = child_columns.Find(binding); + auto child_column = child_stats.GetColumnStats(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == window.window_index) { @@ -186,11 +183,10 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind return result; } -static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats, - const RelationColumnStatsLookup &child_columns) { +static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats) { switch (target.GetExpressionClass()) { case ExpressionClass::BOUND_COLUMN_REF: { - auto column = child_columns.Find(target.Cast().Binding()); + auto column = child_stats.GetColumnStats(target.Cast().Binding()); return column ? optional(column->distinct_count) : optional(); } case ExpressionClass::BOUND_REF: { @@ -217,9 +213,8 @@ optional RelationStatisticsHelper::ExtractDistinctStats(LogicalDi distinct_counts.push_back(column.distinct_count); } } else { - RelationColumnStatsLookup child_columns(child_stats); for (auto &target : distinct.distinct_targets) { - auto count = GetDistinctTargetCount(*target, child_stats, child_columns); + auto count = GetDistinctTargetCount(*target, child_stats); if (!count) { return result; } @@ -255,16 +250,14 @@ optional RelationStatisticsHelper::ProjectOutputStats(const Relat result.filter_strength = stats.filter_strength; result.stats_initialized = true; result.table_name = stats.table_name; - RelationColumnStatsLookup lookup(stats); - auto bindings = op.GetColumnBindings(); - for (auto &binding : bindings) { - auto column = lookup.Find(binding); + for (auto &binding : op.GetColumnBindings()) { + auto column = stats.GetColumnStats(binding); if (!column) { return {}; } result.columns.push_back(*column); } - result.Verify(bindings); + result.Verify(op.GetColumnBindings()); return result; } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp index 4ac81b70b..f1ba72352 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp @@ -11,12 +11,14 @@ ProjectChildStats(LogicalOperator &op, const vector source; + for (auto &child : children) { + source = child.get().GetColumnStats(binding); + if (source) { + break; + } + } if (!source) { return {}; } @@ -45,12 +47,11 @@ static optional ExtractGetWithChildStats(LogicalGet &get, ClientC const RelationStats &child_stats) { auto result = RelationStatisticsHelper::ExtractGetStats(get, context); result.cardinality = child_stats.cardinality; - RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : get.GetColumnBindings()) { if (binding.table_index == get.table_index) { continue; } - auto child_column = child_columns.Find(binding); + auto child_column = child_stats.GetColumnStats(binding); if (!child_column) { return {}; } @@ -66,9 +67,8 @@ static optional ExtractUnnestStats(LogicalOperator &op, const Rel result.cardinality = child_stats.cardinality; result.stats_initialized = true; result.table_name = Identifier(op.GetName()); - RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : op.GetColumnBindings()) { - auto child_column = child_columns.Find(binding); + auto child_column = child_stats.GetColumnStats(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == unnest.unnest_index) { @@ -97,9 +97,8 @@ static optional ExtractComparisonJoinStats(LogicalComparisonJoin mark_result.cardinality = cardinality; mark_result.stats_initialized = true; mark_result.table_name = Identifier(join.GetName()); - RelationColumnStatsLookup left_columns(child_stats[0].get()); for (auto &binding : join.GetColumnBindings()) { - auto column = left_columns.Find(binding); + auto column = child_stats[0].get().GetColumnStats(binding); if (column) { mark_result.columns.emplace_back(binding, column->distinct_count, column->name); } else if (binding.table_index == join.mark_index) { diff --git a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp index ef7363cf5..c0ab697ba 100644 --- a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp +++ b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp @@ -246,9 +246,9 @@ void RemoveDuplicateGroups::VisitAggregate(LogicalAggregate &aggr) { return; } - // Earlier statistics (e.g. from the filter simplification) no longer match the groups - the statistics - // propagation that runs after this optimizer computes them again - aggr.group_stats.clear(); + // This optimizer should run before statistics propagation, so this should be empty + // If it runs after, then group_stats should be updated too + D_ASSERT(aggr.group_stats.empty()); const idx_t num_original_groups = aggr.groups.size(); const idx_t num_aggregate_outputs = aggr.expressions.size(); diff --git a/src/duckdb/src/optimizer/remove_unused_columns.cpp b/src/duckdb/src/optimizer/remove_unused_columns.cpp index 35e31126c..8c9e597c5 100644 --- a/src/duckdb/src/optimizer/remove_unused_columns.cpp +++ b/src/duckdb/src/optimizer/remove_unused_columns.cpp @@ -230,11 +230,10 @@ idx_t BaseColumnPruner::ReplaceBinding(ColumnBinding current_binding, ColumnBind template void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex table_idx, bool replace) { D_ASSERT(mode == RemoveUnusedColumnsMode::APPLY); + idx_t offset = 0; idx_t new_col_idx = 0; - // entries that are referred to are moved to the front of the list, the rest is erased at the end - idx_t kept_count = 0; for (idx_t col_idx = 0; col_idx < list.size(); col_idx++) { - auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx)); + auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx + offset)); if (replace && !root.active_secure_views.empty()) { root.secure_view_replacements[current_binding].clear(); } @@ -244,17 +243,16 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab root.projection_map_replacements[current_binding].clear(); } // this entry is not referred to, erase it from the set of expressions + list.erase_at(col_idx); + offset++; + col_idx--; continue; } - if (kept_count != col_idx) { - list[kept_count] = std::move(list[col_idx]); - } - kept_count++; if (!replace) { continue; } bool should_replace = false; - if (col_idx != new_col_idx) { + if (col_idx + offset != new_col_idx) { should_replace = true; } if (!entry->second.child_columns.empty() && @@ -281,7 +279,6 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab new_col_idx++; } } - list.erase(list.begin() + NumericCast(kept_count), list.end()); } enum class ProjectionBindingResolution : uint8_t { SELECTED, REPLACEMENT }; @@ -1841,20 +1838,9 @@ static bool TryGetCastChild(unique_ptr &expr, optional_ptr *expression) { - if ((*expression)->GetExpressionType() == ExpressionType::OPERATOR_TRY) { - const auto was_inside_try = inside_try; - inside_try = true; - LogicalOperatorVisitor::VisitExpression(expression); - inside_try = was_inside_try; - return; - } - - //! Check if this is a struct extract wrapped in a cast. TRY_CAST is already - //! skipped by TryGetCastChild. A throwing CAST under TRY must also stay put: - //! unused-columns would otherwise emit CAST(struct_extract) in a child - //! projection and leave TRY wrapping only the already-cast column (#25236). + //! Check if this is a struct extract wrapped in a cast optional_ptr> cast_child; - if (!inside_try && TryGetCastChild(*expression, cast_child)) { + if (TryGetCastChild(*expression, cast_child)) { if (HandleExtractExpression(cast_child.get(), expression)) { // already handled return; diff --git a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp index 53de7616b..ef00ba652 100644 --- a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp @@ -121,12 +121,6 @@ static bool ConstantCastIsInvertible(ClientContext &context, BoundFunctionExpres auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); return roundtrip && *roundtrip == constant_value; } - if (target_type.id() == LogicalTypeId::DECIMAL && cast_expression.GetReturnType().id() == LogicalTypeId::DECIMAL) { - // a narrowing decimal cast of the constant is fine if the constant round-trips exactly - string error_message; - auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); - return roundtrip && *roundtrip == constant_value; - } if (target_type.id() != LogicalTypeId::DATE || cast_expression.GetReturnType().id() != LogicalTypeId::TIMESTAMP) { return false; } diff --git a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp index 787a721ba..87f0b2fc3 100644 --- a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp @@ -13,7 +13,6 @@ #include "duckdb/optimizer/matcher/expression_matcher.hpp" #include "duckdb/optimizer/expression_rewriter.hpp" #include "duckdb/common/enums/date_part_specifier.hpp" -#include "duckdb/common/types/date.hpp" #include "duckdb/function/function.hpp" #include "duckdb/function/function_binder.hpp" @@ -38,27 +37,6 @@ DateTruncSimplificationRule::DateTruncSimplificationRule(ExpressionRewriter &rew root = std::move(op); } -static bool TruncatesYearsTowardsZero(const BoundConstantExpression &date_part) { - DatePartSpecifier part; - if (!TryGetDatePartSpecifier(StringValue::Get(date_part.GetValue()), part)) { - return true; - } - return part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || - part == DatePartSpecifier::MILLENNIUM; -} - -static bool IsAfterFirstYear(const Value &value) { - if (value.IsNull()) { - return true; - } - auto date_value = value.DefaultTryCastAs(LogicalType::DATE); - if (!date_value || date_value->IsNull()) { - return false; - } - // leave a margin for time zones - return Date::ExtractYear(date_value->GetValue()) > 1; -} - unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, vector> &bindings, bool &changes_made, bool is_root) { auto &expr = bindings[0].get().Cast(); @@ -95,12 +73,6 @@ unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, v return nullptr; } - // Truncating to decades, centuries or millennia does not round years before the common era towards the past - // (e.g. 150 BC truncates to 100 BC), so the rewrite below is only valid for constants after that - if (TruncatesYearsTowardsZero(date_part) && !IsAfterFirstYear(rhs.GetValue())) { - return nullptr; - } - // Check whether trunc(date_part, constant_rhs) = constant_rhs. const bool is_truncated = DateIsTruncated(date_part, rhs); diff --git a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp index 11825d66e..e8c97f9bc 100644 --- a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp +++ b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp @@ -51,10 +51,8 @@ static bool GetFiniteDomain(const LogicalType &type, int64_t &lo, int64_t &hi, b return true; case LogicalTypeId::TIMESTAMP: case LogicalTypeId::TIMESTAMP_TZ: - // every non-sentinel int64 is a valid, comparable timestamp (make_timestamp accepts all of them), so the - // finite domain is everything except the ±infinity sentinels; that also keeps the never-true sentinels - // (hi+1 / lo-1) of the bisection away from INT64_MIN/MAX. Not all of these values render, though: see - // IsRenderableBound. + // the ±infinity sentinels keep the finite domain bounded away from INT64_MIN/MAX, so the + // never-true sentinels (hi+1 / lo-1) in the bisection can't overflow lo = timestamp_t::ninfinity().value + 1; hi = timestamp_t::infinity().value - 1; has_infinity = true; @@ -147,37 +145,10 @@ static int64_t LastTruePrefix(PRED pred, int64_t lo, int64_t hi, bool &ok) { return l; } -//! Whether a bound with internal representation `bound` can be rendered (Expression::ToString, EXPLAIN). -//! TIMESTAMP values between INT64_MIN and the first midnight of Timestamp::MIN_YEAR are valid and comparable -//! (make_timestamp accepts them) but have no date/time form: Timestamp::Convert overflows on them. -static bool IsRenderableBound(const LogicalType &col_type, int64_t bound) { - switch (col_type.id()) { - case LogicalTypeId::TIMESTAMP: - case LogicalTypeId::TIMESTAMP_TZ: - return bound >= Value::MinimumValue(LogicalType::TIMESTAMP).GetValue().value; - default: - return true; - } -} - -//! Emit `col >= bound` (lower) or `col <= bound` (upper). On types with ±infinity a bound on the edge of the -//! finite domain is written as a strict comparison against the adjacent sentinel instead (`col > -infinity`, -//! `col < infinity`): nothing lies between the two, and the sentinel always renders while the edge of the -//! TIMESTAMP domain does not. Returns nullptr if the bound cannot be rendered - the caller then keeps f(col). -static unique_ptr MakeColBound(bool lower, const BoundColumnRefExpression &col, const LogicalType &col_type, - int64_t bound, int64_t dom_edge, bool has_infinity) { - if (has_infinity && bound == dom_edge) { - auto sentinel = lower ? Value::NegativeInfinity(col_type) : Value::Infinity(col_type); - return BoundComparisonExpression::Create(lower ? ExpressionType::COMPARE_GREATERTHAN - : ExpressionType::COMPARE_LESSTHAN, - col.Copy(), make_uniq(std::move(sentinel))); - } - if (!IsRenderableBound(col_type, bound)) { - return nullptr; - } - return BoundComparisonExpression::Create( - lower ? ExpressionType::COMPARE_GREATERTHANOREQUALTO : ExpressionType::COMPARE_LESSTHANOREQUALTO, col.Copy(), - make_uniq(Value::Numeric(col_type, bound))); +static unique_ptr MakeColComparison(ExpressionType type, const BoundColumnRefExpression &col, + const LogicalType &col_type, int64_t bound) { + return BoundComparisonExpression::Create(type, col.Copy(), + make_uniq(Value::Numeric(col_type, bound))); } unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector> &bindings, @@ -352,16 +323,10 @@ unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector lower; unique_ptr upper; if (need_lower) { - lower = MakeColBound(true, col, col_type, a, dom_lo, has_infinity); - if (!lower) { - return nullptr; - } + lower = MakeColComparison(ExpressionType::COMPARE_GREATERTHANOREQUALTO, col, col_type, a); } if (need_upper) { - upper = MakeColBound(false, col, col_type, b, dom_hi, has_infinity); - if (!upper) { - return nullptr; - } + upper = MakeColComparison(ExpressionType::COMPARE_LESSTHANOREQUALTO, col, col_type, b); } if (lower && upper) { return make_uniq(ExpressionType::CONJUNCTION_AND, std::move(lower), diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp index d937e40e9..8d3eaa6b3 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp @@ -13,6 +13,21 @@ namespace duckdb { +static unique_ptr PromoteJoinSide(unique_ptr child, + vector projection_map) { + if (projection_map.empty()) { + return child; + } + auto filter = make_uniq(); + filter->projection_map = std::move(projection_map); + filter->children.push_back(std::move(child)); + if (filter->children[0]->has_estimated_cardinality) { + filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); + } + filter->ResolveOperatorTypes(); + return std::move(filter); +} + bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: @@ -25,10 +40,10 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< // If the filter is always false or Null, the join output is exactly one of its sides: // promote that side together with its projection map so the output layout stays unchanged. case JoinType::RIGHT_ANTI: - node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); + node_ptr = PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); return true; case JoinType::ANTI: - node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); + node_ptr = PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); return true; case JoinType::LEFT: // anti/left outer join: replace right side with empty node @@ -46,32 +61,13 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< } } -//! Appends the columns of a join side that the join emits to the projection map of its replacement -static void AddProjectedColumns(vector &result, const vector &projection_map, - idx_t column_count, idx_t offset) { - if (projection_map.empty()) { - for (idx_t column_idx = 0; column_idx < column_count; column_idx++) { - result.emplace_back(offset + column_idx); - } - return; - } - for (auto &column_idx : projection_map) { - result.emplace_back(offset + column_idx.GetIndex()); - } -} - bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: case JoinType::SEMI: { - auto &emitted_projection_map = - join.join_type == JoinType::RIGHT_SEMI ? join.right_projection_map : join.left_projection_map; if (join.join_type == JoinType::RIGHT_SEMI) { std::swap(join.children[0], join.children[1]); } - // the cross product also emits the columns of the other side: project them out again - vector projection_map; - AddProjectedColumns(projection_map, emitted_projection_map, join.children[0]->GetColumnBindings().size(), 0); // when the right child has no data, return an empty set // cannot just return the left child because if the right child has no cardinality // then the whole result should be empty. @@ -82,21 +78,14 @@ bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr limit->AddChild(std::move(join.children[1])); auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(limit)); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); + node_ptr = std::move(cross_product); return true; } case JoinType::INNER: { - // inner, replace with cross product - keeping the columns that the join emits - vector projection_map; - if (!join.left_projection_map.empty() || !join.right_projection_map.empty()) { - auto left_count = join.children[0]->GetColumnBindings().size(); - AddProjectedColumns(projection_map, join.left_projection_map, left_count, 0); - AddProjectedColumns(projection_map, join.right_projection_map, join.children[1]->GetColumnBindings().size(), - left_count); - } + // inner, replace with cross product auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(join.children[1])); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); + node_ptr = std::move(cross_product); return true; } default: @@ -239,12 +228,8 @@ void StatisticsPropagator::MultiplyCardinalities(unique_ptr &sta return; } stats->estimated_cardinality = MaxValue(stats->estimated_cardinality, new_stats.estimated_cardinality); - hugeint_t new_max; - if (!Hugeint::TryMultiply(Hugeint::Convert(stats->max_cardinality), Hugeint::Convert(new_stats.max_cardinality), - new_max)) { - stats = nullptr; - return; - } + auto new_max = Hugeint::Multiply(NumericCast(stats->max_cardinality), + NumericCast(new_stats.max_cardinality)); if (new_max < NumericLimits::Maximum()) { int64_t result; if (!Hugeint::TryCast(new_max, result)) { diff --git a/src/duckdb/src/optimizer/topn_window_elimination.cpp b/src/duckdb/src/optimizer/topn_window_elimination.cpp index 586787929..7808bc49e 100644 --- a/src/duckdb/src/optimizer/topn_window_elimination.cpp +++ b/src/duckdb/src/optimizer/topn_window_elimination.cpp @@ -900,10 +900,24 @@ TopNWindowElimination::ExtractOptimizerParameters(const LogicalWindow &window, c params.partition_count = window_expr.Partitions().size(); params.order_type = window_expr.OrderBy()[0].type; - // The statistics of a column binding are those at the top of the plan: a filter above the window (e.g. on the - // result of a decorrelated LATERAL join) can have narrowed them to non-NULL although the input of the window - // contains NULLs. They cannot tell whether the window input can be NULL, so keep the NULL-preserving variants. - params.can_be_null = true; + VisitExpression(&window_expr.OrderByMutable()[0].expression); + if (params.payload_type == TopNPayloadType::SINGLE_COLUMN && !aggregate_payload.empty()) { + VisitExpression(&aggregate_payload[0]); + } + for (const auto &column_ref : column_references) { + const auto &column_stats = stats->find(column_ref.first); + if (column_stats == stats->end() || column_stats->second->CanHaveNull()) { + params.can_be_null = true; + break; + } + } + // A compound ORDER BY expression (NULLIF, arithmetic, cast, ...) can produce NULLs even when its input + // columns are non-null. Only a direct column reference inherits the column's known nullability, so treat a + // non-column-reference as potentially null to keep the nulls-last aggregate variant. + if (window_expr.OrderBy()[0].expression->GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { + params.can_be_null = true; + } + column_references.clear(); return params; } diff --git a/src/duckdb/src/parallel/pipeline_executor.cpp b/src/duckdb/src/parallel/pipeline_executor.cpp index e61574823..3f4cfbc3d 100644 --- a/src/duckdb/src/parallel/pipeline_executor.cpp +++ b/src/duckdb/src/parallel/pipeline_executor.cpp @@ -62,7 +62,7 @@ PipelineExecutor::PipelineExecutor(ClientContext &context_p, Pipeline &pipeline_ auto ¤t_operator = pipeline.operators[i].get(); auto chunk = make_uniq(); - prev_operator.InitializeOutputChunk(BufferAllocator::Get(context.client), *chunk); + chunk->Initialize(BufferAllocator::Get(context.client), prev_operator.GetTypes()); intermediate_chunks.push_back(std::move(chunk)); auto op_state = current_operator.GetOperatorState(context); @@ -896,7 +896,7 @@ void PipelineExecutor::SampleProgress() { void PipelineExecutor::InitializeChunk(DataChunk &chunk) { auto &last_op = pipeline.operators.empty() ? *pipeline.source : pipeline.operators.back().get(); - last_op.InitializeOutputChunk(BufferAllocator::Get(context.client), chunk); + chunk.Initialize(BufferAllocator::Get(context.client), last_op.GetTypes()); } void PipelineExecutor::StartOperator(PhysicalOperator &op) { diff --git a/src/duckdb/src/parallel/scan_read_ahead.cpp b/src/duckdb/src/parallel/scan_read_ahead.cpp index a789974ba..61bd67e25 100644 --- a/src/duckdb/src/parallel/scan_read_ahead.cpp +++ b/src/duckdb/src/parallel/scan_read_ahead.cpp @@ -46,6 +46,12 @@ bool ReadAheadJobCompletion::TryPark(TableFunctionInput &data_p) { return true; } +//! Throw when the query was interrupted, otherwise yield the thread +static void ScanReadAheadYield(ClientContext &context) { + context.InterruptCheck(); + TaskScheduler::YieldThread(); +} + void ReadAheadJobCompletion::WaitForIO() { shared_ptr task; while (pending_io_tasks.load() > 0) { @@ -64,9 +70,8 @@ void ReadAheadJobCompletion::WaitForIO() { class ReadAheadIOTask : public BaseExecutorTask { public: ReadAheadIOTask(TaskExecutor &executor, unique_ptr task_p, - shared_ptr completion_p, ScanReadAhead &read_ahead_p) - : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)), - read_ahead(read_ahead_p) { + shared_ptr completion_p) + : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)) { completion->AddIOTask(); } ~ReadAheadIOTask() override { @@ -75,20 +80,13 @@ class ReadAheadIOTask : public BaseExecutorTask { } void ExecuteTask() override { - try { - // Does the actual IO - task->Execute(); - } catch (std::exception &ex) { - // recording the error through the read-ahead also wakes the threads waiting for progress - read_ahead.PushError(ErrorData(ex)); - } + // Does the actual IO + task->Execute(); } private: unique_ptr task; shared_ptr completion; - //! Outlives the task, the read-ahead drains its executor before it is destroyed - ScanReadAhead &read_ahead; }; //! Async task that opens one file ahead of decoding and releases the pending-open count when destroyed @@ -201,19 +199,13 @@ unique_ptr ScanReadAhead::ClaimJob() { auto job = std::move(ready_queue.front()); ready_queue.pop_front(); ReleaseSlot(); - NotifyProgress(false); return job; } void ScanReadAhead::WaitForJob(ScanReadAheadJob &job) { job.SettleIO(); // the job's I/O has completed, release its budget charge - const auto budget = backlog_budget.load(); - const auto previous = pending_io_bytes.fetch_sub(job.io_bytes); - if (previous >= budget && previous - job.io_bytes < budget) { - // the scan is no longer over budget, a waiting thread can produce - SignalProgress(false); - } + pending_io_bytes -= job.io_bytes; job.io_bytes = 0; ThrowIfError(); } @@ -222,9 +214,7 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc const ProduceJobCallback &claim_and_schedule, unique_ptr &job) { D_ASSERT(!job); - optional yield_deadline; while (true) { - const auto epoch = ProgressEpoch(); // keep the queue full before claiming while (TryProduceJob(claim_and_schedule)) { } @@ -242,18 +232,8 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc } } if (!job) { - // another thread is between claiming an assignment and pushing its job, a short claim is caught by - // yielding and a long one is waited for - const auto now = std::chrono::steady_clock::now(); - if (!yield_deadline) { - yield_deadline = now + std::chrono::microseconds(PRODUCER_YIELD_MICROS); - } - if (now < *yield_deadline) { - context.InterruptCheck(); - TaskScheduler::YieldThread(); - } else { - WaitForProgress(context, epoch); - } + // another thread is between claiming an assignment and pushing its job, wait for it + ScanReadAheadYield(context); continue; } // park until the job's scheduled I/O completes, the last I/O task to finish wakes the scan task @@ -270,22 +250,14 @@ void ScanReadAhead::SetDone() { done = true; } -idx_t ScanReadAhead::ReservationDepth() const { - // over budget one job stays queued, claimed jobs hold the budget until their task resumes - return pending_io_bytes.load() >= backlog_budget.load() ? 1 : read_ahead_depth; -} - bool ScanReadAhead::TryReserveSlot() { // take the minimum job charge up front so concurrent producers cannot all pass the budget check together const bool over_budget = pending_io_bytes.fetch_add(MINIMUM_JOB_IO_CHARGE) >= backlog_budget.load(); + // over budget one job stays queued, claimed jobs hold the budget until their task resumes const idx_t depth = over_budget ? 1 : read_ahead_depth; if (active_jobs.fetch_add(1) >= depth) { --active_jobs; pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; - if (active_jobs.load() < ReservationDepth()) { - // a concurrent attempt failed on this attempt's charge or slot, let it retry - SignalProgress(false); - } return false; } ++active_producers; @@ -300,7 +272,7 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorio_bytes += task->GetIOSize(); - read_tasks.push_back(make_uniq(*executor, std::move(task), completion, *this)); + read_tasks.push_back(make_uniq(*executor, std::move(task), completion)); } job->io_bytes = MaxValue(job->io_bytes, MINIMUM_JOB_IO_CHARGE); // the minimum charge was taken by TryReserveSlot, charge the remainder @@ -312,27 +284,22 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorScheduleTasks(std::move(read_tasks)); lock_guard guard(lock); if (memory_governor) { - // Reserve room for the next job before admission checks the budget. - memory_governor->UpdateReservation(pending_io_bytes.load() + MINIMUM_JOB_IO_CHARGE); + memory_governor->UpdateReservation(pending_io_bytes.load()); backlog_budget = memory_governor->BackpressureBudget(); } // producers push concurrently, so admit jobs to the queue in batch-index order if (job->batch_index < next_batch_index || !pending_jobs.emplace(job->batch_index, std::move(job)).second) { throw InternalException("Read-ahead jobs must have unique batch indexes, assigned densely from 0"); } - idx_t admitted = 0; while (!pending_jobs.empty() && pending_jobs.begin()->first == next_batch_index) { ready_queue.push_back(std::move(pending_jobs.begin()->second)); pending_jobs.erase(pending_jobs.begin()); next_batch_index++; - admitted++; } - NotifyProgress(admitted > 1); } void ScanReadAhead::PushError(ErrorData error) { executor->PushError(std::move(error)); - SignalProgress(true); } void ScanReadAhead::ScheduleFileOpen(std::function open_fn, std::function cancel_fn) { @@ -364,47 +331,4 @@ void ScanReadAhead::ReleaseSlot() { active_jobs--; } -void ScanReadAhead::WakeWaiters(bool wake_all) { - if (wake_all) { - progress.notify_all(); - } else { - progress.notify_one(); - } -} - -void ScanReadAhead::NotifyProgress(bool wake_all) { - ++progress_epoch; - if (waiting_threads.load() > 0) { - WakeWaiters(wake_all); - } -} - -void ScanReadAhead::SignalProgress(bool wake_all) { - ++progress_epoch; - if (waiting_threads.load() == 0) { - return; - } - { - // a waiter that saw the old epoch is blocked in the wait once the lock is free - lock_guard guard(lock); - } - WakeWaiters(wake_all); -} - -void ScanReadAhead::WaitForProgress(ClientContext &context, idx_t epoch) { - context.BlockingInterruptCheck(); - { - unique_lock guard(lock); - ++waiting_threads; - // the timeout bounds the interrupt latency - progress.wait_for(guard, std::chrono::milliseconds(10), [&]() { return progress_epoch.load() != epoch; }); - --waiting_threads; - } - context.BlockingInterruptCheck(); -} - -idx_t ScanReadAhead::ProgressEpoch() const { - return progress_epoch.load(); -} - } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/autocomplete_core.cpp b/src/duckdb/src/parser/peg/autocomplete_core.cpp index a637e7ba1..076aad940 100644 --- a/src/duckdb/src/parser/peg/autocomplete_core.cpp +++ b/src/duckdb/src/parser/peg/autocomplete_core.cpp @@ -229,7 +229,6 @@ vector GenerateAutoCompleteSuggestions(AutoCompleteCatal vector new_suggestions; switch (suggestion.type) { case SuggestionState::SUGGEST_VARIABLE: - case SuggestionState::SUGGEST_BARE_LABEL: // variables have no suggestions available break; case SuggestionState::SUGGEST_KEYWORD: diff --git a/src/duckdb/src/parser/peg/compiled_grammar.cpp b/src/duckdb/src/parser/peg/compiled_grammar.cpp index 3cf3f2878..623048f7e 100644 --- a/src/duckdb/src/parser/peg/compiled_grammar.cpp +++ b/src/duckdb/src/parser/peg/compiled_grammar.cpp @@ -55,8 +55,6 @@ terminal_rule_overrides_t ParsedGrammar::BuildTerminalRuleOverrides(const PEGKey make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); AddTerminalRuleOverride(overrides, "ReservedIdentifier", make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); - AddTerminalRuleOverride(overrides, "BareColLabel", - make_uniq(SuggestionState::SUGGEST_BARE_LABEL, keyword_helper)); AddTerminalRuleOverride(overrides, "CatalogName", make_uniq(SuggestionState::SUGGEST_CATALOG_NAME, keyword_helper)); AddTerminalRuleOverride(overrides, "SchemaName", diff --git a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp index 3517431b4..732eb3d66 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp @@ -8,7 +8,6 @@ static constexpr keyword_categories_t KEYWORD_RESERVED = keyword_categories_t::C static constexpr keyword_categories_t KEYWORD_TYPE_FUNC = keyword_categories_t::CreateCategory(2); static constexpr keyword_categories_t KEYWORD_COL_NAME = keyword_categories_t::CreateCategory(3); static constexpr keyword_categories_t KEYWORD_TYPE_NAME = keyword_categories_t::CreateCategory(4); -static constexpr keyword_categories_t KEYWORD_BARE_LABEL = keyword_categories_t::CreateCategory(5); LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t literal_id) const { keyword_categories_t flags; @@ -27,16 +26,11 @@ LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t liter if (typename_keyword_map.count(text)) { flags |= KEYWORD_TYPE_NAME; } - if (bare_label_keyword_map.count(text)) { - flags |= KEYWORD_BARE_LABEL; - } return LiteralInfo(literal_id, flags); } keyword_categories_t DefaultKeywordMaps::GetIdentifierMask(SuggestionState type) { switch (type) { - case SuggestionState::SUGGEST_BARE_LABEL: - return KEYWORD_BARE_LABEL; case SuggestionState::SUGGEST_TYPE_NAME: return KEYWORD_UNRESERVED | KEYWORD_TYPE_NAME; case SuggestionState::SUGGEST_SCALAR_FUNCTION_NAME: diff --git a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp index f1c75cd79..528d26657 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp @@ -48,32 +48,16 @@ static void PopulateKeywordMap(const ParsedGrammar &grammar, const string &root_ static DefaultKeywordMaps BuildKeywordMaps(const ParsedGrammar &grammar) { DefaultKeywordMaps keyword_maps; - struct KeywordRuleMapping { - const char *rule_name; - reference keyword_map; - bool optional; - }; - // grammars without a bare-label rule only accept non-keywords as bare labels - const KeywordRuleMapping mapping[] = { - {"ReservedKeyword", keyword_maps.reserved_keyword_map, false}, - {"UnreservedKeyword", keyword_maps.unreserved_keyword_map, false}, - {"ColumnNameKeyword", keyword_maps.colname_keyword_map, false}, - {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map, false}, - {"TypeNameKeyword", keyword_maps.typename_keyword_map, false}, - {"BareLabelKeyword", keyword_maps.bare_label_keyword_map, true}, + unordered_map> mapping { + {"ReservedKeyword", keyword_maps.reserved_keyword_map}, + {"UnreservedKeyword", keyword_maps.unreserved_keyword_map}, + {"ColumnNameKeyword", keyword_maps.colname_keyword_map}, + {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map}, + {"TypeNameKeyword", keyword_maps.typename_keyword_map}, }; for (auto &entry : mapping) { - if (entry.optional && !grammar.GetRule(entry.rule_name)) { - continue; - } case_insensitive_set_t active_rules; - PopulateKeywordMap(grammar, entry.rule_name, entry.rule_name, entry.keyword_map.get(), active_rules); - } - // the bare-label classification is orthogonal: every entry must also be an unreserved or column-name keyword - for (auto &keyword : keyword_maps.bare_label_keyword_map) { - if (!keyword_maps.unreserved_keyword_map.count(keyword) && !keyword_maps.colname_keyword_map.count(keyword)) { - throw InvalidInputException("Bare label keyword '%s' is not an unreserved or column-name keyword", keyword); - } + PopulateKeywordMap(grammar, entry.first, entry.first, entry.second.get(), active_rules); } return keyword_maps; } diff --git a/src/duckdb/src/parser/peg/keyword_map.cpp b/src/duckdb/src/parser/peg/keyword_map.cpp index 160b0cd36..f6d8b83f7 100644 --- a/src/duckdb/src/parser/peg/keyword_map.cpp +++ b/src/duckdb/src/parser/peg/keyword_map.cpp @@ -10,7 +10,6 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { auto &colname_keyword_map = keyword_maps.colname_keyword_map; auto &typefunc_keyword_map = keyword_maps.typefunc_keyword_map; auto &typename_keyword_map = keyword_maps.typename_keyword_map; - auto &bare_label_keyword_map = keyword_maps.bare_label_keyword_map; // Populating reserved_keyword_map reserved_keyword_map.insert("all"); @@ -561,366 +560,6 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { typename_keyword_map.insert("try_cast"); typename_keyword_map.insert("unpack"); typename_keyword_map.insert("verbose"); - - // Populating bare_label_keyword_map - bare_label_keyword_map.insert("abort"); - bare_label_keyword_map.insert("absolute"); - bare_label_keyword_map.insert("access"); - bare_label_keyword_map.insert("action"); - bare_label_keyword_map.insert("add"); - bare_label_keyword_map.insert("admin"); - bare_label_keyword_map.insert("after"); - bare_label_keyword_map.insert("aggregate"); - bare_label_keyword_map.insert("also"); - bare_label_keyword_map.insert("alter"); - bare_label_keyword_map.insert("always"); - bare_label_keyword_map.insert("approx"); - bare_label_keyword_map.insert("assertion"); - bare_label_keyword_map.insert("assignment"); - bare_label_keyword_map.insert("attach"); - bare_label_keyword_map.insert("attribute"); - bare_label_keyword_map.insert("backward"); - bare_label_keyword_map.insert("before"); - bare_label_keyword_map.insert("begin"); - bare_label_keyword_map.insert("between"); - bare_label_keyword_map.insert("bigint"); - bare_label_keyword_map.insert("bit"); - bare_label_keyword_map.insert("boolean"); - bare_label_keyword_map.insert("cache"); - bare_label_keyword_map.insert("call"); - bare_label_keyword_map.insert("called"); - bare_label_keyword_map.insert("cascade"); - bare_label_keyword_map.insert("cascaded"); - bare_label_keyword_map.insert("catalog"); - bare_label_keyword_map.insert("chain"); - bare_label_keyword_map.insert("char"); - bare_label_keyword_map.insert("character"); - bare_label_keyword_map.insert("characteristics"); - bare_label_keyword_map.insert("checkpoint"); - bare_label_keyword_map.insert("class"); - bare_label_keyword_map.insert("close"); - bare_label_keyword_map.insert("cluster"); - bare_label_keyword_map.insert("coalesce"); - bare_label_keyword_map.insert("columns"); - bare_label_keyword_map.insert("comment"); - bare_label_keyword_map.insert("comments"); - bare_label_keyword_map.insert("commit"); - bare_label_keyword_map.insert("committed"); - bare_label_keyword_map.insert("compression"); - bare_label_keyword_map.insert("configuration"); - bare_label_keyword_map.insert("conflict"); - bare_label_keyword_map.insert("connect"); - bare_label_keyword_map.insert("connection"); - bare_label_keyword_map.insert("constraints"); - bare_label_keyword_map.insert("content"); - bare_label_keyword_map.insert("continue"); - bare_label_keyword_map.insert("conversion"); - bare_label_keyword_map.insert("copy"); - bare_label_keyword_map.insert("cost"); - bare_label_keyword_map.insert("csv"); - bare_label_keyword_map.insert("cube"); - bare_label_keyword_map.insert("current"); - bare_label_keyword_map.insert("cursor"); - bare_label_keyword_map.insert("cycle"); - bare_label_keyword_map.insert("data"); - bare_label_keyword_map.insert("database"); - bare_label_keyword_map.insert("deallocate"); - bare_label_keyword_map.insert("dec"); - bare_label_keyword_map.insert("decimal"); - bare_label_keyword_map.insert("declare"); - bare_label_keyword_map.insert("defaults"); - bare_label_keyword_map.insert("deferred"); - bare_label_keyword_map.insert("definer"); - bare_label_keyword_map.insert("delete"); - bare_label_keyword_map.insert("delimiter"); - bare_label_keyword_map.insert("delimiters"); - bare_label_keyword_map.insert("depends"); - bare_label_keyword_map.insert("destroy"); - bare_label_keyword_map.insert("detach"); - bare_label_keyword_map.insert("dictionary"); - bare_label_keyword_map.insert("disable"); - bare_label_keyword_map.insert("discard"); - bare_label_keyword_map.insert("disconnect"); - bare_label_keyword_map.insert("distance"); - bare_label_keyword_map.insert("document"); - bare_label_keyword_map.insert("domain"); - bare_label_keyword_map.insert("double"); - bare_label_keyword_map.insert("drop"); - bare_label_keyword_map.insert("each"); - bare_label_keyword_map.insert("enable"); - bare_label_keyword_map.insert("encoding"); - bare_label_keyword_map.insert("encrypted"); - bare_label_keyword_map.insert("enum"); - bare_label_keyword_map.insert("error"); - bare_label_keyword_map.insert("event"); - bare_label_keyword_map.insert("exact"); - bare_label_keyword_map.insert("excluding"); - bare_label_keyword_map.insert("exclusive"); - bare_label_keyword_map.insert("execute"); - bare_label_keyword_map.insert("exists"); - bare_label_keyword_map.insert("explain"); - bare_label_keyword_map.insert("export"); - bare_label_keyword_map.insert("extension"); - bare_label_keyword_map.insert("extensions"); - bare_label_keyword_map.insert("external"); - bare_label_keyword_map.insert("extract"); - bare_label_keyword_map.insert("family"); - bare_label_keyword_map.insert("first"); - bare_label_keyword_map.insert("float"); - bare_label_keyword_map.insert("following"); - bare_label_keyword_map.insert("force"); - bare_label_keyword_map.insert("forward"); - bare_label_keyword_map.insert("function"); - bare_label_keyword_map.insert("functions"); - bare_label_keyword_map.insert("generated"); - bare_label_keyword_map.insert("global"); - bare_label_keyword_map.insert("grant"); - bare_label_keyword_map.insert("granted"); - bare_label_keyword_map.insert("grouping"); - bare_label_keyword_map.insert("grouping_id"); - bare_label_keyword_map.insert("groups"); - bare_label_keyword_map.insert("handler"); - bare_label_keyword_map.insert("header"); - bare_label_keyword_map.insert("hold"); - bare_label_keyword_map.insert("identity"); - bare_label_keyword_map.insert("if"); - bare_label_keyword_map.insert("immediate"); - bare_label_keyword_map.insert("immutable"); - bare_label_keyword_map.insert("implicit"); - bare_label_keyword_map.insert("import"); - bare_label_keyword_map.insert("include"); - bare_label_keyword_map.insert("including"); - bare_label_keyword_map.insert("increment"); - bare_label_keyword_map.insert("index"); - bare_label_keyword_map.insert("indexes"); - bare_label_keyword_map.insert("inherit"); - bare_label_keyword_map.insert("inherits"); - bare_label_keyword_map.insert("inline"); - bare_label_keyword_map.insert("inout"); - bare_label_keyword_map.insert("input"); - bare_label_keyword_map.insert("insensitive"); - bare_label_keyword_map.insert("insert"); - bare_label_keyword_map.insert("install"); - bare_label_keyword_map.insert("instead"); - bare_label_keyword_map.insert("int"); - bare_label_keyword_map.insert("integer"); - bare_label_keyword_map.insert("interval"); - bare_label_keyword_map.insert("invoker"); - bare_label_keyword_map.insert("isolation"); - bare_label_keyword_map.insert("json"); - bare_label_keyword_map.insert("key"); - bare_label_keyword_map.insert("label"); - bare_label_keyword_map.insert("language"); - bare_label_keyword_map.insert("large"); - bare_label_keyword_map.insert("last"); - bare_label_keyword_map.insert("leakproof"); - bare_label_keyword_map.insert("level"); - bare_label_keyword_map.insert("listen"); - bare_label_keyword_map.insert("load"); - bare_label_keyword_map.insert("local"); - bare_label_keyword_map.insert("location"); - bare_label_keyword_map.insert("lock"); - bare_label_keyword_map.insert("locked"); - bare_label_keyword_map.insert("logged"); - bare_label_keyword_map.insert("macro"); - bare_label_keyword_map.insert("map"); - bare_label_keyword_map.insert("mapping"); - bare_label_keyword_map.insert("match"); - bare_label_keyword_map.insert("matched"); - bare_label_keyword_map.insert("materialized"); - bare_label_keyword_map.insert("maxvalue"); - bare_label_keyword_map.insert("merge"); - bare_label_keyword_map.insert("method"); - bare_label_keyword_map.insert("minvalue"); - bare_label_keyword_map.insert("mode"); - bare_label_keyword_map.insert("move"); - bare_label_keyword_map.insert("name"); - bare_label_keyword_map.insert("names"); - bare_label_keyword_map.insert("national"); - bare_label_keyword_map.insert("nchar"); - bare_label_keyword_map.insert("nearest"); - bare_label_keyword_map.insert("new"); - bare_label_keyword_map.insert("next"); - bare_label_keyword_map.insert("no"); - bare_label_keyword_map.insert("none"); - bare_label_keyword_map.insert("nothing"); - bare_label_keyword_map.insert("notify"); - bare_label_keyword_map.insert("nowait"); - bare_label_keyword_map.insert("nullif"); - bare_label_keyword_map.insert("nulls"); - bare_label_keyword_map.insert("numeric"); - bare_label_keyword_map.insert("object"); - bare_label_keyword_map.insert("of"); - bare_label_keyword_map.insert("off"); - bare_label_keyword_map.insert("oids"); - bare_label_keyword_map.insert("old"); - bare_label_keyword_map.insert("operator"); - bare_label_keyword_map.insert("option"); - bare_label_keyword_map.insert("options"); - bare_label_keyword_map.insert("ordinality"); - bare_label_keyword_map.insert("others"); - bare_label_keyword_map.insert("out"); - bare_label_keyword_map.insert("overlay"); - bare_label_keyword_map.insert("overriding"); - bare_label_keyword_map.insert("owned"); - bare_label_keyword_map.insert("owner"); - bare_label_keyword_map.insert("parallel"); - bare_label_keyword_map.insert("parser"); - bare_label_keyword_map.insert("partial"); - bare_label_keyword_map.insert("partition"); - bare_label_keyword_map.insert("partitioned"); - bare_label_keyword_map.insert("passing"); - bare_label_keyword_map.insert("password"); - bare_label_keyword_map.insert("percent"); - bare_label_keyword_map.insert("persistent"); - bare_label_keyword_map.insert("plans"); - bare_label_keyword_map.insert("policy"); - bare_label_keyword_map.insert("position"); - bare_label_keyword_map.insert("pragma"); - bare_label_keyword_map.insert("preceding"); - bare_label_keyword_map.insert("prepare"); - bare_label_keyword_map.insert("prepared"); - bare_label_keyword_map.insert("preserve"); - bare_label_keyword_map.insert("prior"); - bare_label_keyword_map.insert("privileges"); - bare_label_keyword_map.insert("procedural"); - bare_label_keyword_map.insert("procedure"); - bare_label_keyword_map.insert("program"); - bare_label_keyword_map.insert("publication"); - bare_label_keyword_map.insert("quote"); - bare_label_keyword_map.insert("range"); - bare_label_keyword_map.insert("read"); - bare_label_keyword_map.insert("real"); - bare_label_keyword_map.insert("reassign"); - bare_label_keyword_map.insert("recheck"); - bare_label_keyword_map.insert("recursive"); - bare_label_keyword_map.insert("ref"); - bare_label_keyword_map.insert("referencing"); - bare_label_keyword_map.insert("refresh"); - bare_label_keyword_map.insert("register"); - bare_label_keyword_map.insert("reindex"); - bare_label_keyword_map.insert("relative"); - bare_label_keyword_map.insert("release"); - bare_label_keyword_map.insert("repeatable"); - bare_label_keyword_map.insert("replica"); - bare_label_keyword_map.insert("reset"); - bare_label_keyword_map.insert("resource"); - bare_label_keyword_map.insert("resources"); - bare_label_keyword_map.insert("restart"); - bare_label_keyword_map.insert("restrict"); - bare_label_keyword_map.insert("returns"); - bare_label_keyword_map.insert("revoke"); - bare_label_keyword_map.insert("role"); - bare_label_keyword_map.insert("rollback"); - bare_label_keyword_map.insert("rollup"); - bare_label_keyword_map.insert("row"); - bare_label_keyword_map.insert("rows"); - bare_label_keyword_map.insert("rule"); - bare_label_keyword_map.insert("sample"); - bare_label_keyword_map.insert("savepoint"); - bare_label_keyword_map.insert("schema"); - bare_label_keyword_map.insert("schemas"); - bare_label_keyword_map.insert("scope"); - bare_label_keyword_map.insert("scroll"); - bare_label_keyword_map.insert("search"); - bare_label_keyword_map.insert("secret"); - bare_label_keyword_map.insert("secure"); - bare_label_keyword_map.insert("security"); - bare_label_keyword_map.insert("sequence"); - bare_label_keyword_map.insert("sequences"); - bare_label_keyword_map.insert("serializable"); - bare_label_keyword_map.insert("server"); - bare_label_keyword_map.insert("session"); - bare_label_keyword_map.insert("set"); - bare_label_keyword_map.insert("setof"); - bare_label_keyword_map.insert("sets"); - bare_label_keyword_map.insert("share"); - bare_label_keyword_map.insert("similarity"); - bare_label_keyword_map.insert("simple"); - bare_label_keyword_map.insert("skip"); - bare_label_keyword_map.insert("smallint"); - bare_label_keyword_map.insert("snapshot"); - bare_label_keyword_map.insert("sorted"); - bare_label_keyword_map.insert("source"); - bare_label_keyword_map.insert("sql"); - bare_label_keyword_map.insert("stable"); - bare_label_keyword_map.insert("standalone"); - bare_label_keyword_map.insert("start"); - bare_label_keyword_map.insert("statement"); - bare_label_keyword_map.insert("statistics"); - bare_label_keyword_map.insert("stdin"); - bare_label_keyword_map.insert("stdout"); - bare_label_keyword_map.insert("storage"); - bare_label_keyword_map.insert("stored"); - bare_label_keyword_map.insert("strict"); - bare_label_keyword_map.insert("strip"); - bare_label_keyword_map.insert("struct"); - bare_label_keyword_map.insert("subscription"); - bare_label_keyword_map.insert("substring"); - bare_label_keyword_map.insert("sysid"); - bare_label_keyword_map.insert("system"); - bare_label_keyword_map.insert("tables"); - bare_label_keyword_map.insert("tablespace"); - bare_label_keyword_map.insert("target"); - bare_label_keyword_map.insert("temp"); - bare_label_keyword_map.insert("template"); - bare_label_keyword_map.insert("temporary"); - bare_label_keyword_map.insert("text"); - bare_label_keyword_map.insert("ties"); - bare_label_keyword_map.insert("time"); - bare_label_keyword_map.insert("timestamp"); - bare_label_keyword_map.insert("transaction"); - bare_label_keyword_map.insert("transform"); - bare_label_keyword_map.insert("treat"); - bare_label_keyword_map.insert("trigger"); - bare_label_keyword_map.insert("trim"); - bare_label_keyword_map.insert("truncate"); - bare_label_keyword_map.insert("trusted"); - bare_label_keyword_map.insert("try_cast"); - bare_label_keyword_map.insert("tuple"); - bare_label_keyword_map.insert("type"); - bare_label_keyword_map.insert("types"); - bare_label_keyword_map.insert("unbounded"); - bare_label_keyword_map.insert("uncommitted"); - bare_label_keyword_map.insert("unencrypted"); - bare_label_keyword_map.insert("unknown"); - bare_label_keyword_map.insert("unlisten"); - bare_label_keyword_map.insert("unlogged"); - bare_label_keyword_map.insert("until"); - bare_label_keyword_map.insert("update"); - bare_label_keyword_map.insert("use"); - bare_label_keyword_map.insert("user"); - bare_label_keyword_map.insert("vacuum"); - bare_label_keyword_map.insert("valid"); - bare_label_keyword_map.insert("validate"); - bare_label_keyword_map.insert("validator"); - bare_label_keyword_map.insert("value"); - bare_label_keyword_map.insert("values"); - bare_label_keyword_map.insert("varchar"); - bare_label_keyword_map.insert("variable"); - bare_label_keyword_map.insert("version"); - bare_label_keyword_map.insert("view"); - bare_label_keyword_map.insert("views"); - bare_label_keyword_map.insert("virtual"); - bare_label_keyword_map.insert("volatile"); - bare_label_keyword_map.insert("whitespace"); - bare_label_keyword_map.insert("work"); - bare_label_keyword_map.insert("wrapper"); - bare_label_keyword_map.insert("write"); - bare_label_keyword_map.insert("xml"); - bare_label_keyword_map.insert("xmlattributes"); - bare_label_keyword_map.insert("xmlconcat"); - bare_label_keyword_map.insert("xmlelement"); - bare_label_keyword_map.insert("xmlexists"); - bare_label_keyword_map.insert("xmlforest"); - bare_label_keyword_map.insert("xmlnamespaces"); - bare_label_keyword_map.insert("xmlparse"); - bare_label_keyword_map.insert("xmlpi"); - bare_label_keyword_map.insert("xmlroot"); - bare_label_keyword_map.insert("xmlserialize"); - bare_label_keyword_map.insert("xmltable"); - bare_label_keyword_map.insert("yes"); - bare_label_keyword_map.insert("zone"); return keyword_maps; } } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/matcher_factory.cpp b/src/duckdb/src/parser/peg/matcher_factory.cpp index 84f9b1d2d..77e1ca472 100644 --- a/src/duckdb/src/parser/peg/matcher_factory.cpp +++ b/src/duckdb/src/parser/peg/matcher_factory.cpp @@ -220,7 +220,6 @@ Matcher &MatcherFactory::CreateRootMatcher(const string &root_rule) { // START GENERATED PACKRAT MEMOIZED RULES //===--------------------------------------------------------------------===// AddPackratMemoizedRule("Expression"); - AddPackratMemoizedRule("TableRef"); AddPackratMemoizedRule("Identifier"); AddPackratMemoizedRule("ColId"); AddPackratMemoizedRule("CatalogQualification"); diff --git a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp index 9f8df5442..408bf3963 100644 --- a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp +++ b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp @@ -442,9 +442,8 @@ bool Tokenizer::TokenizeInputInternal(TokenizerBehavior &behavior) const { if (CharacterIsInitialNumber(c)) { break; // Continue tokenizing } - // A digit separator must sit between two digits - if (c == '_' && StringUtil::CharacterIsDigit(sql[i - 1]) && i + 1 < sql.size() && - StringUtil::CharacterIsDigit(sql[i + 1])) { + // Allow underscore only when immediately followed by a digit (no consecutive underscores) + if (c == '_' && i + 1 < sql.size() && CharacterIsInitialNumber(sql[i + 1])) { break; } diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp index abd92a91b..cc687f4b9 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp @@ -162,8 +162,9 @@ PEGTransformerFactory::PEGTransformerFactory(ParsedGrammar &grammar_p) : grammar //===--------------------------------------------------------------------===// grammar.SetExpressionDepth("SingleExpression", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("Type", ExpressionDepthKind::NESTING); - grammar.SetExpressionDepth("NestedType", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("LambdaArrowExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("LogicalOrExpression", ExpressionDepthKind::CHAIN); + grammar.SetExpressionDepth("LogicalAndExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("NotExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsExpressionContinuation", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsDistinctFromExpression", ExpressionDepthKind::CHAIN); diff --git a/src/duckdb/src/parser/peg/transformer/transform_common.cpp b/src/duckdb/src/parser/peg/transformer/transform_common.cpp index b466696ab..0dc6dd071 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_common.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_common.cpp @@ -53,12 +53,6 @@ string PEGTransformerFactory::TransformIdentifierOrKeyword(PEGTransformer &trans LogicalType PEGTransformerFactory::TransformType(PEGTransformer &transformer, unique_ptr type_variations, const optional> &array_bounds) { - return LogicalType::UNBOUND(TransformNestedType(transformer, std::move(type_variations), array_bounds)); -} - -unique_ptr PEGTransformerFactory::TransformNestedType(PEGTransformer &transformer, - unique_ptr type_variations, - const optional> &array_bounds) { auto array_depth_guard = transformer.StackCheck(array_bounds ? array_bounds->size() : 0); auto type = std::move(type_variations); if (array_bounds) { @@ -74,7 +68,7 @@ unique_ptr PEGTransformerFactory::TransformNestedType(PEGTrans } } } - return type; + return LogicalType::UNBOUND(std::move(type)); } int64_t PEGTransformerFactory::TransformArrayKeyword(PEGTransformer &transformer) { @@ -138,9 +132,6 @@ PEGTransformerFactory::TransformTimeType(PEGTransformer &transformer, const Logi return make_uniq(Identifier(EnumUtil::ToString(LogicalType::TIMESTAMP)), vector> {}); } - if (with_timezone) { - throw ParserException("TIMESTAMP WITH TIME ZONE does not take a precision"); - } if (modifiers.size() > 1) { throw ParserException("TIMESTAMP only supports a single modifier"); } @@ -325,29 +316,36 @@ QualifiedName PEGTransformerFactory::TransformCatalogReservedSchemaTypeName( return QualifiedName(std::move(qualification), reserved_type_name); } -unique_ptr -PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, - optional>> nested_type) { +unique_ptr PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, + const optional> &type) { vector> map_children; - if (nested_type) { - map_children = std::move(*nested_type); + if (type) { + for (auto &child_type : *type) { + map_children.push_back(UnboundType::GetTypeExpression(child_type)->Copy()); + } } return make_uniq(Identifier("MAP"), std::move(map_children)); } -unique_ptr -PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, - vector> nested_type) { - return make_uniq(Identifier("TUPLE"), std::move(nested_type)); +unique_ptr PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, + const vector &type) { + vector> tuple_children; + for (auto &child : type) { + tuple_children.push_back(UnboundType::GetTypeExpression(child)->Copy()); + } + return make_uniq(Identifier("TUPLE"), std::move(tuple_children)); } -unique_ptr PEGTransformerFactory::TransformRowType( - PEGTransformer &transformer, optional>>> col_id_type_list) { +unique_ptr +PEGTransformerFactory::TransformRowType(PEGTransformer &transformer, + const optional> &col_id_type_list) { vector> struct_children; if (col_id_type_list) { for (auto &child : *col_id_type_list) { - child.second->SetAlias(child.first); - struct_children.push_back(std::move(child.second)); + auto &type_expr = UnboundType::GetTypeExpression(child.second); + auto new_type_expr = type_expr->Copy(); + new_type_expr->SetAlias(child.first); + struct_children.push_back(std::move(new_type_expr)); } } return make_uniq(Identifier("STRUCT"), std::move(struct_children)); @@ -374,25 +372,29 @@ unique_ptr PEGTransformerFactory::TransformVariantType(PEGTran unique_ptr PEGTransformerFactory::TransformUnionType(PEGTransformer &transformer, - vector>> col_id_type_list) { + const child_list_t &col_id_type_list) { + identifier_set_t union_names; vector> union_children; for (auto &colid : col_id_type_list) { - colid.second->SetAlias(colid.first); - union_children.push_back(std::move(colid.second)); + union_names.insert(colid.first); + auto &type_expr = UnboundType::GetTypeExpression(colid.second); + auto new_type_expr = type_expr->Copy(); + new_type_expr->SetAlias(colid.first); + union_children.push_back(std::move(new_type_expr)); } return make_uniq(Identifier("UNION"), std::move(union_children)); } -vector>> +child_list_t PEGTransformerFactory::TransformColIdTypeList(PEGTransformer &transformer, - vector>> col_id_type) { + const vector> &col_id_type) { return col_id_type; } -pair> -PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, - unique_ptr nested_type) { - return make_pair(Identifier(col_id), std::move(nested_type)); +pair PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, + const Identifier &col_id, + const LogicalType &type) { + return make_pair(Identifier(col_id), type); } unique_ptr PEGTransformerFactory::TransformBitType( @@ -529,8 +531,8 @@ unique_ptr PEGTransformerFactory::TransformNumberLiteral(PEGTr } unique_ptr PEGTransformerFactory::TransformSetofType(PEGTransformer &transformer, - unique_ptr nested_type) { - return nested_type; + const LogicalType &type) { + return UnboundType::GetTypeExpression(type)->Copy(); } // StringLiteral <- '\'' [^\']* '\'' diff --git a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp index d71badeae..3b1f0e552 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp @@ -40,9 +40,7 @@ void SetCopyOptions(unique_ptr &info, vector &optio info->parsed_options[option.name] = std::move(row_func); } } else if (option.name == "HEADER" || option.name == "ESCAPE") { - if (option.expression) { - info->parsed_options[option.name] = std::move(option.expression); - } else if (option.children.empty()) { + if (option.children.empty()) { info->parsed_options[option.name] = nullptr; } else { info->parsed_options[option.name] = ConstantExpression::FromValue(option.children[0]); @@ -134,7 +132,6 @@ unique_ptr PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, const optional> &insert_column_list, const bool &from_or_to, unique_ptr copy_file_name, - const optional &legacy_option, const optional> ©_options) { auto result = make_uniq(); auto info = make_uniq(); @@ -152,17 +149,8 @@ PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_pt } info->format = ExtractFormat(info->file_path); - vector generic_options; - - if (legacy_option) { - generic_options.push_back(*legacy_option); - } - if (copy_options) { - generic_options.insert(generic_options.end(), copy_options->begin(), copy_options->end()); - } - - if (!generic_options.empty()) { + auto generic_options = *copy_options; SetCopyOptions(info, generic_options); } @@ -205,11 +193,6 @@ Identifier PEGTransformerFactory::TransformIdentifierColId(PEGTransformer &trans return Identifier(result); } -GenericCopyOption PEGTransformerFactory::TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, - const string &delimiter) { - return GenericCopyOption("delimiter", delimiter); -} - vector PEGTransformerFactory::TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp index d8169e9d9..5a76f6ac1 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp @@ -130,13 +130,6 @@ pair> PEGTransformerFactory::TransformSeqNoCy return make_pair("cycle", make_uniq(SequenceInfo::SEQ_CYCLE, Value(false))); } -//! Sequence options take integer constants; anything else used to be rounded or cast silently -static void CheckIntegerSequenceOption(const char *option, const Literal &literal) { - if (!literal.IsNull() && literal.kind != LiteralKind::INTEGER) { - throw ParserException("Expected an integer argument for %s, got %s", option, literal.ToString()); - } -} - pair> PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, const bool &has_result, unique_ptr expression) { @@ -159,7 +152,6 @@ PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, con throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); - CheckIntegerSequenceOption("INCREMENT", const_expr.GetLiteral()); return make_pair("increment", make_uniq(SequenceInfo::SEQ_INC, const_expr.GetLiteral().ToValue())); } @@ -187,7 +179,6 @@ PEGTransformerFactory::TransformSeqSetMinMax(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); - CheckIntegerSequenceOption(seq_min_or_max == "minvalue" ? "MINVALUE" : "MAXVALUE", const_expr.GetLiteral()); auto seq_info = seq_min_or_max == "minvalue" ? SequenceInfo::SEQ_MIN : SequenceInfo::SEQ_MAX; return make_pair(seq_min_or_max, make_uniq(seq_info, const_expr.GetLiteral().ToValue())); } @@ -205,7 +196,6 @@ PEGTransformerFactory::TransformSeqStartWith(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); - CheckIntegerSequenceOption("START", const_expr.GetLiteral()); return make_pair("start", make_uniq(SequenceInfo::SEQ_START, const_expr.GetLiteral().ToValue())); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp index 3115c9282..9d4023599 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp @@ -33,9 +33,6 @@ PEGTransformerFactory::TransformCreateStatement(PEGTransformer &transformer, con } conflict_policy = OnCreateConflict::REPLACE_ON_CONFLICT; } - if (temporary && *temporary == SecretPersistType::PERSISTENT && result->info->type != CatalogType::SECRET_ENTRY) { - throw ParserException("PERSISTENT is only supported for CREATE SECRET"); - } if (result->info->type == CatalogType::SECRET_ENTRY) { auto &secret_info = result->info->Cast(); secret_info.persist_type = temporary ? *temporary : SecretPersistType::DEFAULT; diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp index 9c870fb7f..991d7ade2 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp @@ -1,6 +1,5 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/string_vector.hpp" -#include "duckdb/parser/expression/type_expression.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/parsed_data/create_type_info.hpp" @@ -36,8 +35,7 @@ PEGTransformerFactory::TransformEnumSelectType(PEGTransformer &transformer, unique_ptr PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal, - const optional> &array_bounds) { + const optional> &string_literal) { auto result = make_uniq(); idx_t enum_count = string_literal ? string_literal->size() : 0; Vector enum_vector(LogicalType::VARCHAR, enum_count); @@ -48,9 +46,6 @@ PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transforme } } result->type = LogicalType::ENUM(enum_vector, enum_count); - if (array_bounds && !array_bounds->empty()) { - result->type = TransformType(transformer, TypeExpression::FromLogicalType(result->type), array_bounds); - } return result; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp index f342bf280..2c634282e 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp @@ -102,9 +102,6 @@ PEGTransformerFactory::TransformCreateViewStmt(PEGTransformer &transformer, cons if (create_secure) { info->security_type = ViewSecurityType::SECURE_VIEW; } - if (create_recursive && !insert_column_list) { - throw ParserException("CREATE RECURSIVE VIEW requires a column list"); - } if (insert_column_list) { info->aliases = StringsToIdentifiers(*insert_column_list); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp index 372c91309..55ce4fdb0 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp @@ -48,24 +48,10 @@ vector> PEGTransformerFactory::TransformDeleteUsingClause(P unique_ptr PEGTransformerFactory::TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name, - optional truncate_options) { - if (truncate_options && *truncate_options) { - throw NotImplementedException("TRUNCATE ... CASCADE is not implemented"); - } + unique_ptr base_table_name) { auto result = make_uniq(); result->node->table = std::move(base_table_name); return std::move(result); } -bool PEGTransformerFactory::TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, - optional truncate_drop_behavior) { - // the alias is ignored, the result is whether CASCADE was given - return truncate_drop_behavior && *truncate_drop_behavior; -} - -bool PEGTransformerFactory::TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior) { - return drop_behavior; -} - } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp index 5ff313174..5fe0ec5c7 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp @@ -213,9 +213,6 @@ DescribeTarget PEGTransformerFactory::TransformDescribeBaseTableName(PEGTransfor DescribeTarget PEGTransformerFactory::TransformDescribeStringLiteral(PEGTransformer &transformer, const string &string_literal) { - if (string_literal.empty()) { - throw ParserException("Expected a table name, but got an empty string"); - } DescribeTarget result; result.is_table_name = true; result.table_name = Identifier(string_literal); diff --git a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp index ecc4f4394..0c8854dd4 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp @@ -8,9 +8,6 @@ unique_ptr PEGTransformerFactory::TransformDropStatement(PEGTransf unique_ptr drop_entries, const optional &drop_behavior) { if (drop_behavior) { - if (drop_entries->info->type == CatalogType::SECRET_ENTRY) { - throw ParserException("CASCADE and RESTRICT are not supported for DROP SECRET"); - } drop_entries->info->cascade = *drop_behavior; } return std::move(drop_entries); @@ -48,13 +45,13 @@ bool PEGTransformerFactory::TransformFunctionTypeFunction(PEGTransformer &transf unique_ptr PEGTransformerFactory::TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name) { + const vector &table_function_name) { auto result = make_uniq(); auto info = make_uniq(); if (table_function_name.size() > 1) { throw NotImplementedException("Can only drop one object at a time"); } - info->SetQualifiedName(table_function_name[0]); + info->SetQualifiedName(QualifiedName(table_function_name[0])); info->type = comment_macro_table; info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; result->info = std::move(info); @@ -214,9 +211,6 @@ unique_ptr PEGTransformerFactory::TransformDropSecret(PEGTransfor info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; info->SetName(secret_name); if (drop_secret_storage) { - if (extra_drop_info->persist_mode == SecretPersistType::TEMPORARY) { - throw ParserException("Can not combine TEMPORARY with specifying a storage for drop secret"); - } extra_drop_info->secret_storage = drop_secret_storage->GetIdentifierName(); } info->extra_drop_info = std::move(extra_drop_info); diff --git a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp index 65aff7f04..42e080ed7 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp @@ -239,19 +239,6 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( if (filter_clause) { filter_expr = std::move(*filter_clause); } - if (within_group_clause) { - if (distinct) { - throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); - } - if (!order_modifier->orders.empty()) { - throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); - } - order_modifier->orders = std::move(*within_group_clause); - if (order_modifier->orders.size() != 1) { - throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); - } - } - if (function_children.size() == 1 && ExpressionIsUnqualifiedStar(function_children[0].GetExpression()) && !distinct && order_modifier->orders.empty()) { // COUNT(*) gets converted into COUNT() @@ -302,6 +289,20 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( throw ParserException("RESPECT/IGNORE NULLS is not supported for non-window functions"); } if (within_group_clause) { + auto order_by_clause = std::move(*within_group_clause); + if (distinct) { + throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); + } + if (!order_modifier->orders.empty()) { + throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); + } + if (!order_modifier) { + throw InternalException("ORDER modifier for WITHIN GROUP is not initialized"); + } + order_modifier->orders = std::move(order_by_clause); + if (order_modifier->orders.size() != 1) { + throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); + } if (lowercase_name == "percentile_cont") { if (function_children.size() != 1) { throw ParserException("Wrong number of arguments for PERCENTILE_CONT"); @@ -602,14 +603,11 @@ static unique_ptr FoldConjunctionExpression(PEGTransformer &tr if (!tails) { return expr; } - auto depth_guard = transformer.StackCheck(1); - vector> children; - children.reserve(tails->size() + 1); - children.push_back(std::move(expr)); + auto depth_guard = transformer.StackCheck(tails->size()); for (auto &tail : *tails) { - children.push_back(std::move(tail)); + expr = make_uniq(conjunction_type, std::move(expr), std::move(tail)); } - return make_uniq(conjunction_type, std::move(children)); + return expr; } unique_ptr PEGTransformerFactory::TransformLogicalOrExpression( @@ -664,10 +662,15 @@ PEGTransformerFactory::TransformColDefAndExpressionTail(PEGTransformer &transfor return is_distinct_from_expression; } -static unique_ptr ApplyNotKeywords(PEGTransformer &transformer, const vector ¬_keywords, - unique_ptr expr) { - auto not_depth_guard = transformer.StackCheck(not_keywords.size()); - for (idx_t i = 0; i < not_keywords.size(); i++) { +unique_ptr +PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, optional> not_expression, + unique_ptr is_expression) { + auto expr = std::move(is_expression); + if (!not_expression) { + return expr; + } + auto not_depth_guard = transformer.StackCheck(not_expression->size()); + for (idx_t i = 0; i < not_expression->size(); i++) { vector> inner_list_children; inner_list_children.push_back(std::move(expr)); expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); @@ -675,22 +678,6 @@ static unique_ptr ApplyNotKeywords(PEGTransformer &transformer return expr; } -unique_ptr -PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, - const optional> ¬_expression, - unique_ptr is_expression) { - if (!not_expression) { - return is_expression; - } - return ApplyNotKeywords(transformer, *not_expression, std::move(is_expression)); -} - -unique_ptr -PEGTransformerFactory::TransformNotComparisonOperand(PEGTransformer &transformer, const vector ¬_expression, - unique_ptr is_expression) { - return ApplyNotKeywords(transformer, not_expression, std::move(is_expression)); -} - vector PEGTransformerFactory::TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword) { return not_keyword; @@ -712,13 +699,6 @@ static unique_ptr ApplyIsTest(unique_ptr exp ExpressionClassToString(is_expr->GetExpressionClass())); } -static unique_ptr ApplyCastOperator(unique_ptr expr, - unique_ptr cast_operator) { - auto cast_expr = unique_ptr_cast(std::move(cast_operator)); - cast_expr->ChildMutable() = std::move(expr); - return std::move(cast_expr); -} - static unique_ptr ApplyIsDistinctFromTail(unique_ptr expr, IsDistinctFromTail tail) { return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); @@ -726,7 +706,13 @@ static unique_ptr ApplyIsDistinctFromTail(unique_ptr ApplyComparisonTail(unique_ptr expr, ComparisonExpressionTail tail) { - return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); + auto right_expr = std::move(tail.expression); + for (idx_t i = 0; i < tail.not_keywords.size(); i++) { + vector> inner_list_children; + inner_list_children.push_back(std::move(right_expr)); + right_expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); + } + return make_uniq(tail.comparison_type, std::move(expr), std::move(right_expr)); } unique_ptr @@ -759,9 +745,6 @@ PEGTransformerFactory::TransformIsExpression(PEGTransformer &transformer, expr = TransformInfixOtherOperatorExpression(transformer, std::move(expr), std::move(other_tail)); break; } - case ExpressionTailType::CAST: - expr = ApplyCastOperator(std::move(expr), std::move(tail.cast)); - break; } previous_type = tail.type; } @@ -813,14 +796,6 @@ IsExpressionTail PEGTransformerFactory::TransformIsOtherOperatorTail(PEGTransfor return result; } -IsExpressionTail PEGTransformerFactory::TransformIsCastTail(PEGTransformer &transformer, - unique_ptr cast_operator) { - IsExpressionTail result; - result.type = ExpressionTailType::CAST; - result.cast = std::move(cast_operator); - return result; -} - unique_ptr PEGTransformerFactory::TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value) { @@ -894,13 +869,15 @@ PEGTransformerFactory::TransformIsDistinctFromTail(PEGTransformer &transformer, return {is_distinct_from_op, std::move(comparison_expression)}; } -ComparisonExpressionTail -PEGTransformerFactory::TransformComparisonExpressionTail(PEGTransformer &transformer, - const ExpressionType &comparison_operator, - unique_ptr comparison_operand) { +ComparisonExpressionTail PEGTransformerFactory::TransformComparisonExpressionTail( + PEGTransformer &transformer, const ExpressionType &comparison_operator, optional> not_expression, + unique_ptr between_in_like_expression) { ComparisonExpressionTail result; result.comparison_type = comparison_operator; - result.expression = std::move(comparison_operand); + if (not_expression) { + result.not_keywords = std::move(*not_expression); + } + result.expression = std::move(between_in_like_expression); return result; } @@ -1145,12 +1122,7 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (!in_predicate && !between_like_op) { return expr; } - idx_t predicate_count = 0; - if (in_predicate) { - for (auto &predicate : *in_predicate) { - predicate_count += 1 + predicate.casts.size(); - } - } + auto predicate_count = in_predicate ? in_predicate->size() : 0; if (between_like_op) { predicate_count++; } @@ -1158,9 +1130,6 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (in_predicate) { for (auto &predicate : *in_predicate) { expr = ApplyBetweenInLikeOperator(std::move(expr), predicate); - for (auto &cast : predicate.casts) { - expr = ApplyCastOperator(std::move(expr), std::move(cast)); - } } } if (between_like_op) { @@ -1169,26 +1138,14 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres return expr; } -BetweenInLikeOperator -PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause, - optional>> cast_operator) { - BetweenInLikeOperator result {has_result, std::move(in_clause)}; - if (cast_operator) { - result.casts = std::move(*cast_operator); - } - return result; +BetweenInLikeOperator PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, + unique_ptr in_clause) { + return {has_result, std::move(in_clause)}; } BetweenInLikeOperator PEGTransformerFactory::TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression) { - if (has_result && between_like_op_expression->GetExpressionClass() == ExpressionClass::FUNCTION) { - auto &function_name = between_like_op_expression->Cast().FunctionName(); - if (StringUtil::StartsWith(function_name.GetIdentifierName(), "!")) { - throw ParserException("NOT cannot precede an operator that is already negated (!~~, !~~*, !~ or !~*)"); - } - } return {has_result, std::move(between_like_op_expression)}; } @@ -1990,12 +1947,11 @@ PEGTransformerFactory::TransformSliceExpression(PEGTransformer &transformer, return make_uniq(ExpressionType::ARRAY_SLICE, std::move(slice_bound)); } -vector> -PEGTransformerFactory::TransformSliceBound(PEGTransformer &transformer, - optional> expression, - optional>> slice_upper_bound) { +vector> PEGTransformerFactory::TransformSliceBound( + PEGTransformer &transformer, optional> expression, + optional> end_slice_bound, optional> step_slice_bound) { vector> slice_bounds; - if (!slice_upper_bound) { + if (!end_slice_bound && !step_slice_bound) { if (expression && *expression) { slice_bounds.push_back(std::move(*expression)); } @@ -2006,27 +1962,11 @@ PEGTransformerFactory::TransformSliceBound(PEGTransformer &transformer, } else { slice_bounds.push_back(OperatorExpression::EmptySliceBound()); } - for (auto &bound : *slice_upper_bound) { - slice_bounds.push_back(std::move(bound)); + if (end_slice_bound && *end_slice_bound) { + slice_bounds.push_back(std::move(*end_slice_bound)); + } else { + slice_bounds.push_back(OperatorExpression::EmptySliceBound()); } - return slice_bounds; -} - -vector> -PEGTransformerFactory::TransformMinusStepSliceBound(PEGTransformer &transformer, - unique_ptr step_slice_bound) { - vector> slice_bounds; - slice_bounds.push_back(OperatorExpression::EmptySliceBound()); - slice_bounds.push_back(std::move(step_slice_bound)); - return slice_bounds; -} - -vector> -PEGTransformerFactory::TransformEndStepSliceBound(PEGTransformer &transformer, - unique_ptr end_slice_bound, - optional> step_slice_bound) { - vector> slice_bounds; - slice_bounds.push_back(std::move(end_slice_bound)); if (step_slice_bound && *step_slice_bound) { slice_bounds.push_back(std::move(*step_slice_bound)); } @@ -2035,15 +1975,19 @@ PEGTransformerFactory::TransformEndStepSliceBound(PEGTransformer &transformer, unique_ptr PEGTransformerFactory::TransformEndSliceBound(PEGTransformer &transformer, - optional> expression) { + optional> end_slice_value) { // If either the lower or upper bound is not specified, we use an empty constant LIST, // which we handle in the execution. - if (expression && *expression) { - return std::move(*expression); + if (end_slice_value && *end_slice_value) { + return std::move(*end_slice_value); } return OperatorExpression::EmptySliceBound(); } +unique_ptr PEGTransformerFactory::TransformEndSliceMinus(PEGTransformer &transformer) { + return OperatorExpression::EmptySliceBound(); +} + unique_ptr PEGTransformerFactory::TransformStepSliceBound(PEGTransformer &transformer, optional> expression) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp index 0b3eef8e5..44549a67b 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp @@ -35,9 +35,6 @@ static const TransformFrameOps FUNC_NAME_KEYWORD_OPS = {"FuncNameKeyword", static const TransformFrameOps TYPE_NAME_KEYWORD_OPS = {"TypeNameKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; -static const TransformFrameOps BARE_LABEL_KEYWORD_OPS = { - "BareLabelKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, - &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; static const TransformFrameOps SETTING_NAME_OPS = {"SettingName", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; @@ -409,8 +406,6 @@ static const TransformFrameOps SCHEMA_RESERVED_TYPE_NAME_OPS = { static const TransformFrameOps TYPE_MODIFIERS_OPS = {"TypeModifiers", &PEGTransformerFactory::InitializeTypeModifiersTrampoline, &PEGTransformerFactory::FinalizeTypeModifiersTrampoline}; -static const TransformFrameOps NESTED_TYPE_OPS = {"NestedType", &PEGTransformerFactory::InitializeNestedTypeTrampoline, - &PEGTransformerFactory::FinalizeNestedTypeTrampoline}; static const TransformFrameOps ROW_TYPE_OPS = {"RowType", &PEGTransformerFactory::InitializeRowTypeTrampoline, &PEGTransformerFactory::FinalizeRowTypeTrampoline}; static const TransformFrameOps SETOF_TYPE_OPS = {"SetofType", &PEGTransformerFactory::InitializeSetofTypeTrampoline, @@ -513,9 +508,6 @@ static const TransformFrameOps IDENTIFIER_COL_ID_OPS = {"IdentifierColId", static const TransformFrameOps COPY_FILE_NAME_SUFFIX_OPS = { "CopyFileNameSuffix", &PEGTransformerFactory::InitializeCopyFileNameSuffixTrampoline, &PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline}; -static const TransformFrameOps COPY_LEGACY_OPTION_OPS = {"CopyLegacyOption", - &PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline, - &PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline}; static const TransformFrameOps COPY_OPTIONS_OPS = {"CopyOptions", &PEGTransformerFactory::InitializeCopyOptionsTrampoline, &PEGTransformerFactory::FinalizeCopyOptionsTrampoline}; @@ -1126,15 +1118,6 @@ static const TransformFrameOps DELETE_STATEMENT_OPS = {"DeleteStatement", static const TransformFrameOps TRUNCATE_STATEMENT_OPS = {"TruncateStatement", &PEGTransformerFactory::InitializeTruncateStatementTrampoline, &PEGTransformerFactory::FinalizeTruncateStatementTrampoline}; -static const TransformFrameOps TRUNCATE_OPTIONS_OPS = {"TruncateOptions", - &PEGTransformerFactory::InitializeTruncateOptionsTrampoline, - &PEGTransformerFactory::FinalizeTruncateOptionsTrampoline}; -static const TransformFrameOps TRUNCATE_ALIAS_OPTIONS_OPS = { - "TruncateAliasOptions", &PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline, - &PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline}; -static const TransformFrameOps TRUNCATE_DROP_BEHAVIOR_OPS = { - "TruncateDropBehavior", &PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline, - &PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline}; static const TransformFrameOps TARGET_OPT_ALIAS_OPS = {"TargetOptAlias", &PEGTransformerFactory::InitializeTargetOptAliasTrampoline, &PEGTransformerFactory::FinalizeTargetOptAliasTrampoline}; @@ -1722,8 +1705,6 @@ static const TransformFrameOps IS_COMPARISON_TAIL_OPS = {"IsComparisonTail", static const TransformFrameOps IS_OTHER_OPERATOR_TAIL_OPS = { "IsOtherOperatorTail", &PEGTransformerFactory::InitializeIsOtherOperatorTailTrampoline, &PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline}; -static const TransformFrameOps IS_CAST_TAIL_OPS = {"IsCastTail", &PEGTransformerFactory::InitializeIsCastTailTrampoline, - &PEGTransformerFactory::FinalizeIsCastTailTrampoline}; static const TransformFrameOps IS_TEST_OPS = {"IsTest", &PEGTransformerFactory::InitializeIsTestTrampoline, &PEGTransformerFactory::FinalizeIsTestTrampoline}; static const TransformFrameOps IS_LITERAL_OPS = {"IsLiteral", &PEGTransformerFactory::InitializeIsLiteralTrampoline, @@ -1762,12 +1743,6 @@ static const TransformFrameOps COMPARISON_EXPRESSION_OPS = { static const TransformFrameOps COMPARISON_EXPRESSION_TAIL_OPS = { "ComparisonExpressionTail", &PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline, &PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline}; -static const TransformFrameOps COMPARISON_OPERAND_OPS = {"ComparisonOperand", - &PEGTransformerFactory::InitializeComparisonOperandTrampoline, - &PEGTransformerFactory::FinalizeComparisonOperandTrampoline}; -static const TransformFrameOps NOT_COMPARISON_OPERAND_OPS = { - "NotComparisonOperand", &PEGTransformerFactory::InitializeNotComparisonOperandTrampoline, - &PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline}; static const TransformFrameOps COMPARISON_OPERATOR_OPS = { "ComparisonOperator", &PEGTransformerFactory::InitializeComparisonOperatorTrampoline, &PEGTransformerFactory::FinalizeComparisonOperatorTrampoline}; @@ -1997,18 +1972,15 @@ static const TransformFrameOps SLICE_EXPRESSION_OPS = {"SliceExpression", &PEGTransformerFactory::FinalizeSliceExpressionTrampoline}; static const TransformFrameOps SLICE_BOUND_OPS = {"SliceBound", &PEGTransformerFactory::InitializeSliceBoundTrampoline, &PEGTransformerFactory::FinalizeSliceBoundTrampoline}; -static const TransformFrameOps SLICE_UPPER_BOUND_OPS = {"SliceUpperBound", - &PEGTransformerFactory::InitializeSliceUpperBoundTrampoline, - &PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline}; -static const TransformFrameOps MINUS_STEP_SLICE_BOUND_OPS = { - "MinusStepSliceBound", &PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline, - &PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline}; -static const TransformFrameOps END_STEP_SLICE_BOUND_OPS = { - "EndStepSliceBound", &PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline, - &PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline}; static const TransformFrameOps END_SLICE_BOUND_OPS = {"EndSliceBound", &PEGTransformerFactory::InitializeEndSliceBoundTrampoline, &PEGTransformerFactory::FinalizeEndSliceBoundTrampoline}; +static const TransformFrameOps END_SLICE_VALUE_OPS = {"EndSliceValue", + &PEGTransformerFactory::InitializeEndSliceValueTrampoline, + &PEGTransformerFactory::FinalizeEndSliceValueTrampoline}; +static const TransformFrameOps END_SLICE_MINUS_OPS = {"EndSliceMinus", + &PEGTransformerFactory::InitializeEndSliceMinusTrampoline, + &PEGTransformerFactory::FinalizeEndSliceMinusTrampoline}; static const TransformFrameOps STEP_SLICE_BOUND_OPS = {"StepSliceBound", &PEGTransformerFactory::InitializeStepSliceBoundTrampoline, &PEGTransformerFactory::FinalizeStepSliceBoundTrampoline}; @@ -2771,9 +2743,6 @@ static const TransformFrameOps QUALIFY_CLAUSE_OPS = {"QualifyClause", static const TransformFrameOps SAMPLE_CLAUSE_OPS = {"SampleClause", &PEGTransformerFactory::InitializeSampleClauseTrampoline, &PEGTransformerFactory::FinalizeSampleClauseTrampoline}; -static const TransformFrameOps SELECT_SAMPLE_CLAUSE_OPS = { - "SelectSampleClause", &PEGTransformerFactory::InitializeSelectSampleClauseTrampoline, - &PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline}; static const TransformFrameOps WINDOW_CLAUSE_OPS = {"WindowClause", &PEGTransformerFactory::InitializeWindowClauseTrampoline, &PEGTransformerFactory::FinalizeWindowClauseTrampoline}; @@ -3026,15 +2995,15 @@ static const TransformFrameOps BASE_TABLE_ALIAS_SET_OPS = { static const TransformFrameOps UPDATE_ALIAS_OPS = {"UpdateAlias", &PEGTransformerFactory::InitializeUpdateAliasTrampoline, &PEGTransformerFactory::FinalizeUpdateAliasTrampoline}; -static const TransformFrameOps UPDATE_SET_CLAUSE_LIST_OPS = { - "UpdateSetClauseList", &PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline, - &PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline}; static const TransformFrameOps UPDATE_SET_CLAUSE_OPS = {"UpdateSetClause", &PEGTransformerFactory::InitializeUpdateSetClauseTrampoline, &PEGTransformerFactory::FinalizeUpdateSetClauseTrampoline}; static const TransformFrameOps UPDATE_SET_TUPLE_OPS = {"UpdateSetTuple", &PEGTransformerFactory::InitializeUpdateSetTupleTrampoline, &PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline}; +static const TransformFrameOps UPDATE_SET_ELEMENT_LIST_OPS = { + "UpdateSetElementList", &PEGTransformerFactory::InitializeUpdateSetElementListTrampoline, + &PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline}; static const TransformFrameOps UPDATE_SET_ELEMENT_OPS = {"UpdateSetElement", &PEGTransformerFactory::InitializeUpdateSetElementTrampoline, &PEGTransformerFactory::FinalizeUpdateSetElementTrampoline}; @@ -3096,7 +3065,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"ColumnNameKeyword", &COLUMN_NAME_KEYWORD_OPS}, {"FuncNameKeyword", &FUNC_NAME_KEYWORD_OPS}, {"TypeNameKeyword", &TYPE_NAME_KEYWORD_OPS}, - {"BareLabelKeyword", &BARE_LABEL_KEYWORD_OPS}, {"SettingName", &SETTING_NAME_OPS}, {"AlterStatement", &ALTER_STATEMENT_OPS}, {"AlterOptions", &ALTER_OPTIONS_OPS}, @@ -3227,7 +3195,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CatalogReservedSchemaTypeName", &CATALOG_RESERVED_SCHEMA_TYPE_NAME_OPS}, {"SchemaReservedTypeName", &SCHEMA_RESERVED_TYPE_NAME_OPS}, {"TypeModifiers", &TYPE_MODIFIERS_OPS}, - {"NestedType", &NESTED_TYPE_OPS}, {"RowType", &ROW_TYPE_OPS}, {"SetofType", &SETOF_TYPE_OPS}, {"UnionType", &UNION_TYPE_OPS}, @@ -3267,7 +3234,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CopyFileNameIdentifierColId", ©_FILE_NAME_IDENTIFIER_COL_ID_OPS}, {"IdentifierColId", &IDENTIFIER_COL_ID_OPS}, {"CopyFileNameSuffix", ©_FILE_NAME_SUFFIX_OPS}, - {"CopyLegacyOption", ©_LEGACY_OPTION_OPS}, {"CopyOptions", ©_OPTIONS_OPS}, {"CopyOptionList", ©_OPTION_LIST_OPS}, {"SpecializedOptionList", &SPECIALIZED_OPTION_LIST_OPS}, @@ -3481,9 +3447,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"DeallocatePrepare", &DEALLOCATE_PREPARE_OPS}, {"DeleteStatement", &DELETE_STATEMENT_OPS}, {"TruncateStatement", &TRUNCATE_STATEMENT_OPS}, - {"TruncateOptions", &TRUNCATE_OPTIONS_OPS}, - {"TruncateAliasOptions", &TRUNCATE_ALIAS_OPTIONS_OPS}, - {"TruncateDropBehavior", &TRUNCATE_DROP_BEHAVIOR_OPS}, {"TargetOptAlias", &TARGET_OPT_ALIAS_OPS}, {"TargetAlias", &TARGET_ALIAS_OPS}, {"DeleteUsingClause", &DELETE_USING_CLAUSE_OPS}, @@ -3687,7 +3650,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctTail", &IS_DISTINCT_TAIL_OPS}, {"IsComparisonTail", &IS_COMPARISON_TAIL_OPS}, {"IsOtherOperatorTail", &IS_OTHER_OPERATOR_TAIL_OPS}, - {"IsCastTail", &IS_CAST_TAIL_OPS}, {"IsTest", &IS_TEST_OPS}, {"IsLiteral", &IS_LITERAL_OPS}, {"IsLiteralValue", &IS_LITERAL_VALUE_OPS}, @@ -3702,8 +3664,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctFromOp", &IS_DISTINCT_FROM_OP_OPS}, {"ComparisonExpression", &COMPARISON_EXPRESSION_OPS}, {"ComparisonExpressionTail", &COMPARISON_EXPRESSION_TAIL_OPS}, - {"ComparisonOperand", &COMPARISON_OPERAND_OPS}, - {"NotComparisonOperand", &NOT_COMPARISON_OPERAND_OPS}, {"ComparisonOperator", &COMPARISON_OPERATOR_OPS}, {"OperatorEqual", &OPERATOR_EQUAL_OPS}, {"OperatorNotEqual", &OPERATOR_NOT_EQUAL_OPS}, @@ -3784,10 +3744,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"MethodFunctionArguments", &METHOD_FUNCTION_ARGUMENTS_OPS}, {"SliceExpression", &SLICE_EXPRESSION_OPS}, {"SliceBound", &SLICE_BOUND_OPS}, - {"SliceUpperBound", &SLICE_UPPER_BOUND_OPS}, - {"MinusStepSliceBound", &MINUS_STEP_SLICE_BOUND_OPS}, - {"EndStepSliceBound", &END_STEP_SLICE_BOUND_OPS}, {"EndSliceBound", &END_SLICE_BOUND_OPS}, + {"EndSliceValue", &END_SLICE_VALUE_OPS}, + {"EndSliceMinus", &END_SLICE_MINUS_OPS}, {"StepSliceBound", &STEP_SLICE_BOUND_OPS}, {"PostfixOperator", &POSTFIX_OPERATOR_OPS}, {"SpecialFunctionExpression", &SPECIAL_FUNCTION_EXPRESSION_OPS}, @@ -4055,7 +4014,6 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"HavingClause", &HAVING_CLAUSE_OPS}, {"QualifyClause", &QUALIFY_CLAUSE_OPS}, {"SampleClause", &SAMPLE_CLAUSE_OPS}, - {"SelectSampleClause", &SELECT_SAMPLE_CLAUSE_OPS}, {"WindowClause", &WINDOW_CLAUSE_OPS}, {"WindowDefinition", &WINDOW_DEFINITION_OPS}, {"SampleEntry", &SAMPLE_ENTRY_OPS}, @@ -4146,9 +4104,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"BaseTableSet", &BASE_TABLE_SET_OPS}, {"BaseTableAliasSet", &BASE_TABLE_ALIAS_SET_OPS}, {"UpdateAlias", &UPDATE_ALIAS_OPS}, - {"UpdateSetClauseList", &UPDATE_SET_CLAUSE_LIST_OPS}, {"UpdateSetClause", &UPDATE_SET_CLAUSE_OPS}, {"UpdateSetTuple", &UPDATE_SET_TUPLE_OPS}, + {"UpdateSetElementList", &UPDATE_SET_ELEMENT_LIST_OPS}, {"UpdateSetElement", &UPDATE_SET_ELEMENT_OPS}, {"UpdateSetColumnTarget", &UPDATE_SET_COLUMN_TARGET_OPS}, {"UseStatement", &USE_STATEMENT_OPS}, @@ -6637,49 +6595,6 @@ PEGTransformerFactory::FinalizeTypeModifiersTrampoline(PEGTransformer &transform return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeNestedTypeTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &repeat_opt = list_pr.GetChild(1).Cast(); - idx_t dynamic_child_count = 0; - if (repeat_opt.HasResult()) { - auto &repeat_pr = repeat_opt.GetResult().Cast(); - auto repeat_children = repeat_pr.GetChildren(); - dynamic_child_count = repeat_children.size(); - process.ReserveChildSlots(2 + dynamic_child_count - 1); - for (idx_t i = repeat_children.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 1 + child_idx); - } - } else { - process.ReserveChildSlots(2 - 1); - } - process.PushChild({transformer.GetRule("TypeVariations"), list_pr.GetChild(0)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - idx_t dynamic_child_count = 0; - auto &dynamic_repeat_opt = list_pr.GetChild(1).Cast(); - if (dynamic_repeat_opt.HasResult()) { - auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); - auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); - dynamic_child_count = dynamic_repeat_children.size(); - } - auto type_variations = process.TakeResult>(0); - optional> array_bounds {}; - if (dynamic_child_count > 0) { - vector array_bounds_value; - for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { - array_bounds_value.push_back(process.TakeResult(i)); - } - array_bounds = std::move(array_bounds_value); - } - auto result = TransformNestedType(transformer, std::move(type_variations), array_bounds); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -6692,11 +6607,11 @@ void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transfor arena_ptr PEGTransformerFactory::FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - optional>>> col_id_type_list {}; + optional> col_id_type_list {}; if (process.child_results[0]) { - col_id_type_list = process.TakeResult>>>(0); + col_id_type_list = process.TakeResult>(0); } - auto result = TransformRowType(transformer, std::move(col_id_type_list)); + auto result = TransformRowType(transformer, col_id_type_list); return transformer.MakeResult>(std::move(result)); } @@ -6704,13 +6619,13 @@ void PEGTransformerFactory::InitializeSetofTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 0); + process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeSetofTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto nested_type = process.TakeResult>(0); - auto result = TransformSetofType(transformer, std::move(nested_type)); + auto type = process.TakeResult(0); + auto result = TransformSetofType(transformer, type); return transformer.MakeResult>(std::move(result)); } @@ -6723,8 +6638,8 @@ void PEGTransformerFactory::InitializeUnionTypeTrampoline(PEGTransformer &transf arena_ptr PEGTransformerFactory::FinalizeUnionTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto col_id_type_list = process.TakeResult>>>(0); - auto result = TransformUnionType(transformer, std::move(col_id_type_list)); + auto col_id_type_list = process.TakeResult>(0); + auto result = TransformUnionType(transformer, col_id_type_list); return transformer.MakeResult>(std::move(result)); } @@ -6746,12 +6661,12 @@ PEGTransformerFactory::FinalizeColIdTypeListTrampoline(PEGTransformer &transform auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(0))); auto dynamic_child_count = dynamic_list_items.size(); - vector>> col_id_type; + vector> col_id_type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - col_id_type.push_back(process.TakeResult>>(i)); + col_id_type.push_back(process.TakeResult>(i)); } - auto result = TransformColIdTypeList(transformer, std::move(col_id_type)); - return transformer.MakeResult>>>(std::move(result)); + auto result = TransformColIdTypeList(transformer, col_id_type); + return transformer.MakeResult>(result); } void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transformer, @@ -6765,7 +6680,7 @@ void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transfor process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); } } else { process.ReserveChildSlots(1 - 1); @@ -6781,16 +6696,16 @@ arena_ptr PEGTransformerFactory::FinalizeMapTypeTrampoline auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(dynamic_list_opt.GetResult())); dynamic_child_count = dynamic_list_items.size(); } - optional>> nested_type {}; - auto &nested_type_opt = list_pr.GetChild(1).Cast(); - if (nested_type_opt.HasResult()) { - vector> nested_type_value; + optional> type {}; + auto &type_opt = list_pr.GetChild(1).Cast(); + if (type_opt.HasResult()) { + vector type_value; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - nested_type_value.push_back(process.TakeResult>(i)); + type_value.push_back(process.TakeResult(i)); } - nested_type = std::move(nested_type_value); + type = std::move(type_value); } - auto result = TransformMapType(transformer, std::move(nested_type)); + auto result = TransformMapType(transformer, type); return transformer.MakeResult>(std::move(result)); } @@ -6802,7 +6717,7 @@ void PEGTransformerFactory::InitializeTupleTypeTrampoline(PEGTransformer &transf process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); } } @@ -6811,11 +6726,11 @@ arena_ptr PEGTransformerFactory::FinalizeTupleTypeTrampoli auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(1))); auto dynamic_child_count = dynamic_list_items.size(); - vector> nested_type; + vector type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - nested_type.push_back(process.TakeResult>(i)); + type.push_back(process.TakeResult(i)); } - auto result = TransformTupleType(transformer, std::move(nested_type)); + auto result = TransformTupleType(transformer, type); return transformer.MakeResult>(std::move(result)); } @@ -6823,16 +6738,16 @@ void PEGTransformerFactory::InitializeColIdTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 1); + process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 1); process.PushChild({transformer.GetRule("ColId"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeColIdTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto col_id = process.TakeResult(0); - auto nested_type = process.TakeResult>(1); - auto result = TransformColIdType(transformer, col_id, std::move(nested_type)); - return transformer.MakeResult>>(std::move(result)); + auto type = process.TakeResult(1); + auto result = TransformColIdType(transformer, col_id, type); + return transformer.MakeResult>(result); } void PEGTransformerFactory::InitializeArrayBoundsTrampoline(PEGTransformer &transformer, @@ -7175,14 +7090,10 @@ PEGTransformerFactory::FinalizeCopyVariationsTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeCopyTableTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(6); - auto ©_options_opt = list_pr.GetChild(5).Cast(); + process.ReserveChildSlots(5); + auto ©_options_opt = list_pr.GetChild(4).Cast(); if (copy_options_opt.HasResult()) { - process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 5); - } - auto ©_legacy_option_opt = list_pr.GetChild(4).Cast(); - if (copy_legacy_option_opt.HasResult()) { - process.PushChild({transformer.GetRule("CopyLegacyOption"), copy_legacy_option_opt.GetResult()}, 4); + process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 4); } process.PushChild({transformer.GetRule("CopyFileName"), list_pr.GetChild(3)}, 3); process.PushChild({transformer.GetRule("FromOrTo"), list_pr.GetChild(2)}, 2); @@ -7202,16 +7113,12 @@ arena_ptr PEGTransformerFactory::FinalizeCopyTableTrampoli } auto from_or_to = process.TakeResult(2); auto copy_file_name = process.TakeResult>(3); - optional copy_legacy_option {}; - if (process.child_results[4]) { - copy_legacy_option = process.TakeResult(4); - } optional> copy_options {}; - if (process.child_results[5]) { - copy_options = process.TakeResult>(5); + if (process.child_results[4]) { + copy_options = process.TakeResult>(4); } auto result = TransformCopyTable(transformer, std::move(base_table_name), insert_column_list, from_or_to, - std::move(copy_file_name), copy_legacy_option, copy_options); + std::move(copy_file_name), copy_options); return transformer.MakeResult>(std::move(result)); } @@ -7410,23 +7317,6 @@ PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &tran return transformer.MakeResult(result); } -void PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); -} - -arena_ptr -PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - bool has_result {}; - auto &has_result_opt = list_pr.GetChild(0).Cast(); - has_result = has_result_opt.HasResult(); - auto string_literal = TransformStringLiteral(transformer, list_pr.GetChild(2)); - auto result = TransformCopyLegacyOption(transformer, has_result, string_literal); - return transformer.MakeResult(result); -} - void PEGTransformerFactory::InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -11585,34 +11475,13 @@ PEGTransformerFactory::FinalizeEnumSelectTypeTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &repeat_opt = list_pr.GetChild(2).Cast(); - idx_t dynamic_child_count = 0; - if (repeat_opt.HasResult()) { - auto &repeat_pr = repeat_opt.GetResult().Cast(); - auto repeat_children = repeat_pr.GetChildren(); - dynamic_child_count = repeat_children.size(); - process.ReserveChildSlots(1 + dynamic_child_count - 1); - for (idx_t i = repeat_children.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 0 + child_idx); - } - } else { - process.ReserveChildSlots(1 - 1); - } + process.ReserveChildSlots(0); } arena_ptr PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - idx_t dynamic_child_count = 0; - auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); - if (dynamic_repeat_opt.HasResult()) { - auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); - auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); - dynamic_child_count = dynamic_repeat_children.size(); - } optional> string_literal {}; auto &string_literal_opt = ExtractResultFromParens(list_pr.GetChild(1)).Cast(); if (string_literal_opt.HasResult()) { @@ -11624,15 +11493,7 @@ PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &t } string_literal = std::move(string_literal_value); } - optional> array_bounds {}; - if (dynamic_child_count > 0) { - vector array_bounds_value; - for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - array_bounds_value.push_back(process.TakeResult(i)); - } - array_bounds = std::move(array_bounds_value); - } - auto result = TransformEnumStringLiteralList(transformer, string_literal, array_bounds); + auto result = TransformEnumStringLiteralList(transformer, string_literal); return transformer.MakeResult>(std::move(result)); } @@ -11805,11 +11666,7 @@ PEGTransformerFactory::FinalizeDeleteStatementTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - auto &truncate_options_opt = list_pr.GetChild(3).Cast(); - if (truncate_options_opt.HasResult()) { - process.PushChild({transformer.GetRule("TruncateOptions"), truncate_options_opt.GetResult()}, 1); - } + process.ReserveChildSlots(1); process.PushChild({transformer.GetRule("BaseTableName"), list_pr.GetChild(2)}, 0); } @@ -11821,73 +11678,10 @@ PEGTransformerFactory::FinalizeTruncateStatementTrampoline(PEGTransformer &trans auto &has_result_opt = list_pr.GetChild(1).Cast(); has_result = has_result_opt.HasResult(); auto base_table_name = process.TakeResult>(0); - optional truncate_options {}; - if (process.child_results[1]) { - truncate_options = process.TakeResult(1); - } - auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name), truncate_options); + auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name)); return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); - process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); - } - process.PushChild({*child_rule, choice_result}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult(0); - return transformer.MakeResult(result); -} - -void PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - auto &truncate_drop_behavior_opt = list_pr.GetChild(1).Cast(); - if (truncate_drop_behavior_opt.HasResult()) { - process.PushChild({transformer.GetRule("TruncateDropBehavior"), truncate_drop_behavior_opt.GetResult()}, 1); - } - process.PushChild({transformer.GetRule("TargetAlias"), list_pr.GetChild(0)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto target_alias = process.TakeResult(0); - optional truncate_drop_behavior {}; - if (process.child_results[1]) { - truncate_drop_behavior = process.TakeResult(1); - } - auto result = TransformTruncateAliasOptions(transformer, target_alias, truncate_drop_behavior); - return transformer.MakeResult(result); -} - -void PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("DropBehavior"), list_pr.GetChild(0)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto drop_behavior = process.TakeResult(0); - auto result = TransformTruncateDropBehavior(transformer, drop_behavior); - return transformer.MakeResult(result); -} - void PEGTransformerFactory::InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -12449,13 +12243,7 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableTrampoli void PEGTransformerFactory::InitializeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); - auto dynamic_child_count = list_items.size(); - process.ReserveChildSlots(3 + dynamic_child_count - 1); - for (idx_t i = list_items.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("QualifiedTableFunction"), list_items[child_idx].get()}, 2 + child_idx); - } + process.ReserveChildSlots(2); auto &if_exists_opt = list_pr.GetChild(1).Cast(); if (if_exists_opt.HasResult()) { process.PushChild({transformer.GetRule("IfExists"), if_exists_opt.GetResult()}, 1); @@ -12467,18 +12255,17 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); - auto dynamic_child_count = dynamic_list_items.size(); auto comment_macro_table = process.TakeResult(0); optional if_exists {}; if (process.child_results[1]) { if_exists = process.TakeResult(1); } - vector qualified_table_function; - for (idx_t i = 2; i < 2 + dynamic_child_count; i++) { - qualified_table_function.push_back(process.TakeResult(i)); + vector table_function_name; + auto table_function_name_items = ExtractParseResultsFromList(list_pr.GetChild(2)); + for (auto &table_function_name_item : table_function_name_items) { + table_function_name.push_back(table_function_name_item.get().Cast().identifier); } - auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, qualified_table_function); + auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, table_function_name); return transformer.MakeResult>(std::move(result)); } @@ -15854,7 +15641,7 @@ PEGTransformerFactory::FinalizeLogicalNotExpressionTrampoline(PEGTransformer &tr not_expression = process.TakeResult>(0); } auto is_expression = process.TakeResult>(1); - auto result = TransformLogicalNotExpression(transformer, not_expression, std::move(is_expression)); + auto result = TransformLogicalNotExpression(transformer, std::move(not_expression), std::move(is_expression)); return transformer.MakeResult>(std::move(result)); } @@ -15883,7 +15670,7 @@ PEGTransformerFactory::FinalizeNotExpressionTrampoline(PEGTransformer &transform not_keyword.push_back(process.TakeResult(i)); } auto result = TransformNotExpression(transformer, not_keyword); - return transformer.MakeResult>(result); + return transformer.MakeResult>(std::move(result)); } void PEGTransformerFactory::InitializeNotKeywordTrampoline(PEGTransformer &transformer, @@ -16046,20 +15833,6 @@ PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &tra return transformer.MakeResult(std::move(result)); } -void PEGTransformerFactory::InitializeIsCastTailTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("CastOperator"), list_pr.GetChild(0)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto cast_operator = process.TakeResult>(0); - auto result = TransformIsCastTail(transformer, std::move(cast_operator)); - return transformer.MakeResult(std::move(result)); -} - void PEGTransformerFactory::InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -16329,8 +16102,12 @@ PEGTransformerFactory::FinalizeComparisonExpressionTrampoline(PEGTransformer &tr void PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("ComparisonOperand"), list_pr.GetChild(1)}, 1); + process.ReserveChildSlots(3); + process.PushChild({transformer.GetRule("BetweenInLikeExpression"), list_pr.GetChild(2)}, 2); + auto ¬_expression_opt = list_pr.GetChild(1).Cast(); + if (not_expression_opt.HasResult()) { + process.PushChild({transformer.GetRule("NotExpression"), not_expression_opt.GetResult()}, 1); + } process.PushChild({transformer.GetRule("ComparisonOperator"), list_pr.GetChild(0)}, 0); } @@ -16338,47 +16115,14 @@ arena_ptr PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto comparison_operator = process.TakeResult(0); - auto comparison_operand = process.TakeResult>(1); - auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(comparison_operand)); - return transformer.MakeResult(std::move(result)); -} - -void PEGTransformerFactory::InitializeComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); - process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + optional> not_expression {}; + if (process.child_results[1]) { + not_expression = process.TakeResult>(1); } - process.PushChild({*child_rule, choice_result}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult>(0); - return transformer.MakeResult>(std::move(result)); -} - -void PEGTransformerFactory::InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("IsExpression"), list_pr.GetChild(1)}, 1); - process.PushChild({transformer.GetRule("NotExpression"), list_pr.GetChild(0)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto not_expression = process.TakeResult>(0); - auto is_expression = process.TakeResult>(1); - auto result = TransformNotComparisonOperand(transformer, not_expression, std::move(is_expression)); - return transformer.MakeResult>(std::move(result)); + auto between_in_like_expression = process.TakeResult>(2); + auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(not_expression), + std::move(between_in_like_expression)); + return transformer.MakeResult(std::move(result)); } void PEGTransformerFactory::InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, @@ -16536,46 +16280,18 @@ PEGTransformerFactory::FinalizeBetweenInLikeExpressionTrampoline(PEGTransformer void PEGTransformerFactory::InitializeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &repeat_opt = list_pr.GetChild(2).Cast(); - idx_t dynamic_child_count = 0; - if (repeat_opt.HasResult()) { - auto &repeat_pr = repeat_opt.GetResult().Cast(); - auto repeat_children = repeat_pr.GetChildren(); - dynamic_child_count = repeat_children.size(); - process.ReserveChildSlots(2 + dynamic_child_count - 1); - for (idx_t i = repeat_children.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("CastOperator"), repeat_children[child_idx].get()}, 1 + child_idx); - } - } else { - process.ReserveChildSlots(2 - 1); - } + process.ReserveChildSlots(1); process.PushChild({transformer.GetRule("InClause"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - idx_t dynamic_child_count = 0; - auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); - if (dynamic_repeat_opt.HasResult()) { - auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); - auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); - dynamic_child_count = dynamic_repeat_children.size(); - } bool has_result {}; auto &has_result_opt = list_pr.GetChild(0).Cast(); has_result = has_result_opt.HasResult(); auto in_clause = process.TakeResult>(0); - optional>> cast_operator {}; - if (dynamic_child_count > 0) { - vector> cast_operator_value; - for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { - cast_operator_value.push_back(process.TakeResult>(i)); - } - cast_operator = std::move(cast_operator_value); - } - auto result = TransformInPredicate(transformer, has_result, std::move(in_clause), std::move(cast_operator)); + auto result = TransformInPredicate(transformer, has_result, std::move(in_clause)); return transformer.MakeResult(std::move(result)); } @@ -18065,10 +17781,14 @@ PEGTransformerFactory::FinalizeSliceExpressionTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - auto &slice_upper_bound_opt = list_pr.GetChild(1).Cast(); - if (slice_upper_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("SliceUpperBound"), slice_upper_bound_opt.GetResult()}, 1); + process.ReserveChildSlots(3); + auto &step_slice_bound_opt = list_pr.GetChild(2).Cast(); + if (step_slice_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 2); + } + auto &end_slice_bound_opt = list_pr.GetChild(1).Cast(); + if (end_slice_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("EndSliceBound"), end_slice_bound_opt.GetResult()}, 1); } auto &expression_opt = list_pr.GetChild(0).Cast(); if (expression_opt.HasResult()) { @@ -18082,91 +17802,70 @@ PEGTransformerFactory::FinalizeSliceBoundTrampoline(PEGTransformer &transformer, if (process.child_results[0]) { expression = process.TakeResult>(0); } - optional>> slice_upper_bound {}; + optional> end_slice_bound {}; if (process.child_results[1]) { - slice_upper_bound = process.TakeResult>>(1); + end_slice_bound = process.TakeResult>(1); } - auto result = TransformSliceBound(transformer, std::move(expression), std::move(slice_upper_bound)); + optional> step_slice_bound {}; + if (process.child_results[2]) { + step_slice_bound = process.TakeResult>(2); + } + auto result = TransformSliceBound(transformer, std::move(expression), std::move(end_slice_bound), + std::move(step_slice_bound)); return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + auto &end_slice_value_opt = list_pr.GetChild(1).Cast(); + if (end_slice_value_opt.HasResult()) { + process.PushChild({transformer.GetRule("EndSliceValue"), end_slice_value_opt.GetResult()}, 0); } - process.PushChild({*child_rule, choice_result}, 0); } arena_ptr -PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult>>(0); - return transformer.MakeResult>>(std::move(result)); +PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + optional> end_slice_value {}; + if (process.child_results[0]) { + end_slice_value = process.TakeResult>(0); + } + auto result = TransformEndSliceBound(transformer, std::move(end_slice_value)); + return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeEndSliceValueTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("StepSliceBound"), list_pr.GetChild(2)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto step_slice_bound = process.TakeResult>(0); - auto result = TransformMinusStepSliceBound(transformer, std::move(step_slice_bound)); - return transformer.MakeResult>>(std::move(result)); -} - -void PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - auto &step_slice_bound_opt = list_pr.GetChild(1).Cast(); - if (step_slice_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); } - process.PushChild({transformer.GetRule("EndSliceBound"), list_pr.GetChild(0)}, 0); + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr -PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto end_slice_bound = process.TakeResult>(0); - optional> step_slice_bound {}; - if (process.child_results[1]) { - step_slice_bound = process.TakeResult>(1); - } - auto result = TransformEndStepSliceBound(transformer, std::move(end_slice_bound), std::move(step_slice_bound)); - return transformer.MakeResult>>(std::move(result)); +PEGTransformerFactory::FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult>(0); + return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, +void PEGTransformerFactory::InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - auto &expression_opt = list_pr.GetChild(1).Cast(); - if (expression_opt.HasResult()) { - process.PushChild({transformer.GetRule("Expression"), expression_opt.GetResult()}, 0); - } + process.ReserveChildSlots(0); } arena_ptr -PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, +PEGTransformerFactory::FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - optional> expression {}; - if (process.child_results[0]) { - expression = process.TakeResult>(0); - } - auto result = TransformEndSliceBound(transformer, std::move(expression)); + auto result = TransformEndSliceMinus(transformer); return transformer.MakeResult>(std::move(result)); } @@ -19216,15 +18915,15 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(8); - auto &returning_clause_opt = list_pr.GetChild(10).Cast(); + auto &returning_clause_opt = list_pr.GetChild(9).Cast(); if (returning_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("ReturningClause"), returning_clause_opt.GetResult()}, 7); } - auto &on_conflict_clause_opt = list_pr.GetChild(9).Cast(); + auto &on_conflict_clause_opt = list_pr.GetChild(8).Cast(); if (on_conflict_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("OnConflictClause"), on_conflict_clause_opt.GetResult()}, 6); } - process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(8)}, 5); + process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(7)}, 5); auto &insert_column_list_opt = list_pr.GetChild(6).Cast(); if (insert_column_list_opt.HasResult()) { process.PushChild({transformer.GetRule("InsertColumnList"), insert_column_list_opt.GetResult()}, 4); @@ -19247,7 +18946,6 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & arena_ptr PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); optional with_clause {}; if (process.child_results[0]) { with_clause = process.TakeResult(0); @@ -19265,9 +18963,6 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[4]) { insert_column_list = process.TakeResult>(4); } - bool has_result {}; - auto &has_result_opt = list_pr.GetChild(7).Cast(); - has_result = has_result_opt.HasResult(); auto insert_values = process.TakeResult(5); optional> on_conflict_clause {}; if (process.child_results[6]) { @@ -19277,10 +18972,9 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[7]) { returning_clause = process.TakeResult>>(7); } - auto result = - TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), - by_name_or_position, insert_column_list, has_result, std::move(insert_values), - std::move(on_conflict_clause), std::move(returning_clause)); + auto result = TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), + by_name_or_position, insert_column_list, std::move(insert_values), + std::move(on_conflict_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -19633,18 +19327,18 @@ void PEGTransformerFactory::InitializeOnConflictUpdateTrampoline(PEGTransformer if (where_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("WhereClause"), where_clause_opt.GetResult()}, 1); } - process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 0); + process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 0); } arena_ptr PEGTransformerFactory::FinalizeOnConflictUpdateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto update_set_clause_list = process.TakeResult>(0); + auto update_set_clause = process.TakeResult>(0); optional> where_clause {}; if (process.child_results[1]) { where_clause = process.TakeResult>(1); } - auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause_list), std::move(where_clause)); + auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause), std::move(where_clause)); return transformer.MakeResult>(std::move(result)); } @@ -21785,11 +21479,7 @@ PEGTransformerFactory::FinalizeInnerTableRefTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableSubqueryTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(5); - auto &sample_clause_opt = list_pr.GetChild(4).Cast(); - if (sample_clause_opt.HasResult()) { - process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 4); - } + process.ReserveChildSlots(4); auto &table_alias_opt = list_pr.GetChild(3).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 3); @@ -21821,12 +21511,8 @@ PEGTransformerFactory::FinalizeTableSubqueryTrampoline(PEGTransformer &transform if (process.child_results[3]) { table_alias = process.TakeResult(3); } - optional> sample_clause {}; - if (process.child_results[4]) { - sample_clause = process.TakeResult>(4); - } - auto result = TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), - table_alias, std::move(sample_clause)); + auto result = + TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), table_alias); return transformer.MakeResult>(std::move(result)); } @@ -21895,11 +21581,7 @@ PEGTransformerFactory::FinalizeTableAliasColonTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeValuesRefTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(4); - auto &sample_clause_opt = list_pr.GetChild(3).Cast(); - if (sample_clause_opt.HasResult()) { - process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 3); - } + process.ReserveChildSlots(3); auto &table_alias_opt = list_pr.GetChild(2).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 2); @@ -21922,12 +21604,7 @@ arena_ptr PEGTransformerFactory::FinalizeValuesRefTrampoli if (process.child_results[2]) { table_alias = process.TakeResult(2); } - optional> sample_clause {}; - if (process.child_results[3]) { - sample_clause = process.TakeResult>(3); - } - auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias, - std::move(sample_clause)); + auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias); return transformer.MakeResult>(std::move(result)); } @@ -22400,11 +22077,7 @@ PEGTransformerFactory::FinalizeTableFunctionTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableFunctionLateralOptTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(6); - auto &sample_clause_opt = list_pr.GetChild(5).Cast(); - if (sample_clause_opt.HasResult()) { - process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 5); - } + process.ReserveChildSlots(5); auto &table_alias_opt = list_pr.GetChild(4).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 4); @@ -22438,13 +22111,8 @@ PEGTransformerFactory::FinalizeTableFunctionLateralOptTrampoline(PEGTransformer if (process.child_results[4]) { table_alias = process.TakeResult(4); } - optional> sample_clause {}; - if (process.child_results[5]) { - sample_clause = process.TakeResult>(5); - } auto result = TransformTableFunctionLateralOpt(transformer, lateral, qualified_table_function, - std::move(table_function_arguments), with_ordinality, table_alias, - std::move(sample_clause)); + std::move(table_function_arguments), with_ordinality, table_alias); return transformer.MakeResult>(std::move(result)); } @@ -22615,17 +22283,15 @@ PEGTransformerFactory::FinalizeFunctionArgumentTrampoline(PEGTransformer &transf void PEGTransformerFactory::InitializeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("Expression"), list_pr.GetChild(2)}, 1); - process.PushChild({transformer.GetRule("TypeFuncName"), list_pr.GetChild(0)}, 0); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("NamedParameter"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto type_func_name = process.TakeResult(0); - auto expression = process.TakeResult>(1); - auto result = TransformNamedFunctionArgument(transformer, type_func_name, std::move(expression)); + auto named_parameter = process.TakeResult(0); + auto result = TransformNamedFunctionArgument(transformer, std::move(named_parameter)); return transformer.MakeResult(std::move(result)); } @@ -23632,21 +23298,6 @@ PEGTransformerFactory::FinalizeSampleClauseTrampoline(PEGTransformer &transforme return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("SampleEntry"), list_pr.GetChild(1)}, 0); -} - -arena_ptr -PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto sample_entry = process.TakeResult>(0); - auto result = TransformSelectSampleClause(transformer, std::move(sample_entry)); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -24599,12 +24250,12 @@ PEGTransformerFactory::FinalizeExpressionOptIdentifierTrampoline(PEGTransformer GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); auto expression = process.TakeResult>(0); - optional bare_col_label {}; - auto &bare_col_label_opt = list_pr.GetChild(1).Cast(); - if (bare_col_label_opt.HasResult()) { - bare_col_label = bare_col_label_opt.GetResult().Cast().identifier; + optional identifier {}; + auto &identifier_opt = list_pr.GetChild(1).Cast(); + if (identifier_opt.HasResult()) { + identifier = identifier_opt.GetResult().Cast().identifier; } - auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), bare_col_label); + auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), identifier); return transformer.MakeResult>(std::move(result)); } @@ -25176,7 +24827,7 @@ void PEGTransformerFactory::InitializeUpdateStatementTrampoline(PEGTransformer & if (from_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("FromClause"), from_clause_opt.GetResult()}, 3); } - process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 2); + process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 2); process.PushChild({transformer.GetRule("UpdateTarget"), list_pr.GetChild(2)}, 1); auto &with_clause_opt = list_pr.GetChild(0).Cast(); if (with_clause_opt.HasResult()) { @@ -25192,7 +24843,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo with_clause = process.TakeResult(0); } auto update_target = process.TakeResult>(1); - auto update_set_clause_list = process.TakeResult>(2); + auto update_set_clause = process.TakeResult>(2); optional> from_clause {}; if (process.child_results[3]) { from_clause = process.TakeResult>(3); @@ -25206,7 +24857,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo returning_clause = process.TakeResult>>(5); } auto result = TransformUpdateStatement(transformer, std::move(with_clause), std::move(update_target), - std::move(update_set_clause_list), std::move(from_clause), + std::move(update_set_clause), std::move(from_clause), std::move(where_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -25286,32 +24937,6 @@ PEGTransformerFactory::FinalizeUpdateAliasTrampoline(PEGTransformer &transformer return transformer.MakeResult(result); } -void PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = list_items.size(); - process.ReserveChildSlots(1 + dynamic_child_count - 1); - for (idx_t i = list_items.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("UpdateSetClause"), list_items[child_idx].get()}, 0 + child_idx); - } -} - -arena_ptr -PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = dynamic_list_items.size(); - vector> update_set_clause; - for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - update_set_clause.push_back(process.TakeResult>(i)); - } - auto result = TransformUpdateSetClauseList(transformer, std::move(update_set_clause)); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -25354,6 +24979,32 @@ PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline(PEGTransformer &transfor return transformer.MakeResult>(std::move(result)); } +void PEGTransformerFactory::InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = list_items.size(); + process.ReserveChildSlots(1 + dynamic_child_count - 1); + for (idx_t i = list_items.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("UpdateSetElement"), list_items[child_idx].get()}, 0 + child_idx); + } +} + +arena_ptr +PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = dynamic_list_items.size(); + vector>> update_set_element; + for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { + update_set_element.push_back(process.TakeResult>>(i)); + } + auto result = TransformUpdateSetElementList(transformer, std::move(update_set_element)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -25368,7 +25019,7 @@ PEGTransformerFactory::FinalizeUpdateSetElementTrampoline(PEGTransformer &transf auto update_set_column_target = process.TakeResult(0); auto expression = process.TakeResult>(1); auto result = TransformUpdateSetElement(transformer, update_set_column_target, std::move(expression)); - return transformer.MakeResult>(std::move(result)); + return transformer.MakeResult>>(std::move(result)); } void PEGTransformerFactory::InitializeUpdateSetColumnTargetTrampoline(PEGTransformer &transformer, @@ -25450,91 +25101,29 @@ arena_ptr PEGTransformerFactory::FinalizeUseTargetTrampoli void PEGTransformerFactory::InitializeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); - process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process && - (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || - choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || - choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { - return; - } - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); - } - process.PushChild({*child_rule, choice_result}, 0); + process.ReserveChildSlots(0); } arena_ptr PEGTransformerFactory::FinalizeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - string child; - if (process.child_results[0]) { - child = process.TakeResult(0); - } else { - auto &list_pr = process.parse_result.Cast(); - auto &choice_result = list_pr.Child(0).GetResult(); - if (choice_result.type == ParseResultType::IDENTIFIER) { - child = choice_result.Cast().identifier.GetIdentifierName(); - } else if (choice_result.type == ParseResultType::KEYWORD) { - child = choice_result.Cast().keyword; - } else if (choice_result.type == ParseResultType::STRING) { - child = choice_result.Cast().result; - } else if (choice_result.type == ParseResultType::OPERATOR) { - child = choice_result.Cast().operator_token; - } else { - child = TransformIdentifierOrKeyword(transformer, choice_result); - } - } - auto result = TransformSchemaNameAsUseTarget(transformer, child); + auto &list_pr = process.parse_result.Cast(); + auto schema_name = list_pr.GetChild(0).Cast().identifier; + auto result = TransformSchemaNameAsUseTarget(transformer, schema_name); return transformer.MakeResult(result); } void PEGTransformerFactory::InitializeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); - process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process && - (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || - choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || - choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { - return; - } - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); - } - process.PushChild({*child_rule, choice_result}, 0); + process.ReserveChildSlots(0); } arena_ptr PEGTransformerFactory::FinalizeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - string child; - if (process.child_results[0]) { - child = process.TakeResult(0); - } else { - auto &list_pr = process.parse_result.Cast(); - auto &choice_result = list_pr.Child(0).GetResult(); - if (choice_result.type == ParseResultType::IDENTIFIER) { - child = choice_result.Cast().identifier.GetIdentifierName(); - } else if (choice_result.type == ParseResultType::KEYWORD) { - child = choice_result.Cast().keyword; - } else if (choice_result.type == ParseResultType::STRING) { - child = choice_result.Cast().result; - } else if (choice_result.type == ParseResultType::OPERATOR) { - child = choice_result.Cast().operator_token; - } else { - child = TransformIdentifierOrKeyword(transformer, choice_result); - } - } - auto result = TransformCatalogNameAsUseTarget(transformer, child); + auto &list_pr = process.parse_result.Cast(); + auto catalog_name = list_pr.GetChild(0).Cast().identifier; + auto result = TransformCatalogNameAsUseTarget(transformer, catalog_name); return transformer.MakeResult(result); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp index 74a0b8c0e..3bc06b644 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp @@ -44,8 +44,7 @@ static void SetGenericCopyOptionExpression(GenericCopyOption ©_option, uniqu ExpressionClassToString(child->GetExpressionClass())); } } - } else if (expression->GetExpressionType() == ExpressionType::FUNCTION || - expression->GetExpressionType() == ExpressionType::VALUE_PARAMETER) { + } else if (expression->GetExpressionType() == ExpressionType::FUNCTION) { copy_option.expression = std::move(expression); } else if (expression->GetExpressionType() == ExpressionType::STAR) { copy_option.children.push_back(Value("*")); diff --git a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp index 0f152888e..4bd7f30df 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp @@ -10,7 +10,7 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, const optional> &insert_column_list, - bool has_overriding_clause, InsertValues insert_values, optional> on_conflict_clause, + InsertValues insert_values, optional> on_conflict_clause, optional>> returning_clause) { auto result = make_uniq(); auto &node = *result->node; @@ -26,10 +26,6 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( throw ParserException( "You can not provide both a column list and DEFAULT VALUES, please remove one of the two"); } - if (has_overriding_clause && insert_values.default_values) { - throw ParserException("OVERRIDING SYSTEM|USER VALUE can not be combined with DEFAULT VALUES, " - "please remove one of the two"); - } if (insert_values.default_values) { node.default_values = true; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp index 0c168a38d..0424b6a21 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp @@ -85,15 +85,12 @@ arena_ptr PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &pivot_columns = list_pr.Child(2); - auto &pivot_aggregates = list_pr.Child(3); - auto &pivot_group = list_pr.Child(4); - if (!pivot_columns.HasResult() && !pivot_aggregates.HasResult() && !pivot_group.HasResult()) { - throw ParserException("PIVOT requires an ON, USING or GROUP BY clause"); - } if (!process.child_results[4]) { bool has_parameters = transformer.ParamCount() > process.manual_state; process.SetChildResult(4, transformer.MakeResult(has_parameters)); + auto &pivot_columns = list_pr.Child(2); + auto &pivot_aggregates = list_pr.Child(3); + auto &pivot_group = list_pr.Child(4); bool pushed_child = false; if (pivot_group.HasResult()) { process.PushChild({pivot_group.GetResult()}, 3); @@ -113,6 +110,8 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor } auto source = process.TakeResult>(0); auto has_parameters = process.TakeResult(4); + auto &pivot_columns = list_pr.Child(2); + auto &pivot_group = list_pr.Child(4); auto select_node = make_uniq(); if (!pivot_columns.HasResult()) { select_node->from_table = std::move(source); @@ -161,6 +160,7 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor select_node->select_list.push_back(make_uniq()); auto pivot_ref = make_uniq(); pivot_ref->source = std::move(source); + auto &pivot_aggregates = list_pr.Child(3); if (pivot_aggregates.HasResult()) { pivot_ref->aggregates = process.TakeResult>>(2); } else { diff --git a/src/duckdb/src/parser/peg/transformer/transform_select.cpp b/src/duckdb/src/parser/peg/transformer/transform_select.cpp index 31a17c55c..b06a869b8 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_select.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_select.cpp @@ -317,10 +317,9 @@ PEGTransformerFactory::FinalizeSimpleSelectTrampoline(PEGTransformer &transforme } FunctionArgument PEGTransformerFactory::TransformNamedFunctionArgument(PEGTransformer &transformer, - const Identifier &type_func_name, - unique_ptr expression) { - expression->SetAlias(type_func_name); - return FunctionArgument(type_func_name, std::move(expression)); + MacroParameter named_parameter) { + named_parameter.expression->SetAlias(named_parameter.name); + return FunctionArgument(named_parameter.name, std::move(named_parameter.expression)); } FunctionArgument PEGTransformerFactory::TransformPositionalFunctionArgument(PEGTransformer &transformer, @@ -770,10 +769,7 @@ JoinType PEGTransformerFactory::TransformInnerJoin(PEGTransformer &transformer) unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, vector table_function_arguments, const optional &with_ordinality, - const optional &table_alias, optional> sample_clause) { - if (lateral.value_or(false) && sample_clause) { - throw ParserException("TABLESAMPLE cannot be applied to a LATERAL table function"); - } + const optional &table_alias) { auto result = make_uniq(); result->with_ordinality = @@ -783,9 +779,6 @@ unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( result->alias = table_alias->name; result->column_name_alias = table_alias->column_name_alias; } - if (sample_clause) { - result->sample = std::move(*sample_clause); - } return std::move(result); } @@ -931,7 +924,6 @@ vector PEGTransformerFactory::GroupByExpressionUnfolding(GroupByExp for (auto &child_expr : group_by_expr.children) { auto child_sets = GroupByExpressionUnfolding(child_expr, map, result); result_sets.insert(result_sets.end(), child_sets.begin(), child_sets.end()); - CheckGroupingSetMax(result_sets.size()); } } else if (group_by_expr.type == GroupByExpressionInfoType::CUBE || group_by_expr.type == GroupByExpressionInfoType::ROLLUP) { @@ -1356,14 +1348,10 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme const optional &table_alias_colon, const optional &lateral, unique_ptr subquery_reference, - const optional &table_alias, - optional> sample_clause) { + const optional &table_alias) { if (table_alias_colon && table_alias) { throw ParserException("Table reference %s cannot have two aliases", subquery_reference->ToString()); } - if (lateral.value_or(false) && sample_clause) { - throw ParserException("TABLESAMPLE cannot be applied to a LATERAL subquery"); - } if (table_alias_colon) { subquery_reference->alias = *table_alias_colon; } @@ -1371,9 +1359,6 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme subquery_reference->alias = table_alias->name; subquery_reference->column_name_alias = table_alias->column_name_alias; } - if (sample_clause) { - subquery_reference->sample = std::move(*sample_clause); - } return subquery_reference; } @@ -1410,8 +1395,7 @@ Identifier PEGTransformerFactory::TransformTableAliasColon(PEGTransformer &trans unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias, - optional> sample_clause) { + const optional &table_alias) { if (table_alias_colon && table_alias) { throw ParserException("Table reference cannot have two aliases"); } @@ -1423,9 +1407,6 @@ unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &t subquery_ref->alias = table_alias->name; subquery_ref->column_name_alias = table_alias->column_name_alias; } - if (sample_clause) { - subquery_ref->sample = std::move(*sample_clause); - } return std::move(subquery_ref); } @@ -1676,11 +1657,6 @@ unique_ptr PEGTransformerFactory::TransformSampleClause(PEGTransf return sample_entry; } -unique_ptr PEGTransformerFactory::TransformSelectSampleClause(PEGTransformer &transformer, - unique_ptr sample_entry) { - return sample_entry; -} - vector> PEGTransformerFactory::TransformWindowClause(PEGTransformer &transformer, vector> window_definition) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_update.cpp b/src/duckdb/src/parser/peg/transformer/transform_update.cpp index 439621d07..d8c447127 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_update.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_update.cpp @@ -81,26 +81,20 @@ unique_ptr PEGTransformerFactory::TransformUpdateSetTuple(PEGTran return result; } -unique_ptr -PEGTransformerFactory::TransformUpdateSetClauseList(PEGTransformer &transformer, - vector> update_set_info) { +unique_ptr PEGTransformerFactory::TransformUpdateSetElementList( + PEGTransformer &transformer, vector>> update_set_element) { auto result = make_uniq(); - for (auto &info : update_set_info) { - for (idx_t i = 0; i < info->columns.size(); i++) { - result->columns.emplace_back(std::move(info->columns[i])); - result->expressions.push_back(std::move(info->expressions[i])); - } + for (auto &element : update_set_element) { + result->columns.emplace_back(std::move(element.first)); + result->expressions.push_back(std::move(element.second)); } return result; } -unique_ptr PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, - const string &update_set_column_target, - unique_ptr expression) { - auto result = make_uniq(); - result->columns.emplace_back(update_set_column_target); - result->expressions.push_back(std::move(expression)); - return result; +pair> +PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, + unique_ptr expression) { + return {update_set_column_target, std::move(expression)}; } string PEGTransformerFactory::TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, diff --git a/src/duckdb/src/parser/peg/transformer/transform_use.cpp b/src/duckdb/src/parser/peg/transformer/transform_use.cpp index 67b487a30..e702245ac 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_use.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_use.cpp @@ -17,17 +17,19 @@ unique_ptr PEGTransformerFactory::TransformUseStatement(PEGTransfo return make_uniq("schema", std::move(value_expr), SetScope::AUTOMATIC); } -// UseTarget <- UseTargetCatalogSchema / SchemaNameAsUseTarget / CatalogNameAsUseTarget -// SchemaNameAsUseTarget <- SchemaName / StringLiteral +// UseTarget <- UseTargetCatalogSchema / SchemaName / CatalogName QualifiedName PEGTransformerFactory::TransformSchemaNameAsUseTarget(PEGTransformer &transformer, - const string &schema_name) { - return QualifiedName(Identifier(schema_name)); + const Identifier &schema_name) { + QualifiedName result; + result = QualifiedName(schema_name); + return result; } -// CatalogNameAsUseTarget <- CatalogName / StringLiteral QualifiedName PEGTransformerFactory::TransformCatalogNameAsUseTarget(PEGTransformer &transformer, - const string &catalog_name) { - return QualifiedName(Identifier(catalog_name)); + const Identifier &catalog_name) { + QualifiedName result; + result = QualifiedName(catalog_name); + return result; } // UseTargetCatalogSchema <- CatalogName '.' ReservedSchemaName DotIdentifier* diff --git a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp index 22b371bbb..eca1e184c 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp @@ -58,8 +58,6 @@ VacuumOptions PEGTransformerFactory::TransformVacuumParensOptions(PEGTransformer } if (StringUtil::CIEquals(option, "analyze")) { options.analyze = true; - } else { - throw ParserException("unrecognized VACUUM option \"%s\"", option); } } return options; diff --git a/src/duckdb/src/planner/bind_context.cpp b/src/duckdb/src/planner/bind_context.cpp index bf8423944..a2d68d4d2 100644 --- a/src/duckdb/src/planner/bind_context.cpp +++ b/src/duckdb/src/planner/bind_context.cpp @@ -557,7 +557,7 @@ string CandidateColumnMessage(const vector &candidate_columns, const void BindContext::GenerateAllColumnExpressions(StarExpression &expr, vector> &new_select_list) { - if (bindings_list.empty() && expr.RelationName().empty()) { + if (bindings_list.empty()) { throw BinderException("* expression without FROM clause!"); } StarBindState star_state(new_select_list); @@ -625,21 +625,6 @@ void BindContext::GenerateAllColumnExpressions(StarExpression &expr, if (!binding) { binding = GetMatchingBinding(expr.RelationName(), expr); if (!binding) { - // Expand the nearest enclosing match into column references. Expression binding then - // resolves their correlation depth, just as it does for explicit qualified columns. - auto &scopes = binder.GetEnclosingScopes(); - for (idx_t i = scopes.size(); i > 0; i--) { - auto &scope_context = scopes[i - 1].get().GetBinder().bind_context; - ErrorData scope_error; - if (scope_context.GetBinding(expr.RelationName(), scope_error) || - scope_context.GetMatchingBinding(expr.RelationName(), expr)) { - scope_context.GenerateAllColumnExpressions(expr, new_select_list); - return; - } - } - if (bindings_list.empty()) { - throw BinderException("* expression without FROM clause!"); - } error.Throw(); } is_struct_ref = true; diff --git a/src/duckdb/src/planner/binder.cpp b/src/duckdb/src/planner/binder.cpp index 30aad5b4c..bedc1860c 100644 --- a/src/duckdb/src/planner/binder.cpp +++ b/src/duckdb/src/planner/binder.cpp @@ -146,11 +146,6 @@ BoundStatement Binder::Bind(QueryNode &node) { } BoundStatement Binder::Bind(TableRef &ref) { - for (auto &column_alias : ref.column_name_alias) { - if (column_alias.empty()) { - throw BinderException(ref, "column alias cannot be empty"); - } - } BoundStatement result; switch (ref.type) { case TableReferenceType::BASE_TABLE: diff --git a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp index 89bf6a80b..9dfe3d128 100644 --- a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp @@ -127,21 +127,6 @@ BindResult BaseSelectBinder::BindAggregate(FunctionExpression &aggr, AggregateFu if (inside_aggregate) { throw BinderException(aggr, "aggregate function calls cannot be nested"); } - // an aggregate is computed over the rows of the query, not over the elements an enclosing lambda is applied to - - // so the parameters of the enclosing lambdas can not be referenced in the aggregate - struct InaccessibleLambdaBindings { - InaccessibleLambdaBindings(ExpressionBinder &binder_p, idx_t count) - : binder(binder_p), previous_count(binder.inaccessible_lambda_binding_count) { - binder.inaccessible_lambda_binding_count = count; - } - ~InaccessibleLambdaBindings() { - binder.inaccessible_lambda_binding_count = previous_count; - } - ExpressionBinder &binder; - idx_t previous_count; - }; - InaccessibleLambdaBindings inaccessible_lambdas(*this, lambda_bindings ? lambda_bindings->size() : 0); - // first bind the child of the aggregate expression (if any) this->bound_aggregate = true; unique_ptr bound_filter; diff --git a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp index 6c57f4cd6..23945d2a6 100644 --- a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp @@ -60,9 +60,6 @@ void ExpressionBinder::QualifyColumnNames(ExpressionBinder &expression_binder, u BindResult ExpressionBinder::BindExpression(LambdaRefExpression &lambda_ref, idx_t depth) { D_ASSERT(lambda_bindings && lambda_ref.LambdaIndex() < lambda_bindings->size()); - if (lambda_ref.LambdaIndex() < inaccessible_lambda_binding_count) { - throw BinderException(lambda_ref, "aggregate functions cannot reference lambda parameters"); - } return (*lambda_bindings)[lambda_ref.LambdaIndex()].Bind(lambda_ref, depth); } diff --git a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp index 37c6c38c1..79bd7e184 100644 --- a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp @@ -106,25 +106,6 @@ struct ListReduceRebindResult { bool override_has_accumulator_type = false; }; -//! Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen accumulator type -//! when decimals are involved -static void CastDecimalLambdaToAccumulatorType(ClientContext &context, BindResult &bind_lambda_result, - const LogicalType &accumulator_type) { - auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); - const auto return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); - if (return_type == accumulator_type || - (!TypeContainsDecimal(accumulator_type) && !TypeContainsDecimal(return_type))) { - return; - } - auto cast_expr = BoundCastExpression::AddCastToType(context, std::move(rebound_lambda_expr.LambdaExprMutable()), - accumulator_type); - if (!cast_expr) { - throw BinderException("Could not cast lambda return type %s to accumulator type %s", return_type.ToString(), - accumulator_type.ToString()); - } - rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); -} - ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t depth, const vector &function_child_types, bind_lambda_function_t bind_lambda_function, @@ -177,7 +158,22 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t bind_lambda_result.error.Throw(); } - CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); + auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); + // Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen + // accumulator type when decimals are involved. + if (TypeContainsDecimal(accumulator_type) || + TypeContainsDecimal(rebound_lambda_expr.LambdaExpr()->GetReturnType())) { + if (rebound_lambda_expr.LambdaExpr()->GetReturnType() != accumulator_type) { + const auto old_return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); + auto cast_expr = BoundCastExpression::AddCastToType( + context, std::move(rebound_lambda_expr.LambdaExprMutable()), accumulator_type); + if (!cast_expr) { + throw BinderException("Could not cast lambda return type %s to accumulator type %s", + old_return_type.ToString(), accumulator_type.ToString()); + } + rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); + } + } result.did_rebind = true; result.capture_child_types = std::move(rebind_child_types); } else if (accumulator_type != list_child_type) { @@ -192,7 +188,6 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t if (bind_lambda_result.HasError()) { bind_lambda_result.error.Throw(); } - CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); result.did_rebind = true; result.override_accumulator_type_storage = accumulator_type; result.override_has_accumulator_type = true; @@ -466,8 +461,7 @@ BindResult ExpressionBinder::BindLambdaFunction(FunctionExpression &function, Sc auto &lambda_expr = args[lambda_expr_idx].GetExpressionMutable()->Cast(); unique_ptr lambda_expr_copy; - // the aliases of list_reduce (e.g. reduce) are separate catalog entries - const bool is_list_reduce = func.name == "list_reduce" || func.alias_of == "list_reduce"; + const bool is_list_reduce = func.name == "list_reduce"; if (is_list_reduce) { // We have to make this copy before binding in case we have to double-bind lambda_expr_copy = lambda_expr.Copy(); diff --git a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp index 5953ec8ba..ca14f9277 100644 --- a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp @@ -7,7 +7,6 @@ #include "duckdb/common/exception/binder_exception.hpp" #include "duckdb/common/unique_ptr.hpp" #include "duckdb/function/scalar_macro_function.hpp" -#include "duckdb/main/client_context.hpp" #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/parser/expression/subquery_expression.hpp" @@ -205,8 +204,6 @@ void ExpressionBinder::UnfoldMacroExpression(FunctionExpression &function, Scala BindResult ExpressionBinder::BindMacro(FunctionExpression &function, ScalarMacroCatalogEntry ¯o_func, idx_t depth, unique_ptr &expr) { auto stack_checker = StackCheck(*expr, 3); - // expanding nested macros can take a long time, allow the query to be interrupted - context.InterruptCheck(); // unfold the macro expression UnfoldMacroExpression(function, macro_func, expr, depth); diff --git a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp index 12c63762b..634332130 100644 --- a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp @@ -80,7 +80,6 @@ BindResult ExpressionBinder::BindExpression(TypeExpression &type_expr, idx_t dep for (auto ¶m : unbound_parameters) { // Otherwise, try to fold it to a constant value ConstantBinder binder(this->binder, context, StringUtil::Format("Type parameter for type '%s'", type_name)); - binder.ContinueStackDepth(*this); auto expr = param->Copy(); auto bound_expr = binder.Bind(expr); diff --git a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp index d44038f98..77a0e953d 100644 --- a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp @@ -226,9 +226,7 @@ BindResult BaseSelectBinder::BindWindowExpression(WindowExpression &window, idx_ // bind the aggregate FunctionBinder function_binder(binder); - auto window_bound_aggregate = function_binder.BindAggregateFunction( - func, std::move(arguments), error, nullptr, - window.Distinct() ? AggregateType::DISTINCT : AggregateType::NON_DISTINCT); + auto window_bound_aggregate = function_binder.BindAggregateFunction(func, std::move(arguments), error, nullptr); // No function found, throw an error if (!window_bound_aggregate) { error.AddQueryLocation(window); diff --git a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp index d798f33ae..c0709ba68 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp @@ -142,9 +142,6 @@ void CTEBindState::Bind(CTEBinding &binding) { // names are picked from the LHS, unless aliases are explicitly specified names = query.names; for (idx_t i = 0; i < aliases.size() && i < names.size(); i++) { - if (aliases[i].empty()) { - throw BinderException("column alias cannot be empty"); - } names[i] = aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp index 9ce269546..8bf83d31b 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp @@ -42,9 +42,6 @@ BoundStatement Binder::BindNode(RecursiveCTENode &statement) { // names are picked from the LHS, unless aliases are explicitly specified result.names = left.names; for (idx_t i = 0; i < statement.aliases.size() && i < result.names.size(); i++) { - if (statement.aliases[i].empty()) { - throw BinderException("column alias cannot be empty"); - } result.names[i] = statement.aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp index 49843c812..27ce506ff 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp @@ -725,6 +725,9 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from } bind_state.AddRegularColumn(); + if (can_group_by_all && is_window) { + throw BinderException("Cannot group on a window clause"); + } if (can_group_by_all && result.unnests.SelectList().size() > unnest_count) { throw BinderException("Cannot group on an UNNEST or UNLIST clause"); } @@ -732,10 +735,6 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from if (select_binder.BoundAggregates()) { throw BinderException("Cannot mix aggregates with non-aggregated columns!"); } - if (is_window) { - // a window over aggregates (e.g. sum(sum(x)) OVER ()) is fine, a window over a group is not - throw BinderException("Cannot group on a window clause"); - } // we are forcing aggregates, and the node has columns bound // this entry becomes a group group_by_all_indexes.push_back(i); diff --git a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp index 5b4e01ca8..52387f016 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp @@ -5,7 +5,6 @@ #include "duckdb/planner/expression_binder.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/common/limits.hpp" -#include "duckdb/main/client_context.hpp" #include "duckdb/main/config.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/parser/expression/comparison_expression.hpp" @@ -19,8 +18,6 @@ namespace duckdb { unique_ptr Binder::BindTableMacro(FunctionExpression &function, TableMacroCatalogEntry ¯o_func, idx_t depth) { - // expanding nested macros can take a long time, allow the query to be interrupted - context.InterruptCheck(); // validate the arguments and separate positional and default arguments vector> positional_arguments; InsertionOrderPreservingMap, Identifier, identifier_map_t> named_arguments; diff --git a/src/duckdb/src/planner/binder/statement/bind_attach.cpp b/src/duckdb/src/planner/binder/statement/bind_attach.cpp index 81718c46e..4a19a052b 100644 --- a/src/duckdb/src/planner/binder/statement/bind_attach.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_attach.cpp @@ -30,7 +30,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { if (stmt.info->parsed_path) { TableFunctionBinder path_binder(*this, context, "Attach", "Attach path"); auto bound_path = path_binder.Bind(stmt.info->parsed_path); - auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path, true); + auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path); if (path_val.IsNull()) { throw BinderException("ATTACH path expression must not evaluate to NULL"); } @@ -43,7 +43,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { unordered_map kv_options; for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for ATTACH option \"" + entry.first + "\""); } diff --git a/src/duckdb/src/planner/binder/statement/bind_connect.cpp b/src/duckdb/src/planner/binder/statement/bind_connect.cpp index 23823cbba..59f485d09 100644 --- a/src/duckdb/src/planner/binder/statement/bind_connect.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_connect.cpp @@ -19,7 +19,7 @@ BoundStatement Binder::Bind(ConnectStatement &stmt) { TableFunctionBinder option_binder(*this, context, "Connect", "Connect parameter"); for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for CONNECT option \"" + entry.first + "\""); diff --git a/src/duckdb/src/planner/binder/statement/bind_copy.cpp b/src/duckdb/src/planner/binder/statement/bind_copy.cpp index 6a8d67aee..756aa4d9e 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy.cpp @@ -782,7 +782,7 @@ void Binder::BindCopyFormat(CopyInfo &info) { if (info.file_path_expression) { auto inputs = BindCopyOption(context, *this, "filename", info.file_path_expression, conversion_policy); if (inputs.size() != 1 || inputs[0].type().id() != LogicalTypeId::VARCHAR) { - throw BinderException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); + throw InternalException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); } if (!info.file_path.empty()) { throw InternalException("Both a file path and a file path expression were provided for COPY - only one of " diff --git a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp index af07d6889..7596bfa3c 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp @@ -51,13 +51,14 @@ unique_ptr Binder::BindCopyDatabaseSchema(Catalog &from_databas unique_ptr Binder::BindCopyDatabaseData(Catalog &source_catalog, const Identifier &target_database_name) { - auto entries = PhysicalExport::GetNaiveExportOrder(context, source_catalog); + auto source_schemas = source_catalog.GetSchemas(context); + + // We can just use ExtractEntries here because the order doesn't matter + ExportEntries entries; + PhysicalExport::ExtractEntries(context, source_schemas, entries); vector> insert_nodes; - for (auto &table_ref : entries) { - if (table_ref.get().type != CatalogType::TABLE_ENTRY) { - continue; - } + for (auto &table_ref : entries.tables) { auto &table = table_ref.get().Cast(); // generate the insert statement InsertStatement insert_stmt; diff --git a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp index 13c4247b3..25be3c9ac 100644 --- a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp @@ -43,11 +43,6 @@ static void VerifyCompressionType(ClientContext &context, optional_ptrCast(); for (auto &col : base.columns.Logical()) { auto compression_type = col.CompressionType(); - if (col.Generated() && compression_type != CompressionType::COMPRESSION_AUTO) { - throw BinderException("Can't set a compression type on generated column %s, generated columns are " - "not stored", - col.Name()); - } auto compression_availability_result = CompressionTypeIsAvailable(compression_type, storage_manager); if (!compression_availability_result.IsAvailable()) { if (compression_availability_result.IsDeprecated()) { diff --git a/src/duckdb/src/planner/binder/statement/bind_export.cpp b/src/duckdb/src/planner/binder/statement/bind_export.cpp index 7869d95b1..81f22f66f 100644 --- a/src/duckdb/src/planner/binder/statement/bind_export.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_export.cpp @@ -46,11 +46,10 @@ string SanitizeExportIdentifier(const Identifier &str) { return result; } -bool ReferencedTableIsOrdered(const TableCatalogEntry &referencing_table, const Identifier &referenced_table, - catalog_entry_vector_t &ordered) { +bool ReferencedTableIsOrdered(const Identifier &referenced_table, catalog_entry_vector_t &ordered) { for (auto &entry : ordered) { auto &table_entry = entry.get().Cast(); - if (&table_entry.ParentSchema() == &referencing_table.ParentSchema() && table_entry.name == referenced_table) { + if (table_entry.name == referenced_table) { // The referenced table is already ordered return true; } @@ -80,7 +79,7 @@ void ScanForeignKeyTable(catalog_entry_vector_t &ordered, catalog_entry_vector_t // This table references a table, don't move it yet move_to_ordered = false; break; - } else if (!ReferencedTableIsOrdered(table_entry, fk.info.table, ordered)) { + } else if (!ReferencedTableIsOrdered(fk.info.table, ordered)) { // The table that it references isn't ordered yet move_to_ordered = false; break; diff --git a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp index 0bbee6fd6..bb986b527 100644 --- a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp @@ -15,7 +15,7 @@ namespace duckdb { Value Binder::BindExternalResourceValue(unique_ptr &expr) { TableFunctionBinder binder(*this, context, "External resource", "External resource parameter"); auto bound = binder.Bind(expr); - return ExpressionExecutor::EvaluateScalar(context, *bound, true); + return ExpressionExecutor::EvaluateScalar(context, *bound); } void Binder::BindExternalResourceParams(case_insensitive_map_t> &parsed_params, diff --git a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp index 20b47314c..c053c9189 100644 --- a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp @@ -186,39 +186,6 @@ void RewriteMergeBindings(unique_ptr &expr, const vector FindMergeJoin(LogicalOperator &op, LogicalOperator &source, - LogicalOperator &target, idx_t &source_idx) { - if (op.children.size() == 2) { - for (idx_t child_idx = 0; child_idx < 2; child_idx++) { - if (ContainsOperator(*op.children[child_idx], source) && - ContainsOperator(*op.children[1 - child_idx], target)) { - source_idx = child_idx; - return op; - } - } - } - for (auto &child : op.children) { - auto result = FindMergeJoin(*child, source, target, source_idx); - if (result) { - return result; - } - } - return nullptr; -} - void RewriteMergeBindings(LogicalOperator &op, const vector &source_bindings, TableIndex new_table_index) { LogicalOperatorVisitor::EnumerateExpressions( @@ -350,7 +317,6 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { } else { join.type = JoinType::INNER; } - auto &source_op = *source_binding.plan; join.left = make_uniq(std::move(source_binding), std::move(source_binder)); join.right = make_uniq(std::move(bound_table), std::move(target_binder)); if (node.join_condition) { @@ -361,14 +327,16 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { auto bound_join_node = Bind(join); auto root = std::move(bound_join_node.plan); - idx_t source_idx = 0; - auto merge_join = FindMergeJoin(*root, source_op, get, source_idx); - if (!merge_join) { - throw NotImplementedException("Expected a join after binding a join operator - but got a %s", root->type); + auto join_ref = reference(*root); + while (join_ref.get().children.size() == 1) { + join_ref = *join_ref.get().children[0]; + } + if (join_ref.get().children.size() != 2) { + throw NotImplementedException("Expected a join after binding a join operator - but got a %s", + join_ref.get().type); } - auto join_ref = reference(*merge_join); // CreatePlan can turn a RIGHT join into a LEFT join - in that case the children are reversed - bool inverted = source_idx == 1; + bool inverted = join.type == JoinType::RIGHT && join_ref.get().Cast().join_type == JoinType::LEFT; auto &source = join_ref.get().children[inverted ? 1 : 0]; // bind WHEN_MATCHED merge actions (can contain references to both source and target) diff --git a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp index 46ecedf6b..5fef48a72 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp @@ -62,8 +62,8 @@ BoundStatement Binder::BindWithReplacementScan(ClientContext &context, BaseTable // Then the database-wide ones, including the built-in file scans if (!replacement_function) { - for (const auto &scan : DBConfig::GetConfig(context).replacement_scans.Snapshot()) { - if (auto result = scan->function(context, input, scan->data.get())) { + for (const auto &scan : DBConfig::GetConfig(context).replacement_scans) { + if (auto result = scan.function(context, input, scan.data.get())) { replacement_function = std::move(result); break; } @@ -304,8 +304,6 @@ BoundStatement Binder::Bind(BaseTableRef &ref) { case CatalogType::VIEW_ENTRY: { // the node is a view: get the query that the view represents auto &view_catalog_entry = table_or_view->Cast(); - // expanding nested views can take a long time, allow the query to be interrupted - context.InterruptCheck(); // We need to use a new binder for the view that doesn't reference any CTEs // defined for this binder so there are no collisions between the CTEs defined // for the view and for the current query diff --git a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp index 453117558..031e644aa 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp @@ -271,35 +271,11 @@ static unique_ptr PivotInitialAggregate(ClientContext &context, Pivo return subquery_stage1; } -//! The key of a pivot value: the length of the value, followed by the value - or "N" for NULL values. -//! Prefixing the length makes the concatenated keys of multiple pivot values unambiguous. -static string PivotValueKey(const Value &value) { - if (value.IsNull()) { - return "N"; - } - auto str = value.ToString(); - return to_string(str.size()) + ":" + str; -} - unique_ptr ConstructPivotExpression(unique_ptr pivot_expr) { - // coalesce(strlen(pivot::VARCHAR)::VARCHAR || ':' || pivot::VARCHAR, 'N'), see PivotValueKey auto cast = make_uniq(LogicalType::VARCHAR, std::move(pivot_expr)); - vector> strlen_children; - strlen_children.push_back(cast->Copy()); - auto length = make_uniq(LogicalType::VARCHAR, - make_uniq("strlen", std::move(strlen_children))); - vector> prefix_children; - prefix_children.push_back(std::move(length)); - prefix_children.push_back(ConstantExpression::String(":")); - auto prefix = make_uniq("||", std::move(prefix_children)); - vector> key_children; - key_children.push_back(std::move(prefix)); - key_children.push_back(std::move(cast)); - auto key = make_uniq("||", std::move(key_children)); - vector> coalesce_children; - coalesce_children.push_back(std::move(key)); - coalesce_children.push_back(ConstantExpression::String("N")); + coalesce_children.push_back(std::move(cast)); + coalesce_children.push_back(ConstantExpression::String("NULL")); auto coalesce = make_uniq(ExpressionType::OPERATOR_COALESCE, std::move(coalesce_children)); return std::move(coalesce); } @@ -471,9 +447,8 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { } if (unique_names.size() != aggregates.size()) { - // identical aggregates with different names are bound to a single aggregate - throw BinderException("PIVOT: identical aggregates must have the same name - use a single aggregate, or " - "make the aggregates differ"); + throw InternalException("Pivot aggregate mismatch: %llu unique names, %llu aggregates", unique_names.size(), + aggregates.size()); } identifier_map_t name_to_position; @@ -514,11 +489,13 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { auto name = pivot_value.name + aggr_name; string pivot_str; - for (idx_t value_idx = 0; value_idx < pivot_value.values.size(); value_idx++) { - if (value_idx > 0) { - pivot_str += "_"; + for (auto &value : pivot_value.values) { + auto str = value.ToString(); + if (pivot_str.empty()) { + pivot_str = std::move(str); + } else { + pivot_str += "_" + str; } - pivot_str += PivotValueKey(pivot_value.values[value_idx]); } result.bound_pivot.pivot_values.push_back(std::move(pivot_str)); names.push_back(Identifier(std::move(name))); diff --git a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp index de12d298e..697da9c54 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp @@ -275,9 +275,6 @@ static void WarnShowDescribesQuery(ClientContext &context) { BoundStatement Binder::BindShow(ShowRef &ref) { BoundStatement result; if (ref.GetTableName().empty()) { - if (!ref.query) { - throw BinderException("SHOW requires a setting name, a table name or a query"); - } // "SHOW (SELECT ...)": describe the columns of the query. Deprecated in favor of DESCRIBE, and not affected // by show_behavior (a parenthesized query is neither a setting nor a named table). WarnShowDescribesQuery(context); diff --git a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp index 0332bf655..30f39a605 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp @@ -36,15 +36,6 @@ static TableFunctionBindType GetTableFunctionBindType(TableFunctionCatalogEntry } } if (all_scalar) { - for (idx_t function_idx = 0; function_idx < table_function.functions.Size(); function_idx++) { - const auto &function = *table_function.functions.GetFunctionByOffset(function_idx); - for (auto ¶m : function.GetSignature().GetParameters()) { - if (param.GetType().id() == LogicalTypeId::TABLE) { - throw BinderException("Table function %s requires a subquery as its TABLE parameter", - table_function.name); - } - } - } return TableFunctionBindType::STANDARD_TABLE_FUNCTION; } // if we have non-scalar parameters - we need to look at the function definition to decide how to bind diff --git a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp index 07437da74..f98bd5d44 100644 --- a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp @@ -1,5 +1,4 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" - #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/catalog/catalog_entry/aggregate_function_catalog_entry.hpp" @@ -92,11 +91,6 @@ bool BoundAggregateExpression::Equals(const BaseExpression &other_p) const { if (!Expression::Equals(*children[i], *other.children[i])) { return false; } - // expression equality ignores the return type, but a collation lives in the return type and decides - // which argument values compare equal - two aggregates that disagree on it are not the same aggregate - if (!children[i]->GetReturnType().EqualsIncludingCollation(other.children[i]->GetReturnType())) { - return false; - } } if (state_export_mode != other.state_export_mode) { return false; diff --git a/src/duckdb/src/planner/expression/bound_cast_expression.cpp b/src/duckdb/src/planner/expression/bound_cast_expression.cpp index 26d925cb2..896f4c36b 100644 --- a/src/duckdb/src/planner/expression/bound_cast_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_cast_expression.cpp @@ -109,48 +109,6 @@ unique_ptr BoundCastExpression::AddArrayCastToList(ClientContext &co return BoundCastExpression::AddCastToType(context, std::move(expr), LogicalType::LIST(child_type)); } -//! Whether a cast between nested types is invertible: the structure has to stay the same, and all child casts have to -//! be invertible -static bool NestedCastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { - if (source_type.id() != target_type.id()) { - return false; - } - switch (source_type.id()) { - case LogicalTypeId::LIST: - return BoundCastExpression::CastIsInvertible(ListType::GetChildType(source_type), - ListType::GetChildType(target_type)); - case LogicalTypeId::ARRAY: - if (ArrayType::IsAnySize(source_type) || ArrayType::IsAnySize(target_type) || - ArrayType::GetSize(source_type) != ArrayType::GetSize(target_type)) { - return false; - } - return BoundCastExpression::CastIsInvertible(ArrayType::GetChildType(source_type), - ArrayType::GetChildType(target_type)); - case LogicalTypeId::MAP: - return BoundCastExpression::CastIsInvertible(MapType::KeyType(source_type), MapType::KeyType(target_type)) && - BoundCastExpression::CastIsInvertible(MapType::ValueType(source_type), MapType::ValueType(target_type)); - case LogicalTypeId::STRUCT: - case LogicalTypeId::TUPLE: { - // adding or removing fields is not invertible - auto &source_children = StructType::GetChildTypes(source_type); - auto &target_children = StructType::GetChildTypes(target_type); - if (source_children.size() != target_children.size()) { - return false; - } - for (idx_t child_idx = 0; child_idx < source_children.size(); child_idx++) { - if (source_children[child_idx].first != target_children[child_idx].first || - !BoundCastExpression::CastIsInvertible(source_children[child_idx].second, - target_children[child_idx].second)) { - return false; - } - } - return true; - } - default: - return false; - } -} - bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { D_ASSERT(source_type.IsValid() && target_type.IsValid()); if (source_type.id() == LogicalTypeId::BOOLEAN || target_type.id() == LogicalTypeId::BOOLEAN) { @@ -165,9 +123,6 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (source_type.id() == LogicalTypeId::VARIANT || target_type.id() == LogicalTypeId::VARIANT) { return false; } - if (source_type.IsNested() || target_type.IsNested()) { - return NestedCastIsInvertible(source_type, target_type); - } if (source_type.id() == LogicalTypeId::DECIMAL || target_type.id() == LogicalTypeId::DECIMAL) { uint8_t source_width, target_width; uint8_t source_scale, target_scale; @@ -182,22 +137,7 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (target_scale < source_scale) { return false; } - if (target_type.IsUnsigned()) { - // decimals can be negative - return false; - } - // the target needs to fit all digits before the decimal point - // integral types do not fit all numbers of their decimal width, e.g. SMALLINT (width 5) does not fit 99999 - auto source_digits = source_width - source_scale; - auto target_digits = target_width - target_scale; - if (source_type.id() == LogicalTypeId::HUGEINT || source_type.id() == LogicalTypeId::UHUGEINT) { - // (u)hugeint values can have up to 39 digits - source_digits++; - } - if (target_type.id() != LogicalTypeId::DECIMAL) { - target_digits--; - } - return source_digits <= target_digits; + return true; } switch (source_type.id()) { case LogicalTypeId::TIMESTAMP: @@ -229,12 +169,6 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const break; } break; - case LogicalTypeId::TIME_NS: - // a cast to microseconds rounds the nanoseconds - if (target_type.id() == LogicalTypeId::TIME) { - return false; - } - break; case LogicalTypeId::VARCHAR: case LogicalTypeId::BIT: case LogicalTypeId::TIME_TZ: diff --git a/src/duckdb/src/planner/expression_binder.cpp b/src/duckdb/src/planner/expression_binder.cpp index 7af091211..1c227e951 100644 --- a/src/duckdb/src/planner/expression_binder.cpp +++ b/src/duckdb/src/planner/expression_binder.cpp @@ -32,10 +32,6 @@ void ExpressionBinder::InitializeStackCheck() { } } -void ExpressionBinder::ContinueStackDepth(const ExpressionBinder &parent) { - stack_depth = parent.stack_depth; -} - StackChecker ExpressionBinder::StackCheck(const ParsedExpression &expr, idx_t extra_stack) { D_ASSERT(stack_depth != DConstants::INVALID_INDEX); auto max_expression_depth = Settings::Get(context); diff --git a/src/duckdb/src/planner/expression_binder/check_binder.cpp b/src/duckdb/src/planner/expression_binder/check_binder.cpp index 519488add..be5ceace5 100644 --- a/src/duckdb/src/planner/expression_binder/check_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/check_binder.cpp @@ -35,9 +35,8 @@ BindResult ExpressionBinder::BindQualifiedColumnName(ColumnRefExpression &colref if (colref.ColumnNames()[0] == table_name) { struct_start++; } - // the first name (after the table name) is the column, the remaining names are struct fields - auto result = make_uniq_base(colref.ColumnNames()[struct_start]); - for (idx_t i = struct_start + 1; i < colref.ColumnNames().size(); i++) { + auto result = make_uniq_base(colref.ColumnNames().back()); + for (idx_t i = struct_start; i + 1 < colref.ColumnNames().size(); i++) { result = CreateStructExtract(std::move(result), colref.ColumnNames()[i]); } return BindExpression(result, 0); diff --git a/src/duckdb/src/planner/expression_binder/index_binder.cpp b/src/duckdb/src/planner/expression_binder/index_binder.cpp index 19b38df7f..f5c2341f0 100644 --- a/src/duckdb/src/planner/expression_binder/index_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/index_binder.cpp @@ -1,11 +1,9 @@ #include "duckdb/planner/expression_binder/index_binder.hpp" #include "duckdb/catalog/catalog.hpp" -#include "duckdb/planner/binder.hpp" #include "duckdb/parser/parsed_data/create_index_info.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" -#include "duckdb/planner/binder.hpp" #include "duckdb/planner/column_binding.hpp" #include "duckdb/execution/index/bound_index.hpp" #include "duckdb/execution/index/unbound_index.hpp" @@ -82,34 +80,19 @@ unique_ptr IndexBinder::BindCreateIndex(ClientContext &context, // Add the dependencies. auto &dependencies = create_index_info->dependencies; auto &catalog = table_entry.ParentCatalog(); - SetCatalogLookupCallback([&dependencies, &catalog, &table_entry](CatalogEntry &entry) { + SetCatalogLookupCallback([&dependencies, &catalog](CatalogEntry &entry) { if (&catalog != &entry.ParentCatalog()) { return; } - if (&entry == &table_entry) { - // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table - dependencies.AddDependency(entry, DependencyDependentFlags()); - return; - } - // other entries the index expressions use (e.g. macros) cannot be dropped, replaced or altered while the index - // exists (note that blocking alone is the legacy value that is read as non-blocking for indexes) - dependencies.AddDependency(entry, DependencyDependentFlags().SetBlocking().SetAlterBlocking()); + // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table + dependencies.AddDependency(entry, DependencyDependentFlags()); }); // Bind the index expressions. - // The expressions are bound again when the index is loaded, so they cannot depend on the session or the query - // they are bound in (e.g. now(), getvariable()) - such functions are folded to constants while binding. - auto &properties = binder.GetStatementProperties(); - const auto statement_requires_rebind = properties.always_require_rebind; vector> expressions; for (auto &expr : create_index_info->expressions) { - properties.always_require_rebind = false; expressions.push_back(Bind(expr)); - if (properties.always_require_rebind) { - throw BinderException("Index keys cannot contain expressions with side effects."); - } } - properties.always_require_rebind = statement_requires_rebind; auto &get = plan->Cast(); InitCreateIndexInfo(get, *create_index_info); diff --git a/src/duckdb/src/planner/filter/dynamic_filter.cpp b/src/duckdb/src/planner/filter/dynamic_filter.cpp index 4e235b845..a95f643a1 100644 --- a/src/duckdb/src/planner/filter/dynamic_filter.cpp +++ b/src/duckdb/src/planner/filter/dynamic_filter.cpp @@ -24,13 +24,13 @@ void DynamicFilterData::SetValue(Value val) { if (val.IsNull()) { return; } - annotated_lock_guard l(lock); + lock_guard l(lock); constant = std::move(val); initialized = true; } void DynamicFilterData::Reset() { - annotated_lock_guard l(lock); + lock_guard l(lock); initialized = false; } diff --git a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp index 3a0a18b78..222b68dbc 100644 --- a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp @@ -19,9 +19,6 @@ #include "duckdb/storage/statistics/numeric_stats.hpp" #include "duckdb/storage/buffer_manager.hpp" -#include -#include - namespace duckdb { static constexpr idx_t MAX_NUM_SECTORS = (1ULL << 26); @@ -30,23 +27,11 @@ static constexpr idx_t MIN_NUM_BITS = 512; static constexpr idx_t LOG_SECTOR_SIZE = 6; // a sector is 64 bits, log2(64) = 6 static constexpr idx_t SHIFT_MASK = 0x3F3F3F3F3F3F3F3F; // 6 bits for 64 positions static constexpr idx_t N_BITS = 4; // the number of bits to set per hash -static constexpr idx_t BITS_PER_SECTOR = 1ULL << LOG_SECTOR_SIZE; - -//! A sector is one entry of the bit array, so the two have to agree on how wide it is -static_assert(BITS_PER_SECTOR == sizeof(uint64_t) * 8, "a bloom filter sector must be exactly one array entry"); - -//! A filter is sized from a cardinality estimate, so never let it claim more than this share of the memory budget -static constexpr idx_t MAX_MEMORY_FRACTION = 64; void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { BufferManager &buffer_manager = BufferManager::GetBufferManager(context_p); - // a capped filter stays complete - fewer sectors only raise the false positive rate, and Seal drops a - // filter that ended up too dense to reject anything - const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; - const idx_t budget_bytes = buffer_manager.GetMaxMemory() / MAX_MEMORY_FRACTION; - const idx_t budget_sectors = MaxValue(PreviousPowerOfTwo(budget_bytes / sizeof(uint64_t)), min_sectors); - num_sectors = MinValue(GetNumberOfSectors(number_of_rows), budget_sectors); + num_sectors = GetNumberOfSectors(number_of_rows); bitmask = num_sectors - 1; buf_ = buffer_manager.GetBufferAllocator().Allocate(64 + num_sectors * sizeof(uint64_t)); @@ -55,79 +40,24 @@ void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { std::fill_n(bf, num_sectors, 0); initialized = true; - sealed = false; -} - -void BloomFilter::Fold(idx_t new_num_sectors) { - D_ASSERT(initialized); - D_ASSERT(!sealed); - D_ASSERT(IsPowerOfTwo(new_num_sectors)); - D_ASSERT(new_num_sectors <= num_sectors); - const uint64_t new_bitmask = new_num_sectors - 1; - for (idx_t i = new_num_sectors; i < num_sectors; i++) { - bf[i & new_bitmask] |= bf[i]; - } - num_sectors = new_num_sectors; - bitmask = new_bitmask; } -double BloomFilter::Density() const { +void BloomFilter::Merge(const BloomFilter &other) { D_ASSERT(initialized); - idx_t bits_set = 0; + D_ASSERT(other.initialized); + D_ASSERT(num_sectors == other.num_sectors); + D_ASSERT(bitmask == other.bitmask); for (idx_t i = 0; i < num_sectors; i++) { - bits_set += static_cast(std::bitset(bf[i]).count()); + bf[i] |= other.bf[i]; } - return static_cast(bits_set) / static_cast(num_sectors * BITS_PER_SECTOR); } -//! False positive rate the sizing in GetNumberOfSectors aims for. With m bits, n keys and k probes per key a -//! given bit stays zero with probability (1 - 1/m)^(n*k) ~ e^(-k*n/m), so all k probed bits are set - a false -//! positive - with probability (1 - e^(-k*n/m))^k. Here m/n is MIN_NUM_BITS_PER_KEY and k is N_BITS. -static double TargetFalsePositiveRate() { - const double load = static_cast(N_BITS) / static_cast(MIN_NUM_BITS_PER_KEY); - return std::pow(1.0 - std::exp(-load), static_cast(N_BITS)); -} - -void BloomFilter::Seal(ClientContext &context) { - if (!initialized || sealed) { - sealed = true; - return; - } - const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; - double density = Density(); - // a density of exactly zero means no key was ever inserted, so there is nothing to fold down - if (density > 0.0) { - // each fold ORs two sectors together, so the expected density goes from p to 1 - (1 - p)^2 - const double target = TargetFalsePositiveRate(); - // target_sectors is only a candidate size - nothing is folded until the loop has settled on one - idx_t target_sectors = num_sectors; - while (target_sectors > min_sectors) { - const double folded_density = 1.0 - (1.0 - density) * (1.0 - density); - if (std::pow(folded_density, static_cast(N_BITS)) > target) { - break; - } - density = folded_density; - target_sectors >>= 1; - } - if (target_sectors < num_sectors) { - Fold(target_sectors); - // the filter was sized from an estimate, so hand the memory folding freed up back to the allocator - auto &allocator = BufferManager::GetBufferManager(context).GetBufferAllocator(); - auto compacted_buf = allocator.Allocate(64 + num_sectors * sizeof(uint64_t)); - auto compacted_bf = - reinterpret_cast((64ULL + reinterpret_cast(compacted_buf.get())) & ~63ULL); - std::copy_n(bf, num_sectors, compacted_bf); - buf_ = std::move(compacted_buf); - bf = compacted_bf; - } - } - sealed = true; - // a filter that accepts more than the scan-time policy tolerates gets paused on its first vectors anyway, - // so it is not worth the memory it holds or the hashing it costs - float selectivity_threshold; - idx_t n_vectors_to_check; - GetThresholdAndVectorsToCheck(SelectivityOptionalFilterType::BF, selectivity_threshold, n_vectors_to_check); - useful = std::pow(density, static_cast(N_BITS)) < static_cast(selectivity_threshold); +void BloomFilter::Reset() { + buf_.Reset(); + num_sectors = 0; + bitmask = 0; + initialized = false; + bf = nullptr; } idx_t BloomFilter::GetNumberOfSectors(idx_t number_of_rows) { @@ -149,25 +79,7 @@ inline uint64_t GetMask(const hash_t hash) { return mask; } -void BloomFilter::InsertHashes(const Vector &hashes_v, const SelectionVector &sel, const idx_t count) { - D_ASSERT(hashes_v.GetType() == LogicalType::HASH); - if (count == 0) { - return; - } - // VectorOperations::Hash produces a constant vector for a constant input, and a flat one otherwise - if (hashes_v.GetVectorType() == VectorType::CONSTANT_VECTOR) { - InsertOne(*ConstantVector::GetData(hashes_v)); - return; - } - D_ASSERT(hashes_v.GetVectorType() == VectorType::FLAT_VECTOR); - const auto hashes = FlatVector::GetData(hashes_v); - for (idx_t i = 0; i < count; i++) { - // sel can be the incremental selection vector, so this cannot use get_index_unsafe - InsertOne(hashes[sel.get_index(i)]); - } -} - -void BloomFilter::InsertHashes(const Vector &hashes_v) { +void BloomFilter::InsertHashes(const Vector &hashes_v) const { for (auto hash : hashes_v.Values()) { InsertOne(hash.GetValue()); } @@ -200,23 +112,17 @@ idx_t BloomFilter::LookupHashes(const Vector &hashes_v, const SelectionVector &s return found_count; } -inline void BloomFilter::InsertOne(const hash_t hash) { +inline void BloomFilter::InsertOne(const hash_t hash) const { D_ASSERT(initialized); - D_ASSERT(!sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); - // duplicate keys find their bits already set - checking first keeps the shared sector read-only then, - // instead of a contended read-modify-write - if ((slot.load(std::memory_order_relaxed) & mask) != mask) { - slot.fetch_or(mask, std::memory_order_relaxed); - } + slot.fetch_or(mask, std::memory_order_relaxed); } bool BloomFilter::LookupOne(const uint64_t hash) const { D_ASSERT(initialized); - D_ASSERT(sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); @@ -225,10 +131,10 @@ bool BloomFilter::LookupOne(const uint64_t hash) const { return (bf_entry & mask) == mask; } -BloomFilterFunctionData::BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, +BloomFilterFunctionData::BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p) - : filter(std::move(filter_p)), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), + : filter(filter_p), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p), selectivity_threshold(selectivity_threshold_p), n_vectors_to_check(n_vectors_to_check_p) { } diff --git a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp index 4b0a0cbf8..13e56d61d 100644 --- a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp @@ -21,7 +21,6 @@ DynamicFilterData::DynamicFilterData(ExpressionType comparison_type_p, Value con } unique_ptr DynamicFilterData::ToExpression(const Expression &column) const { - annotated_shared_lock guard(lock); return BoundComparisonExpression::Create(comparison_type, column.Copy(), make_uniq(constant)); } @@ -121,7 +120,7 @@ static idx_t DynamicFilterSelect(DataChunk &args, ExpressionState &state, option ExpressionType comparison_type; Value constant; { - annotated_shared_lock lock(func_data.filter_data->lock); + lock_guard lock(func_data.filter_data->lock); comparison_type = func_data.filter_data->comparison_type; constant = func_data.filter_data->constant; } @@ -151,7 +150,7 @@ FilterPropagateResult DynamicFilterScalarFun::FilterPrune(const FunctionStatisti if (!data.filter_data) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } - annotated_shared_lock lock(data.filter_data->lock); + lock_guard lock(data.filter_data->lock); if (!data.filter_data->initialized) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } diff --git a/src/duckdb/src/planner/filter/table_filter_functions.cpp b/src/duckdb/src/planner/filter/table_filter_functions.cpp index 5e84362cf..a4237539e 100644 --- a/src/duckdb/src/planner/filter/table_filter_functions.cpp +++ b/src/duckdb/src/planner/filter/table_filter_functions.cpp @@ -118,8 +118,7 @@ void TableFilterFunctionSerialize(Serializer &serializer, const optional_ptr TableFilterFunctionDeserialize(Deserializer &deserializer, BoundScalarFunction &function) { auto key_type = function.GetArguments().empty() ? LogicalType::ANY : function.GetArguments()[0]; if (function.GetName() == BloomFilterScalarFun::NAME) { - return make_uniq(shared_ptr(), false, string(), key_type, 0.0f, - idx_t(0)); + return make_uniq(nullptr, false, string(), key_type, 0.0f, idx_t(0)); } if (function.GetName() == PrefixRangeScalarFun::NAME) { return make_uniq(nullptr, true, string(), key_type, 0.0f, idx_t(0)); diff --git a/src/duckdb/src/planner/operator/logical_join.cpp b/src/duckdb/src/planner/operator/logical_join.cpp index ab5ec92f9..c083f190f 100644 --- a/src/duckdb/src/planner/operator/logical_join.cpp +++ b/src/duckdb/src/planner/operator/logical_join.cpp @@ -2,7 +2,6 @@ #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression_iterator.hpp" -#include "duckdb/planner/operator/logical_filter.hpp" #include "duckdb/main/config.hpp" namespace duckdb { @@ -84,21 +83,6 @@ void LogicalJoin::GetExpressionBindings(const Expression &root_expr, unordered_s }); } -unique_ptr LogicalJoin::PromoteJoinSide(unique_ptr child, - vector projection_map) { - if (projection_map.empty()) { - return child; - } - auto filter = make_uniq(); - filter->projection_map = std::move(projection_map); - filter->children.push_back(std::move(child)); - if (filter->children[0]->has_estimated_cardinality) { - filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); - } - filter->ResolveOperatorTypes(); - return std::move(filter); -} - void LogicalJoin::MoveJoinState(LogicalJoin &source, LogicalJoin &target) { D_ASSERT(source.join_type == target.join_type); target.mark_index = source.mark_index; diff --git a/src/duckdb/src/planner/planner.cpp b/src/duckdb/src/planner/planner.cpp index 1ff2280ba..1b5881abb 100644 --- a/src/duckdb/src/planner/planner.cpp +++ b/src/duckdb/src/planner/planner.cpp @@ -240,12 +240,9 @@ void Planner::CreatePlan(SQLStatement &statement) { auto max_tree_depth = Settings::Get(context); CheckTreeDepth(*plan, max_tree_depth); - // A LogicalPlanStatement carries an already planned (and possibly optimized) plan - if (statement.type != StatementType::LOGICAL_PLAN_STATEMENT) { - RewriteTriggersToDependent(*this->binder, *this->plan); - RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); - this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); - } + RewriteTriggersToDependent(*this->binder, *this->plan); + RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); + this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); D_ASSERT(!ContainsDependentJoin(*this->plan)); D_ASSERT(VerifyPlannedExpressions(*this->plan)); D_ASSERT(VerifyCanonicalComparisonJoins(*this->plan)); diff --git a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp index 339cfba43..46c597971 100644 --- a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp +++ b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp @@ -435,25 +435,13 @@ BoundExpressionSQLExportState::ExportComparison(const BoundFunctionExpression &e D_ASSERT(!expression.BindInfo()); D_ASSERT(ChildrenAreConsistentWithArguments(expression.GetChildren(), expression.Function().GetArguments())); D_ASSERT(expression.GetReturnType() == expression.Function().GetReturnType()); - auto &left_type = expression.GetChildren()[0]->GetReturnType(); - auto &right_type = expression.GetChildren()[1]->GetReturnType(); - // the regex range filter compares VARCHAR values with BLOB bounds, as the bounds need not be valid UTF-8 - const bool varchar_blob = (left_type.id() == LogicalTypeId::VARCHAR && right_type.id() == LogicalTypeId::BLOB) || - (left_type.id() == LogicalTypeId::BLOB && right_type.id() == LogicalTypeId::VARCHAR); - D_ASSERT(left_type == right_type || varchar_blob); + D_ASSERT(expression.GetChildren()[0]->GetReturnType() == expression.GetChildren()[1]->GetReturnType()); vector> children; vector issues; ExportChildren(expression.GetChildren(), path, children, issues); if (!issues.empty()) { return BoundExpressionSQLExportResult::Failure(std::move(issues)); } - if (varchar_blob) { - // encode reinterprets the VARCHAR bytes as a BLOB, so the bytes are compared as before - auto &varchar_child = left_type.id() == LogicalTypeId::VARCHAR ? children[0] : children[1]; - vector> arguments; - arguments.push_back(std::move(varchar_child)); - varchar_child = SQLExportHelpers::SystemFunction("encode", std::move(arguments)); - } return BoundExpressionSQLExportResult::Success(make_uniq( expression.GetExpressionType(), std::move(children[0]), std::move(children[1]))); } diff --git a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp index 21298cdce..f3e359ac7 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp @@ -331,9 +331,6 @@ bool LogicalPlanSQLExportHelpers::ComposeOrder(LogicalOperator &op, LogicalPlanS AddSubstitutions(state, child, *source); auto expressions = CollectExpressions(op); auto &modifier = select.modifiers[0]->Cast(); - if (modifier.orders.size() != expressions.size()) { - return false; - } vector> orders; for (idx_t i = 0; i < expressions.size(); i++) { auto &expression = *SemanticInput(expressions[i].get()); diff --git a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp index b17415c21..afcd594b6 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp @@ -332,13 +332,9 @@ BoundExpressionSQLExportState::BuildAggregateCall(const BoundAggregateExpression auto order_bys = make_uniq(); if (expression.GetOrderBys()) { for (auto &order : expression.GetOrderBys()->orders) { - auto &child = children[child_index++]; - if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { - continue; - } - order_bys->orders.emplace_back( - order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); + order_bys->orders.emplace_back(order.type, order.null_order, + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), + std::move(children[child_index++]))); } } result->FilterMutable() = std::move(filter); diff --git a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp index f0a40204a..2806aa549 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp @@ -233,9 +233,6 @@ ExportOrderModifier(LogicalOperator &op, LogicalPlanSQLExportContext &context, c if (expression.HasError()) { return Result::Failure(expression); } - if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *expression.GetValue())) { - continue; - } modifier->orders.emplace_back( order.type, order.null_order, SQLExportHelpers::OrderExpression(SemanticExpressionType(*order.expression, expression_context), @@ -265,10 +262,6 @@ static LogicalPlanSQLExportResult ExportOrderedRelation(LogicalOperator &op, Log if (modifier.HasError()) { return LogicalPlanSQLExportResult::Failure(modifier); } - if (modifier.GetValue()->orders.empty()) { - LogicalPlanSQLExportHelpers::SetChildScope(*select, std::move(child.GetValue()), plain); - return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); - } select->modifiers.push_back(std::move(modifier.GetValue())); if (!plain && LogicalPlanSQLExportHelpers::ComposeOrder(op, child.GetValue(), *select, expression_context, path)) { return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); @@ -476,9 +469,7 @@ LogicalPlanSQLExportResult LogicalDistinct::ToSQL(LogicalPlanSQLExportContext &c if (order.HasError()) { return LogicalPlanSQLExportResult::Failure(order); } - if (!order.GetValue()->orders.empty()) { - select->modifiers.push_back(std::move(order.GetValue())); - } + select->modifiers.push_back(std::move(order.GetValue())); } select->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(child.GetValue())); return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); diff --git a/src/duckdb/src/planner/sql_export/sql_export_window.cpp b/src/duckdb/src/planner/sql_export/sql_export_window.cpp index 64dab0866..9f329bb12 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_window.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_window.cpp @@ -82,19 +82,6 @@ static optional_ptr RangeOffset(const BoundWindowExpression &e return &offset; } -static bool IsOrderDependentBoundary(WindowBoundary boundary) { - switch (boundary) { - case WindowBoundary::EXPR_PRECEDING_RANGE: - case WindowBoundary::EXPR_FOLLOWING_RANGE: - case WindowBoundary::CURRENT_ROW_GROUPS: - case WindowBoundary::EXPR_PRECEDING_GROUPS: - case WindowBoundary::EXPR_FOLLOWING_GROUPS: - return true; - default: - return false; - } -} - static WindowSQLFrame ReconstructWindowFrame(const BoundWindowExpression &expression) { WindowSQLFrame frame; if (expression.OrderBy().size() == 1) { @@ -191,17 +178,10 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression for (idx_t i = 0; i < expression.Partitions().size(); i++) { window->PartitionsMutable().push_back(std::move(children[ordinal++])); } - // RANGE offsets and GROUPS frames are defined in terms of the ORDER BY - const bool frame_needs_order = - IsOrderDependentBoundary(expression.WindowStart()) || IsOrderDependentBoundary(expression.WindowEnd()); for (auto &order : expression.OrderBy()) { - auto &child = children[ordinal++]; - if (!frame_needs_order && SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { - continue; - } window->OrderByMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); } auto &named_arguments = function.GetNamedArguments(); auto positional_count = function.GetPositionalArgumentCount(); @@ -224,13 +204,9 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression window->EndExprMutable() = std::move(children[ordinal++]); } for (auto &order : expression.ArgOrders()) { - auto &child = children[ordinal++]; - if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { - continue; - } window->ArgOrdersMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); } window->IgnoreNullsMutable() = expression.IgnoreNulls(); window->HasIgnoreNullsMutable() = expression.IgnoreNulls(); diff --git a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp index 8c9dd8106..5e198a4b1 100644 --- a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp +++ b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp @@ -2631,8 +2631,9 @@ static bool CanRestrictCorrelationDomain(LogicalOperator &op, unordered_set(binder.context) && - !config.IsOptimizerDisabled(OptimizerType::DELIMINATOR); + cte_deliminator_enabled = + Settings::Get(binder.context) && + config.options.disabled_optimizers.find(OptimizerType::DELIMINATOR) == config.options.disabled_optimizers.end(); } BindingReplacementGraph DelimJoinCTERewriter::MaterializeDelimJoinAsCTE(unique_ptr &plan, diff --git a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp index 69038c5d7..457740d4c 100644 --- a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp +++ b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp @@ -1107,22 +1107,12 @@ FlattenDependentJoins::UnnestingState FlattenDependentJoins::PushDownJoin(unique } if (join_type == JoinType::MARK) { - if (right_requires_domain) { + if (!left_requires_domain && right_requires_domain) { auto right_state = PushDownChild(plan, propagate_null_values, state, false, 1); auto left_state = PushDownChild(plan, propagate_null_values, right_state.bindings, false, 0); - auto &comparison_join = plan->Cast(); - auto conditions = std::move(comparison_join.conditions); - comparison_join.conditions.clear(); - AddCorrelatedJoinConditions(comparison_join, left_state.bindings, right_state.bindings); - // Keep the correlation keys before the IN predicate for per-group NULL and empty-set handling. - comparison_join.mark_types.insert(comparison_join.mark_types.begin(), delim_types.begin(), - delim_types.end()); - for (auto &condition : conditions) { - comparison_join.conditions.push_back(std::move(condition)); - } - RewriteCorrelatedBindings(*plan, left_state.bindings); + AddCorrelatedJoinConditions(plan->Cast(), left_state.bindings, right_state.bindings); left_state.replacement_graph.Merge(right_state.replacement_graph); return left_state; } diff --git a/src/duckdb/src/planner/table_filter_state.cpp b/src/duckdb/src/planner/table_filter_state.cpp index 33bfb58bb..932ea641b 100644 --- a/src/duckdb/src/planner/table_filter_state.cpp +++ b/src/duckdb/src/planner/table_filter_state.cpp @@ -348,7 +348,7 @@ class BloomFilterExecutor final : public ExpressionFilterExecutor { } } - shared_ptr filter; + optional_ptr filter; bool filters_null_values; Vector hashes; SelectionVector bloom_sel; diff --git a/src/duckdb/src/storage/block_allocator.cpp b/src/duckdb/src/storage/block_allocator.cpp index 60b19d2d7..d7088a345 100644 --- a/src/duckdb/src/storage/block_allocator.cpp +++ b/src/duckdb/src/storage/block_allocator.cpp @@ -329,7 +329,7 @@ data_ptr_t BlockAllocator::AllocateData(const idx_t size) const { } void BlockAllocator::FreeData(const data_ptr_t pointer, const idx_t size) const { - estimated_unflushed_memory.fetch_add(size, std::memory_order_relaxed); + deallocated_since_flush.fetch_add(size, std::memory_order_relaxed); if (!IsActive() || !IsInPool(pointer)) { return allocator.FreeData(pointer, size); } @@ -368,7 +368,7 @@ void BlockAllocator::ThreadFlush(bool allocator_background_threads, idx_t thresh } void BlockAllocator::FlushAll(const optional_idx extra_memory) const { - estimated_unflushed_memory.store(0, std::memory_order_relaxed); + deallocated_since_flush.store(0, std::memory_order_relaxed); if (IsActive() && IsEnabled() && extra_memory.IsValid()) { FreeInternal(extra_memory.GetIndex()); } @@ -377,35 +377,22 @@ void BlockAllocator::FlushAll(const optional_idx extra_memory) const { } } -idx_t BlockAllocator::GetEstimatedUnflushedMemory() const { - return estimated_unflushed_memory.load(std::memory_order_relaxed); +idx_t BlockAllocator::GetDeallocatedSinceFlush() const { + return deallocated_since_flush.load(std::memory_order_relaxed); } -bool BlockAllocator::TryFlushDeallocated(const idx_t threshold, const idx_t memory_limit, - const idx_t used_memory) const { +bool BlockAllocator::TryFlushDeallocated(const idx_t threshold) const { if (!SupportsFlush()) { return false; } - auto deallocated = estimated_unflushed_memory.load(std::memory_order_relaxed); + auto deallocated = deallocated_since_flush.load(std::memory_order_relaxed); if (deallocated < threshold) { return false; } - // the thread that claims the accumulated amount checks whether a flush is needed - if (!estimated_unflushed_memory.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { + // the thread that claims the accumulated amount performs the flush + if (!deallocated_since_flush.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { return false; } - if (!IsActive()) { - const auto mapped_memory = allocator.GetMappedMemory(); - if (mapped_memory.IsValid()) { - // Freed pages cannot occupy more than the allocator's entire mapped footprint. - const auto unflushed = MinValue(deallocated, mapped_memory.GetIndex()); - if (used_memory + unflushed <= memory_limit) { - // Keep the upper bound in subsequent pressure checks, adding any concurrent frees. - estimated_unflushed_memory.fetch_add(unflushed, std::memory_order_relaxed); - return false; - } - } - } FlushAll(deallocated); return true; } diff --git a/src/duckdb/src/storage/buffer/buffer_pool.cpp b/src/duckdb/src/storage/buffer/buffer_pool.cpp index 6106abb0a..dc5e68a38 100644 --- a/src/duckdb/src/storage/buffer/buffer_pool.cpp +++ b/src/duckdb/src/storage/buffer/buffer_pool.cpp @@ -586,7 +586,7 @@ void BufferPool::FlushAllocator(const optional_idx extra_memory) { void BufferPool::FlushOnBulkDeallocation() { const auto threshold = GetBulkDeallocationFlushThreshold(); - const auto deallocated = block_allocator.GetEstimatedUnflushedMemory(); + const auto deallocated = block_allocator.GetDeallocatedSinceFlush(); if (deallocated < threshold) { return; } @@ -595,7 +595,7 @@ void BufferPool::FlushOnBulkDeallocation() { if (used_memory + deallocated <= maximum_memory.load(std::memory_order_relaxed)) { return; } - block_allocator.TryFlushDeallocated(threshold, maximum_memory.load(std::memory_order_relaxed), used_memory); + block_allocator.TryFlushDeallocated(threshold); } vector BufferPool::GetEvictionQueueInfo() const { diff --git a/src/duckdb/src/storage/checkpoint_manager.cpp b/src/duckdb/src/storage/checkpoint_manager.cpp index 08f6f4ede..05574c6ce 100644 --- a/src/duckdb/src/storage/checkpoint_manager.cpp +++ b/src/duckdb/src/storage/checkpoint_manager.cpp @@ -613,12 +613,7 @@ void CheckpointWriter::WriteSequence(SequenceCatalogEntry &seq, Serializer &seri void CheckpointReader::ReadSequence(CatalogTransaction transaction, Deserializer &deserializer) { auto info = ReadCreateInfo(deserializer, CatalogType::SEQUENCE_ENTRY, "sequence"); auto &sequence_info = info->Cast(); - auto entry = catalog.CreateSequence(transaction, sequence_info); - auto &storage_manager = catalog.GetAttached().GetStorageManager(); - if (entry && storage_manager.HasStorageVersion() && - StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { - entry->Cast().DeriveLastValue(); - } + catalog.CreateSequence(transaction, sequence_info); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst.cpp b/src/duckdb/src/storage/compression/dict_fsst.cpp index 71d024ea1..0d9cd73f3 100644 --- a/src/duckdb/src/storage/compression/dict_fsst.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst.cpp @@ -160,15 +160,17 @@ void DictFSSTCompressionStorage::StringFetchRow(ColumnSegment &segment, ColumnFe // Select //===--------------------------------------------------------------------===// void DictFSSTSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + const SelectionVector &sel, idx_t sel_count) { auto &scan_state = state.scan_state->Cast(); - if (scan_state.mode != DictFSSTMode::FSST_ONLY && scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { - // scan + slice, so that we can emit a dictionary vector - DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); - result.Slice(sel, sel_count); + if (scan_state.mode == DictFSSTMode::FSST_ONLY) { + // for FSST only + auto start = state.GetPositionInSegment(); + scan_state.Select(result, start, sel, sel_count); return; } - scan_state.Select(result, result_offset, state.GetPositionInSegment(), sel, sel_count); + // fallback: scan + slice + DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); + result.Slice(sel, sel_count); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp index d07d8bbcb..a6e555a82 100644 --- a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp @@ -157,25 +157,19 @@ const SelectionVector &CompressedStringScanState::GetSelVec(idx_t start, idx_t s } void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_offset, idx_t start, idx_t scan_count) { - Select(result, result_offset, start, *FlatVector::IncrementalSelectionVector(), scan_count); - result.Verify(); -} - -void CompressedStringScanState::Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, - idx_t sel_count) { - const auto scan_count = sel_count == 0 ? 0 : sel.get_index(sel_count - 1) + 1; + // Create a decompression buffer of sufficient size if we don't already have one. auto &selvec = GetSelVec(start, scan_count); //! (index 0 is reserved for NULL, which we don't have in this mode) const idx_t start_offset = mode == DictFSSTMode::FSST_ONLY ? start + 1 : 0; - auto result_data = FlatVector::Writer(result, sel_count, result_offset); + auto result_data = FlatVector::Writer(result, scan_count, result_offset); if (dictionary) { // We have prepared the full dictionary, we can reference these strings directly auto dictionary_values = FlatVector::GetData(dictionary->data); - for (idx_t i = 0; i < sel_count; i++) { + for (idx_t i = 0; i < scan_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(start_offset + sel.get_index(i)); + auto string_number = selvec.get_index(i + start_offset); if (string_number == 0) { result_data.WriteNull(); continue; @@ -183,9 +177,9 @@ void CompressedStringScanState::Select(Vector &result, idx_t result_offset, idx_ result_data.WriteStringRef(dictionary_values[string_number]); } } else { - for (idx_t i = 0; i < sel_count; i++) { + for (idx_t i = 0; i < scan_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(start_offset + sel.get_index(i)); + auto string_number = selvec.get_index(start_offset + i); if (string_number == 0) { result_data.WriteNull(); continue; @@ -199,6 +193,25 @@ void CompressedStringScanState::Select(Vector &result, idx_t result_offset, idx_ result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); } } + result.Verify(); +} + +void CompressedStringScanState::Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count) { + D_ASSERT(!dictionary); + D_ASSERT(mode == DictFSSTMode::FSST_ONLY); + idx_t start_offset = start + 1; + auto result_data = FlatVector::Writer(result, sel_count); + for (idx_t i = 0; i < sel_count; i++) { + // Lookup dict offset in index buffer + auto string_number = start_offset + sel.get_index(i); + if (decompress_position > string_number) { + throw InternalException("DICT_FSST: not performing a sequential scan?"); + } + for (; decompress_position < string_number; decompress_position++) { + decompress_offset += string_lengths[decompress_position]; + } + result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); + } } bool CompressedStringScanState::AllowDictionaryScan(idx_t scan_count) { diff --git a/src/duckdb/src/storage/compression/fsst.cpp b/src/duckdb/src/storage/compression/fsst.cpp index c6c724e5a..b30090418 100644 --- a/src/duckdb/src/storage/compression/fsst.cpp +++ b/src/duckdb/src/storage/compression/fsst.cpp @@ -55,7 +55,7 @@ struct FSSTStorage { static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); + const SelectionVector &sel, idx_t sel_count); static void SetDictionary(ColumnSegment &segment, BufferHandle &handle, StringDictionaryContainer container); static StringDictionaryContainer GetDictionary(ColumnSegment &segment, BufferHandle &handle); @@ -768,7 +768,7 @@ void FSSTStorage::StringScan(ColumnSegment &segment, ColumnScanState &state, idx // Select //===--------------------------------------------------------------------===// void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { + const SelectionVector &sel, idx_t sel_count) { auto &scan_state = state.scan_state->Cast(); auto start = state.GetPositionInSegment(); D_ASSERT(start <= segment.count.load()); @@ -783,7 +783,7 @@ void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t v for (idx_t i = 0; i < sel_count; i++) { idx_t index = sel.get_index(i); D_ASSERT(index < vector_count); - result_data[result_offset + i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); + result_data[i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); } } diff --git a/src/duckdb/src/storage/compression/numeric_constant.cpp b/src/duckdb/src/storage/compression/numeric_constant.cpp index fa9da5fb2..94d751c24 100644 --- a/src/duckdb/src/storage/compression/numeric_constant.cpp +++ b/src/duckdb/src/storage/compression/numeric_constant.cpp @@ -251,23 +251,14 @@ void ConstantFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row // Select //===--------------------------------------------------------------------===// void ConstantSelectValidity(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, - ScanVectorType scan_type) { - if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { - ConstantScanFunctionValidity(segment, state, sel_count, result); - return; - } - ConstantScanPartialValidity(segment, state, sel_count, result, result_offset); + const SelectionVector &sel, idx_t sel_count) { + ConstantScanFunctionValidity(segment, state, sel_count, result); } template void ConstantSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { - if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { - ConstantScanFunction(segment, state, vector_count, result); - return; - } - ConstantScanPartial(segment, state, sel_count, result, result_offset); + const SelectionVector &sel, idx_t sel_count) { + ConstantScanFunction(segment, state, vector_count, result); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/rle.cpp b/src/duckdb/src/storage/compression/rle.cpp index bbdbbc0b0..e5e0df6e2 100644 --- a/src/duckdb/src/storage/compression/rle.cpp +++ b/src/duckdb/src/storage/compression/rle.cpp @@ -519,21 +519,19 @@ void RLEScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, V //===--------------------------------------------------------------------===// template void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + const SelectionVector &sel, idx_t sel_count) { auto &scan_state = state.scan_state->Cast>(); // If we are scanning an entire Vector and it contains only a single run we don't need to select at all auto current_run = scan_state.ValidateAndGetCurrentRun(); - if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR && scan_state.position_in_entry < current_run.length && + if (scan_state.position_in_entry < current_run.length && CanEmitConstantVector(scan_state.position_in_entry, current_run.length, vector_count)) { RLEScanConstant(scan_state, current_run, vector_count, result); return; } - auto result_data = FlatVector::Writer(result, sel_count, result_offset); + auto result_data = FlatVector::Writer(result, sel_count); - // Keep the current run until the selection advances past it. - idx_t run_end = current_run.length - scan_state.position_in_entry; idx_t prev_idx = 0; for (idx_t i = 0; i < sel_count; i++) { auto next_idx = sel.get_index(i); @@ -541,16 +539,14 @@ void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_coun throw InternalException("Error in RLESelect - selection vector indices are not ordered"); } D_ASSERT(next_idx < vector_count); - while (next_idx >= run_end) { - scan_state.ForwardToNextRun(); - current_run = scan_state.ValidateAndGetCurrentRun(); - run_end += current_run.length; - } - result_data.WriteValue(current_run.value); + // skip forward to the next index + scan_state.SkipInternal(next_idx - prev_idx); + // read the element + result_data.WriteValue(scan_state.ValidateAndGetCurrentRun().value); + // move the next to the prev prev_idx = next_idx; } - // Restore the position at the last selected row before skipping the tail. - scan_state.position_in_entry = current_run.length - (run_end - prev_idx); + // skip the tail scan_state.SkipInternal(vector_count - prev_idx); } diff --git a/src/duckdb/src/storage/compression/string_uncompressed.cpp b/src/duckdb/src/storage/compression/string_uncompressed.cpp index 9abdb7f15..7348b0934 100644 --- a/src/duckdb/src/storage/compression/string_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/string_uncompressed.cpp @@ -158,8 +158,7 @@ void UncompressedStringStorage::StringScan(ColumnSegment &segment, ColumnScanSta // Select //===--------------------------------------------------------------------===// void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, - Vector &result, const SelectionVector &sel, idx_t sel_count, idx_t result_offset, - ScanVectorType) { + Vector &result, const SelectionVector &sel, idx_t sel_count) { // clear any previously locked buffers and get the primary buffer handle auto &scan_state = state.scan_state->Cast(); auto layout = StringSegmentLayout::Read(scan_state.handle, segment); @@ -175,7 +174,7 @@ void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState & auto selection_index = sel.get_index(i); D_ASSERT(selection_index < vector_count); auto entry = strings.GetEntry(selection_index); - result_data[result_offset + i] = FetchStringFromEntry(state.context, segment, result, entry); + result_data[i] = FetchStringFromEntry(state.context, segment, result, entry); } } diff --git a/src/duckdb/src/storage/compression/validity_uncompressed.cpp b/src/duckdb/src/storage/compression/validity_uncompressed.cpp index 6ab8fbe5b..a9778897b 100644 --- a/src/duckdb/src/storage/compression/validity_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/validity_uncompressed.cpp @@ -520,7 +520,7 @@ void ValidityScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_cou // Select //===--------------------------------------------------------------------===// void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { + const SelectionVector &sel, idx_t sel_count) { auto start = state.GetPositionInSegment(); if (sel_count == 0) { return; @@ -528,21 +528,18 @@ void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector D_ASSERT(start <= segment.count && vector_count <= segment.count - start); D_ASSERT(sel_count <= vector_count); - if (result_offset == 0) { - result.Flatten(); - } + result.Flatten(); auto &scan_state = state.scan_state->Cast(); - auto input_data = ValidityScanData(scan_state.reader, start, vector_count); - auto input_start = start % ValidityMask::BITS_PER_VALUE; auto &result_mask = FlatVector::ValidityMutable(result); for (idx_t i = 0; i < sel_count; i++) { auto selected_index = sel.get_index(i); D_ASSERT(selected_index < vector_count); - auto source_idx = input_start + selected_index; - auto entry = input_data[source_idx / ValidityMask::BITS_PER_VALUE]; + auto source_idx = start + selected_index; + auto entry_offset = source_idx / ValidityMask::BITS_PER_VALUE * sizeof(validity_t); + auto entry = scan_state.reader.Get(entry_offset); if (!ValidityMask::RowIsValid(entry, source_idx % ValidityMask::BITS_PER_VALUE)) { - result_mask.SetInvalid(result_offset + i); + result_mask.SetInvalid(i); } } } @@ -641,14 +638,12 @@ void ValidityRevertAppend(ColumnSegment &segment, idx_t new_count) { //===--------------------------------------------------------------------===// CompressionFunction ValidityUncompressed::GetFunction(PhysicalType data_type) { D_ASSERT(data_type == PhysicalType::BIT); - CompressionFunction result(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, + return CompressionFunction(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, ValidityAnalyze, ValidityFinalAnalyze, UncompressedFunctions::InitCompression, UncompressedFunctions::Compress, UncompressedFunctions::FinalizeCompress, ValidityInitScan, ValidityScan, ValidityScanPartial, ValidityFetchRow, UncompressedFunctions::EmptySkip, ValidityInitSegment, ValidityInitAppend, ValidityAppend, ValidityFinalizeAppend, ValidityRevertAppend); - result.select = ValiditySelect; - return result; } } // namespace duckdb diff --git a/src/duckdb/src/storage/data_table.cpp b/src/duckdb/src/storage/data_table.cpp index 2f9cec660..a7884c327 100644 --- a/src/duckdb/src/storage/data_table.cpp +++ b/src/duckdb/src/storage/data_table.cpp @@ -902,8 +902,6 @@ void DataTable::InitializeLocalStorage(LocalAppendState &state, DuckTableEntry & GetTableName(), TableModification()); } - // The transaction-local indexes are created with the local storage, and only for indexes that are bound - info->BindIndexes(context); auto &local_storage = LocalStorage::Get(context, db); local_storage.InitializeStorage(state, *this, table); state.constraint_state = InitializeConstraintState(table, bound_constraints); diff --git a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp index 3c9b26937..5d9dbd3cb 100644 --- a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp +++ b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp @@ -616,12 +616,6 @@ bool CachingFileHandle::TryGetNetworkThroughput(NetworkThroughputEstimate &resul void CachingFileHandle::ReadAndRecord(QueryContext context, data_ptr_t buffer, idx_t nr_bytes, idx_t location) { auto handle = GetFileHandle(); - if (handle->file_system.IsLocalFileSystem()) { - // Pre-fault local read buffers before the kernel fills them. - for (idx_t offset = 0; offset < nr_bytes; offset += 4096) { - buffer[offset] = 0; - } - } const auto read_start = steady_clock::now(); handle->Read(context, buffer, nr_bytes, location); auto cache_valid_until = handle->file_system.GetCacheValidUntil(*handle); diff --git a/src/duckdb/src/storage/statistics/numeric_stats.cpp b/src/duckdb/src/storage/statistics/numeric_stats.cpp index 14640c6a1..ab56e0446 100644 --- a/src/duckdb/src/storage/statistics/numeric_stats.cpp +++ b/src/duckdb/src/storage/statistics/numeric_stats.cpp @@ -31,25 +31,6 @@ BaseStatistics NumericStats::CreateUnknown(LogicalType type) { return result; } -//! The type whose limits are used for the min/max of empty statistics -static LogicalType GetEmptyStatsLimitType(const LogicalType &type) { - if (type.id() != LogicalTypeId::ENUM) { - return type; - } - // the dictionary of an ENUM can be empty or have a single entry: use the limits of the index type instead, so - // that the min is larger than the max until a value is added - switch (type.InternalType()) { - case PhysicalType::UINT8: - return LogicalType::UTINYINT; - case PhysicalType::UINT16: - return LogicalType::USMALLINT; - case PhysicalType::UINT32: - return LogicalType::UINTEGER; - default: - throw InternalException("Invalid Internal Type for ENUMs"); - } -} - BaseStatistics NumericStats::CreateEmpty(LogicalType type) { BaseStatistics result(std::move(type)); result.InitializeEmpty(); @@ -60,13 +41,11 @@ BaseStatistics NumericStats::CreateEmpty(LogicalType type) { case PhysicalType::DOUBLE: InitializeEmptyFloatingPointStats(result); break; - default: { - auto limit_type = GetEmptyStatsLimitType(result.GetType()); - SetMin(result, Value::MaximumValue(limit_type)); - SetMax(result, Value::MinimumValue(limit_type)); + default: + SetMin(result, Value::MaximumValue(result.GetType())); + SetMax(result, Value::MinimumValue(result.GetType())); break; } - } return result; } diff --git a/src/duckdb/src/storage/statistics/variant_stats.cpp b/src/duckdb/src/storage/statistics/variant_stats.cpp index 5440fb2d3..ddc90feaf 100644 --- a/src/duckdb/src/storage/statistics/variant_stats.cpp +++ b/src/duckdb/src/storage/statistics/variant_stats.cpp @@ -696,8 +696,8 @@ bool VariantStats::MergeShredding(const BaseStatistics &stats, const BaseStatist return true; } else { D_ASSERT(!stats_typed_value_type.IsNested()); - if (stats_typed_value_type != other_typed_value_type) { - //! other is not the same type (e.g. a DECIMAL of a different width or scale), can't merge + if (stats_typed_value_type.id() != other_typed_value_type.id()) { + //! other is not the same type, can't merge return false; } auto new_typed_stats = stats_typed_value.Copy(); diff --git a/src/duckdb/src/storage/table/column_data.cpp b/src/duckdb/src/storage/table/column_data.cpp index c820ee4f8..66f3d82e0 100644 --- a/src/duckdb/src/storage/table/column_data.cpp +++ b/src/duckdb/src/storage/table/column_data.cpp @@ -262,71 +262,24 @@ idx_t ColumnData::ScanVector(ColumnScanState &state, Vector &result, idx_t remai } void ColumnData::SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count, ScanVectorType scan_type) { + idx_t sel_count) { BeginScanVectorInternal(state); - // scratch space to rebase the selection vector to the start of a segment scan - // this owns its data, as compression functions can retain the selection vector (e.g. by slicing the result) - SelectionVector rebased_sel; - idx_t sel_offset = 0; - idx_t scanned_count = 0; - // the position up to which the scan state of the current segment has been advanced - // segments without any selected rows are not scanned, so they must be skipped in the next scan - idx_t segment_internal_index = state.offset_in_column; - while (scanned_count < target_count) { - auto ¤t = state.current->GetNode(); - auto current_start = state.current->GetRowStart(); - idx_t scan_count = - MinValue(target_count - scanned_count, current_start + current.count - state.offset_in_column); - segment_internal_index = state.offset_in_column; - // gather the selected rows that fall within this segment - idx_t segment_sel_count = 0; - while (sel_offset + segment_sel_count < sel_count && - sel.get_index(sel_offset + segment_sel_count) < scanned_count + scan_count) { - segment_sel_count++; - } - if (segment_sel_count > 0) { - // the indices must be relative to the start of the segment scan - for the first segment this is - // already the case, so we can forward the selection vector of the caller directly - reference segment_sel(sel); - if (scanned_count > 0) { - if (!rebased_sel.IsSet()) { - rebased_sel.Initialize(); - } - for (idx_t i = 0; i < segment_sel_count; i++) { - rebased_sel.set_index(i, sel.get_index(sel_offset + i) - scanned_count); - } - segment_sel = rebased_sel; - } - if (state.scan_options && state.scan_options->force_fetch_row) { - for (idx_t i = 0; i < segment_sel_count; i++) { - ColumnFetchState fetch_state; - auto source_idx = state.offset_in_column + segment_sel.get().get_index(i) - current_start; - current.FetchRow(fetch_state, UnsafeNumericCast(source_idx), result, sel_offset + i); - } - } else { - current.Select(state, scan_count, result, segment_sel.get(), segment_sel_count, sel_offset, scan_type); - } - segment_internal_index = state.offset_in_column + scan_count; - } - sel_offset += segment_sel_count; - scanned_count += scan_count; - state.offset_in_column += scan_count; - if (scanned_count < target_count) { - auto next = data.GetNextSegment(*state.current); - if (!next) { - break; - } - state.previous_states.emplace_back(std::move(state.scan_state)); - state.current = next; - state.current->GetNode().InitializeScan(state); - state.segment_checked = false; - } + auto ¤t = state.current->GetNode(); + if (state.current->GetRowStart() + current.count - state.offset_in_column < target_count) { + throw InternalException("ColumnData::SelectVector should be able to fetch everything from one segment"); } - if (scan_type == ScanVectorType::SCAN_FLAT_VECTOR) { - // not every compression function sets the size of the result - FlatVector::SetSize(result, count_t(sel_count)); + if (state.scan_options && state.scan_options->force_fetch_row) { + auto start = state.GetPositionInSegment(); + for (idx_t i = 0; i < sel_count; i++) { + auto source_idx = sel.get_index(i); + ColumnFetchState fetch_state; + current.FetchRow(fetch_state, UnsafeNumericCast(start + source_idx), result, i); + } + } else { + current.Select(state, target_count, result, sel, sel_count); } - state.internal_index = segment_internal_index; + state.offset_in_column += target_count; + state.internal_index = state.offset_in_column; } void ColumnData::FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/column_segment.cpp b/src/duckdb/src/storage/table/column_segment.cpp index 641f4deef..36f1f2f1b 100644 --- a/src/duckdb/src/storage/table/column_segment.cpp +++ b/src/duckdb/src/storage/table/column_segment.cpp @@ -151,16 +151,11 @@ void ColumnSegment::Scan(ColumnScanState &state, idx_t scan_count, Vector &resul } void ColumnSegment::Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, - idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + idx_t sel_count) { if (!function.get().select) { throw InternalException("ColumnSegment::Select not implemented for this compression method"); } - if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { - D_ASSERT(result_offset == 0); - } else { - D_ASSERT(result.GetVectorType() == VectorType::FLAT_VECTOR); - } - function.get().select(*this, state, scan_count, result, sel, sel_count, result_offset, scan_type); + function.get().select(*this, state, scan_count, result, sel, sel_count); } void ColumnSegment::Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/standard_column_data.cpp b/src/duckdb/src/storage/table/standard_column_data.cpp index ac2fa3ed2..734db61b9 100644 --- a/src/duckdb/src/storage/table/standard_column_data.cpp +++ b/src/duckdb/src/storage/table/standard_column_data.cpp @@ -113,13 +113,14 @@ void StandardColumnData::Select(TransactionData transaction, idx_t vector_index, bool validity_has_select = validity_compression && validity_compression->select; auto target_count = GetVectorCount(vector_index); auto scan_type = GetVectorScanType(state, target_count, result); - if (!has_select || !validity_has_select || HasUpdates() || validity->HasUpdates()) { - // the updates need to be merged into the entire vector, so we cannot select + bool scan_entire_vector = scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR; + if (!has_select || !validity_has_select || !scan_entire_vector) { + // we are not scanning an entire vector - this can have several causes (updates, etc) ColumnData::Select(transaction, vector_index, state, result, sel, sel_count); return; } - SelectVector(state, result, target_count, sel, sel_count, scan_type); - validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count, scan_type); + SelectVector(state, result, target_count, sel, sel_count); + validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count); } void StandardColumnData::InitializeAppend(ColumnAppendState &state) { diff --git a/src/duckdb/src/storage/wal_replay.cpp b/src/duckdb/src/storage/wal_replay.cpp index fa1b476ca..54611faa2 100644 --- a/src/duckdb/src/storage/wal_replay.cpp +++ b/src/duckdb/src/storage/wal_replay.cpp @@ -1176,11 +1176,6 @@ void WriteAheadLogDeserializer::ReplaySequenceValue() { // fetch the sequence from the catalog auto &seq = catalog.GetEntry(context, ReplayEntryName(catalog, entry.qualified_name)); seq.ReplayValue(entry.usage_count, entry.counter, entry.last_value); - auto &storage_manager = db.GetStorageManager(); - if (storage_manager.HasStorageVersion() && - StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { - seq.DeriveLastValue(); - } } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/transaction/meta_transaction.cpp b/src/duckdb/src/transaction/meta_transaction.cpp index d74a02b47..b3cf2b9e2 100644 --- a/src/duckdb/src/transaction/meta_transaction.cpp +++ b/src/duckdb/src/transaction/meta_transaction.cpp @@ -245,10 +245,9 @@ AttachedDatabase &MetaTransaction::UseDatabase(shared_ptr &dat if (entry == referenced_databases.end()) { auto used_entry = used_databases.emplace(db_ref.GetName(), db_ref); if (!used_entry.second) { - // e.g. a database was detached and another one attached under the same name in this transaction - throw TransactionException( - "Database name %s was already used by a different database in this transaction - commit or roll back " - "the transaction before using the name for another database", + // return used_entry.first->second.get(); + throw InternalException( + "Database name %s was already used by a different database for this meta transaction", db_ref.GetName()); } referenced_databases.emplace(reference(db_ref), database); diff --git a/src/duckdb/third_party/httplib/httplib.hpp b/src/duckdb/third_party/httplib/httplib.hpp index 5675d7611..ae0a35db6 100644 --- a/src/duckdb/third_party/httplib/httplib.hpp +++ b/src/duckdb/third_party/httplib/httplib.hpp @@ -194,6 +194,14 @@ #define CPPHTTPLIB_RECV_FLAGS 0 #endif +#ifndef CPPHTTPLIB_SEND_FLAGS +#ifdef MSG_NOSIGNAL +#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL +#else +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif +#endif + #ifndef CPPHTTPLIB_LISTEN_BACKLOG #define CPPHTTPLIB_LISTEN_BACKLOG 128 #endif @@ -321,14 +329,6 @@ using socket_t = int; #endif #endif //_WIN32 -#ifndef CPPHTTPLIB_SEND_FLAGS -#ifdef MSG_NOSIGNAL -#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL -#else -#define CPPHTTPLIB_SEND_FLAGS 0 -#endif -#endif - #if defined(__APPLE__) #include #endif diff --git a/src/duckdb/third_party/ska_sort/ska_sort.hpp b/src/duckdb/third_party/ska_sort/ska_sort.hpp index 5347ea7d7..3296a8c1b 100644 --- a/src/duckdb/third_party/ska_sort/ska_sort.hpp +++ b/src/duckdb/third_party/ska_sort/ska_sort.hpp @@ -1070,39 +1070,25 @@ struct TypeAt<0, First, More...> // { // }; -// LNK note that we use the full comparison (not just extracted key) here -template -struct FullComparison -{ - typedef typename std::remove_reference())>::type value_type; - bool operator()(const value_type & l, const value_type & r) const - { - return l < r; - } -}; - -// Comparison sort of a radix bucket whose extracted keys are all equal: vergesort over the full comparison, with -// the sorted runs it finds merged by the caller's merge policy (see duckdb_vergesort::inplace_merge_policy), so -// that the merge scratch memory of the nested sort is the one the caller controls -template -inline void StdSortFallback(It begin, It end, ExtractKey &, const Merge & merge) -{ - FullComparison comp; - const auto fallback = [&](const It &fb_begin, const It &fb_end) { - duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); - }; - duckdb_vergesort::vergesort(begin, end, comp, fallback, merge); +template +inline void StdSortFallback(It begin, It end, ExtractKey & extract_key) +{ + // LNK note that we use the full comparison (not just extracted key) here + static const auto comp = [&](const typename std::remove_reference::type & l, const typename std::remove_reference::type & r){ return l < r; }; + static const auto fallback = [&](const It &fb_begin, const It &fb_end) { + duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); + }; + duckdb_vergesort::vergesort(begin, end, comp, fallback); } template -inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey &) +inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey & extract_key) { if (num_elements <= 1) return true; if (num_elements >= StdSortThreshold) return false; - // below vergesort's minimum size (128), where it would hand the whole range to pdqsort without merging anything - duckdb_pdqsort::pdqsort_branchless(begin, end, FullComparison()); + StdSortFallback(begin, end, extract_key); return true; } @@ -1436,8 +1422,8 @@ struct SortStarter> template struct SortStarter { - template - static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, const Merge & merge) + template + static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, void * next_sort_data = nullptr) { if (StdSortIfLessThanThreshold(begin, end, num_elements, extract_key)) return; @@ -1447,26 +1433,22 @@ struct SortStarter // next_sort = nullptr; // LNK: different templating for DuckDB - // The sort data pointer that the radix passes carry along is the merge policy for the comparison sort of the - // buckets that the radix passes cannot order (the sub keys of DuckDB's sort keys never look at it) void (*next_sort)(It, It, size_t, ExtractKey &, void *) = nullptr; - void * next_sort_data = nullptr; if (extract_key.requires_next_sort) { - next_sort = [](It b, It e, size_t, ExtractKey& ek, void* merge_policy) { - StdSortFallback(b, e, ek, *static_cast(merge_policy)); + next_sort = [](It b, It e, size_t, ExtractKey& ek, void*) { + StdSortFallback(b, e, ek); }; - next_sort_data = const_cast(static_cast(&merge)); } InplaceSorter::sort(begin, end, num_elements, extract_key, next_sort, next_sort_data); } }; -template -void inplace_radix_sort(It begin, It end, ExtractKey & extract_key, const Merge & merge) +template +void inplace_radix_sort(It begin, It end, ExtractKey & extract_key) { typedef SubKey SubKeyType; - SortStarter::sort(begin, end, end - begin, extract_key, merge); + SortStarter::sort(begin, end, end - begin, extract_key); } struct IdentityFunctor @@ -1479,18 +1461,10 @@ struct IdentityFunctor }; } -// Sorts [begin, end) by the radix of extract_key; buckets whose extracted keys are equal (when extract_key requires -// a next sort) are sorted by the full comparison with vergesort, whose run merges go through the given merge policy -template -static void ska_sort(It begin, It end, ExtractKey && extract_key, const Merge & merge) -{ - detail::inplace_radix_sort<128, 1024>(begin, end, extract_key, merge); -} - template static void ska_sort(It begin, It end, ExtractKey && extract_key) { - ska_sort(begin, end, extract_key, duckdb_vergesort::inplace_merge_policy()); + detail::inplace_radix_sort<128, 1024>(begin, end, extract_key); } template diff --git a/src/duckdb/third_party/vergesort/vergesort.h b/src/duckdb/third_party/vergesort/vergesort.h index e1322da65..1d7275318 100644 --- a/src/duckdb/third_party/vergesort/vergesort.h +++ b/src/duckdb/third_party/vergesort/vergesort.h @@ -47,19 +47,6 @@ namespace duckdb_vergesort { - // Merge policy used for the final pairwise run merges: a callable merge(first, middle, last, compare) that - // merges the sorted ranges [first, middle) and [middle, last). The default is std::inplace_merge, which takes - // its temporary buffer from operator new; callers that want to control that memory pass their own policy. - struct inplace_merge_policy - { - template - void operator()(RandomAccessIterator first, RandomAccessIterator middle, - RandomAccessIterator last, Compare compare) const - { - std::inplace_merge(first, middle, last, compare); - } - }; - namespace detail { // In-place merge where [first, middle1), [middle1, middle2) @@ -191,9 +178,9 @@ namespace duckdb_vergesort } } - template + template void vergesort(RandomAccessIterator first, RandomAccessIterator last, - Compare compare, std::random_access_iterator_tag, Fallback fallback, Merge merge) + Compare compare, std::random_access_iterator_tag, Fallback fallback) { typedef typename std::iterator_traits::difference_type difference_type; difference_type dist = last - first; @@ -334,7 +321,7 @@ namespace duckdb_vergesort for (typename std::list::iterator it = runs.begin() ; it != runs.end() && it != detail::prev(runs.end()) ; ++it) { - merge(begin, *it, *detail::next(it), compare); + std::inplace_merge(begin, *it, *detail::next(it), compare); // Remove the middle iterator and advance it = runs.erase(it); @@ -344,19 +331,12 @@ namespace duckdb_vergesort } } - template - void vergesort(BidirectionalIterator first, BidirectionalIterator last, - Compare compare, Fallback fallback, Merge merge) - { - typedef typename std::iterator_traits::iterator_category category; - detail::vergesort(first, last, compare, category(), fallback, merge); - } - template void vergesort(BidirectionalIterator first, BidirectionalIterator last, Compare compare, Fallback fallback) { - vergesort(first, last, compare, fallback, inplace_merge_policy()); + typedef typename std::iterator_traits::iterator_category category; + detail::vergesort(first, last, compare, category(), fallback); } template From 8a3945fb3291ae1f5b685a932c75817795e21745 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Thu, 15 Oct 2026 20:44:29 +0000 Subject: [PATCH 04/13] Update vendored DuckDB sources to 43690c453d1 --- ...nerated_extension_loader_package_build.cpp | 8 +- src/duckdb/src/common/enum_util.cpp | 7 +- .../tree_renderer/html_tree_renderer.cpp | 2 +- .../types/row/tuple_data_collection.cpp | 9 +- src/duckdb/src/execution/join_hashtable.cpp | 48 +- .../operator/join/physical_hash_join.cpp | 134 +++- .../function/table/version/pragma_version.cpp | 6 +- .../duckdb/execution/join_hashtable.hpp | 25 +- .../duckdb/execution/merge_sort_tree.hpp | 4 +- .../src/include/duckdb/logging/log_type.hpp | 4 +- .../duckdb/main/profiler/gathered_metrics.hpp | 38 +- .../duckdb/main/profiler/profiling_utils.hpp | 21 +- .../include/duckdb/main/query_profiler.hpp | 2 +- .../duckdb/main/statement_iterator.hpp | 3 + .../duckdb/parser/peg/inlined_grammar.hpp | 417 ++++++++++++- .../keyword_helper/default_keyword_maps.hpp | 2 + .../src/include/duckdb/parser/peg/matcher.hpp | 3 +- .../parser/peg/matcher/identifier_matcher.hpp | 4 + .../peg/transformer/peg_transformer.hpp | 148 +++-- .../duckdb/planner/expression_binder.hpp | 3 + .../duckdb/planner/filter/bloom_filter.hpp | 6 +- .../planner/filter/table_filter_functions.hpp | 38 +- src/duckdb/src/logging/log_types.cpp | 8 +- .../src/main/profiler/gathered_metrics.cpp | 7 +- .../src/main/profiler/profiling_utils.cpp | 4 +- src/duckdb/src/main/query_profiler.cpp | 6 +- src/duckdb/src/main/statement_iterator.cpp | 4 +- .../src/optimizer/rule/monotone_preimage.cpp | 51 +- .../src/parser/peg/autocomplete_core.cpp | 1 + .../src/parser/peg/compiled_grammar.cpp | 2 + .../keyword_helper/default_keyword_maps.cpp | 6 + .../parsed_grammar_keyword_helper.cpp | 30 +- src/duckdb/src/parser/peg/keyword_map.cpp | 361 +++++++++++ .../parser/peg/tokenizer/base_tokenizer.cpp | 13 +- .../transformer/peg_transformer_factory.cpp | 1 + .../peg/transformer/transform_common.cpp | 70 +-- .../parser/peg/transformer/transform_copy.cpp | 17 +- .../transformer/transform_create_sequence.cpp | 10 + .../transformer/transform_create_table.cpp | 3 + .../peg/transformer/transform_create_view.cpp | 3 + .../peg/transformer/transform_delete.cpp | 16 +- .../parser/peg/transformer/transform_drop.cpp | 3 + .../peg/transformer/transform_expression.cpp | 49 +- .../transform_generated_trampoline.cpp | 584 +++++++++++++----- .../peg/transformer/transform_insert.cpp | 6 +- .../peg/transformer/transform_pivot.cpp | 12 +- .../peg/transformer/transform_select.cpp | 20 +- .../peg/transformer/transform_update.cpp | 24 +- .../parser/peg/transformer/transform_use.cpp | 16 +- .../peg/transformer/transform_vacuum.cpp | 2 + .../expression/bind_type_expression.cpp | 1 + src/duckdb/src/planner/expression_binder.cpp | 4 + .../filter/table_filter_bloom_function.cpp | 128 +++- .../planner/filter/table_filter_functions.cpp | 3 +- src/duckdb/src/planner/planner.cpp | 9 +- src/duckdb/src/planner/table_filter_state.cpp | 2 +- 56 files changed, 1959 insertions(+), 449 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index 1654161a0..c1e9f6bda 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha47887"; + descriptor->api_version = "v2.0.0-alpha48043"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha47887"; + descriptor->api_version = "v2.0.0-alpha48043"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha47887"; + descriptor->api_version = "v2.0.0-alpha48043"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha47887"; + descriptor->api_version = "v2.0.0-alpha48043"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/common/enum_util.cpp b/src/duckdb/src/common/enum_util.cpp index 588815540..652402efc 100644 --- a/src/duckdb/src/common/enum_util.cpp +++ b/src/duckdb/src/common/enum_util.cpp @@ -6492,19 +6492,20 @@ const StringUtil::EnumStringLiteral *GetSuggestionStateValues() { { static_cast(SuggestionState::SUGGEST_TABLE_FUNCTION_NAME), "SUGGEST_TABLE_FUNCTION_NAME" }, { static_cast(SuggestionState::SUGGEST_PRAGMA_NAME), "SUGGEST_PRAGMA_NAME" }, { static_cast(SuggestionState::SUGGEST_SETTING_NAME), "SUGGEST_SETTING_NAME" }, - { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" } + { static_cast(SuggestionState::SUGGEST_RESERVED_VARIABLE), "SUGGEST_RESERVED_VARIABLE" }, + { static_cast(SuggestionState::SUGGEST_BARE_LABEL), "SUGGEST_BARE_LABEL" } }; return values; } template<> const char* EnumUtil::ToChars(SuggestionState value) { - return StringUtil::EnumToString(GetSuggestionStateValues(), 14, "SuggestionState", static_cast(value)); + return StringUtil::EnumToString(GetSuggestionStateValues(), 15, "SuggestionState", static_cast(value)); } template<> SuggestionState EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 14, "SuggestionState", value)); + return static_cast(StringUtil::StringToEnum(GetSuggestionStateValues(), 15, "SuggestionState", value)); } const StringUtil::EnumStringLiteral *GetTableColumnTypeValues() { diff --git a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp index f60ab52bd..b8909899a 100644 --- a/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp +++ b/src/duckdb/src/common/tree_renderer/html_tree_renderer.cpp @@ -300,7 +300,7 @@ void HTMLTreeRenderer::RenderProfiler(const QueryProfiler &profiler, BaseTreeRen query_sql = HighlightSQL(FormatSQL(profiler.GetQuerySQL())); query_timings.clear(); for (auto &entry : qm.GetMetricTimings()) { - query_timings.emplace_back(entry.first, static_cast(entry.second) / 1e9); + query_timings.emplace_back(entry.first, static_cast(entry.second.duration) / 1e9); } has_query_metrics = true; profiler.RenderProfilingNodeTree(*this, ss); diff --git a/src/duckdb/src/common/types/row/tuple_data_collection.cpp b/src/duckdb/src/common/types/row/tuple_data_collection.cpp index 3a34bcf12..19a682334 100644 --- a/src/duckdb/src/common/types/row/tuple_data_collection.cpp +++ b/src/duckdb/src/common/types/row/tuple_data_collection.cpp @@ -757,7 +757,14 @@ void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state, TupleDa } void TupleDataCollection::FinalizePinState(TupleDataPinState &pin_state) { - D_ASSERT(!segments.empty()); + if (segments.empty()) { + // The collection was Reset() while an append state still referred to it, which happens when an exception + // (e.g. out-of-memory) unwinds a Repartition after it moved this partition's rows and before the owner + // re-initialised its append states. There is no segment left to store the handles in, so release them. + pin_state.row_handles.clear(); + pin_state.heap_handles.clear(); + return; + } FinalizePinState(pin_state, *segments.back()); } diff --git a/src/duckdb/src/execution/join_hashtable.cpp b/src/duckdb/src/execution/join_hashtable.cpp index bd90a7982..493e0f084 100644 --- a/src/duckdb/src/execution/join_hashtable.cpp +++ b/src/duckdb/src/execution/join_hashtable.cpp @@ -174,9 +174,9 @@ void JoinHashTable::Merge(JoinHashTable &other) { } } - if (bloom_filter.IsInitialized() && other.bloom_filter.IsInitialized()) { - bloom_filter.Merge(other.bloom_filter); - } + // every thread-local table shares this operator's filter - a different one here would mean its rows + // never reached the filter that gets published + D_ASSERT(!other.bloom_filter || bloom_filter.get() == other.bloom_filter.get()); sink_collection->Combine(*other.sink_collection); @@ -715,8 +715,8 @@ void JoinHashTable::Build(PartitionedTupleDataAppendState &append_state, DataChu // hash the keys and obtain an entry in the list // note that we only hash the keys used in the equality comparison Hash(keys, *current_sel, added_count, hash_values); - if (bloom_filter.IsInitialized()) { - bloom_filter.InsertHashes(hash_values); + if (bloom_filter) { + bloom_filter->InsertHashes(hash_values, *current_sel, added_count); } // Re-reference and ToUnifiedFormat the hash column after computing it @@ -1003,9 +1003,9 @@ static void InsertHashesLoop(unsafe_optional_ptr> entries, Ve void JoinHashTable::InsertHashes(Vector &hashes_v, TupleDataChunkState &chunk_state, InsertState &insert_state, bool parallel) { - // Insert Hashes into the BF - if (bloom_filter.IsInitialized()) { - bloom_filter.InsertHashes(hashes_v); + // a sealed filter was accumulated during Sink and is already complete; only a finalize-built one fills here + if (bloom_filter && !bloom_filter->IsSealed()) { + bloom_filter->InsertHashes(hashes_v); } auto atomic_entries = GetAtomicEntries(); auto &row_locations = chunk_state.row_locations; @@ -1072,9 +1072,10 @@ void JoinHashTable::AllocatePointerTable() { throw InternalException("Hashtable capacity exceeds 48-bit limit (2^48 - 1)"); } - if (should_build_bloom_filter && !bloom_filter.IsInitialized()) { - bloom_filter_init_count = MaxValue(Count(), 1); - bloom_filter.Initialize(context, bloom_filter_init_count); + if (should_build_bloom_filter && (!bloom_filter || bloom_filter->IsSealed())) { + // (re)create the finalize-built filter now that the actual build count is known + bloom_filter = make_shared_ptr(); + bloom_filter->Initialize(context, MaxValue(Count(), 1)); } if (hash_map.get()) { @@ -1100,28 +1101,6 @@ void JoinHashTable::AllocatePointerTable() { {"size", to_string(data_collection->SizeInBytes() + hash_map.GetSize())}}); } -void JoinHashTable::PrepareBloomFilterForFinalize() { - if (!should_build_bloom_filter) { - return; - } - - // Finalize scans every build tuple and inserts its hash into the bloom filter. - // Make sure any existing filter has enough sectors for the actual build count. - const auto build_count = Count(); - const auto actual_init_count = MaxValue(build_count, 1); - if (bloom_filter.IsInitialized()) { - const auto current_sectors = BloomFilter::GetNumberOfSectors(bloom_filter_init_count); - const auto required_sectors = BloomFilter::GetNumberOfSectors(actual_init_count); - if (current_sectors >= required_sectors) { - return; - } - } - - bloom_filter.Reset(); - bloom_filter_init_count = actual_init_count; - bloom_filter.Initialize(context, bloom_filter_init_count); -} - void JoinHashTable::InitializePointerTable(idx_t entry_idx_from, idx_t entry_idx_to) { // initialize HT with all-zero entries auto entries = GetEntries(); @@ -2599,9 +2578,8 @@ void JoinHashTable::ResetForNewIterationSinglePartition() { chains_longer_than_one.store(false, std::memory_order_relaxed); total_probe_matches = 0; load_factor = DEFAULT_LOAD_FACTOR; + bloom_filter.reset(); should_build_bloom_filter = false; - bloom_filter.Reset(); - bloom_filter_init_count = 0; prefix_range_filter.reset(); should_build_prefix_range_filter = false; ResetMarkJoinInfo(*this); diff --git a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp index d0db40702..21154bb4a 100644 --- a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp @@ -350,6 +350,60 @@ struct LayoutGate { static idx_t GetBuildSpaceRequirement(ClientContext &context, const JoinHashTable &ht, idx_t num_threads); +//! Building the bloom filter is costly on the build side to make probing faster, +//! so only use it if there are less build tuples than probing tuples +static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; +//! If we have a build side without a filter, only build the bloom filter if the ratio/size is small enough +static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; +static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; +//! A join is expected to stay in memory while its estimated build side is below this share of the memory budget +static constexpr idx_t LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION = 4; + +//! Whether the build side is expected to exceed the memory budget and spill +static bool JoinIsLikelyExternal(ClientContext &context, const PhysicalHashJoin &op) { + if (Settings::Get(context)) { + return true; + } + idx_t row_width = sizeof(data_ptr_t) + sizeof(hash_t); // per-row pointer table and hash overhead + for (auto &type : op.children[1].get().GetTypes()) { + row_width += GetTypeIdSize(type.InternalType()); + } + const auto build_size = op.children[1].get().estimated_cardinality * row_width; + const auto memory_budget = BufferManager::GetBufferManager(context).GetMaxMemory(); + return build_size > memory_budget / LIKELY_IN_MEMORY_BUILD_MEMORY_FRACTION; +} + +//! Whether to accumulate a bloom filter during Sink. Only an external join needs that: its finalize only ever +//! materializes some partitions, so a filter built there would be incomplete, while every row passes through +//! Sink. An in-memory join builds its filter during finalize instead, where the actual build count and the +//! competing filter choices are known. The estimate-based checks mirror CanUseBloomFilter, which decides +//! publication once the actual build count is known. +static bool ShouldBuildBloomFilterInSink(ClientContext &context, const PhysicalHashJoin &op) { + if (!op.filter_pushdown || op.filter_pushdown->probe_info.empty()) { + return false; + } + idx_t equality_count = 0; + for (auto &cond : op.conditions) { + const auto cmp = cond.GetComparisonType(); + if (cmp == ExpressionType::COMPARE_EQUAL || cmp == ExpressionType::COMPARE_NOT_DISTINCT_FROM) { + equality_count++; + } + } + if (equality_count != 1) { + return false; + } + const auto build_estimate = static_cast(op.children[1].get().estimated_cardinality); + const auto probe_estimate = static_cast(op.children[0].get().estimated_cardinality); + if (build_estimate > BUILD_TO_PROBE_RATIO_THRESHOLD * probe_estimate) { + return false; + } + if (!op.filter_pushdown->build_side_has_filter && build_estimate > NON_FILTERING_RATIO_THRESHOLD * probe_estimate && + build_estimate > NON_FILTERING_BUILD_SIDE_THRESHOLD) { + return false; + } + return JoinIsLikelyExternal(context, op); +} + class HashJoinGlobalSinkState : public GlobalSinkState { public: HashJoinGlobalSinkState(const PhysicalHashJoin &op_p, ClientContext &context_p) @@ -384,6 +438,19 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } + InitializeBloomFilter(); + } + + //! Creates the bloom filter that every thread-local hash table inserts into during Sink + void InitializeBloomFilter() { + // a perfect hash join publishes its own exact filter, so a bloom filter would never leave the sink + if (skip_filter_pushdown || can_use_perfect_hash || !ShouldBuildBloomFilterInSink(context, op)) { + hash_table->SetBloomFilter(nullptr); + return; + } + auto filter = make_shared_ptr(); + filter->Initialize(context, MaxValue(op.children[1].get().estimated_cardinality, 1)); + hash_table->SetBloomFilter(std::move(filter)); } ~HashJoinGlobalSinkState() override { @@ -431,6 +498,7 @@ class HashJoinGlobalSinkState : public GlobalSinkState { } global_filter_state = op.filter_pushdown->GetGlobalState(context, op); } + InitializeBloomFilter(); // The next iteration's first chunk publishes the layout afresh: the rerun build may deliver a different // dictionary (or none), so neither a narrowed slot nor the dictionary it indexes into can be carried over. // ResetForNewIterationSinglePartition returned the global JHT to the unpublished state accordingly. @@ -514,6 +582,8 @@ class HashJoinLocalSinkState : public LocalSinkState { } hash_table = op.InitializeHashTable(context, gstate.hash_table->GetRadixBits()); + // every thread inserts into this one filter - InsertOne is an atomic fetch_or, so it needs no lock + hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); // sink_collection exists only after the layout is published on the first build chunk, so // InitializeAppendState runs lazily inside Sink. keep_hash_table = gstate.keep_local_hash_tables; @@ -561,6 +631,9 @@ class HashJoinLocalSinkState : public LocalSinkState { hash_table = op.InitializeHashTable(context.client, gstate.hash_table->GetRadixBits()); append_state_initialised = false; } + // the global state resets before any local one, so the filter this picks up is always a fresh one + D_ASSERT(!gstate.hash_table->GetBloomFilter() || !gstate.hash_table->GetBloomFilter()->IsSealed()); + hash_table->SetBloomFilter(gstate.hash_table->GetBloomFilter()); keep_hash_table = gstate.keep_local_hash_tables; gstate.active_local_states++; if (op.filter_pushdown) { @@ -1178,6 +1251,9 @@ class HashJoinFinalizeEvent : public BasePipelineEvent { } if (sink.global_filter_state) { + // a finalize-built bloom filter is complete once the tasks are done; a sink-built one is + // already sealed and this is a no-op + sink.hash_table->SealBloomFilter(); PublishDeferredRuntimeFilters(sink.context, *sink.hash_table, *sink.global_filter_state); } sink.hash_table->finalized = true; @@ -1229,6 +1305,24 @@ void HashJoinGlobalSinkState::InitializeProbeSpill() { } } +//! Computes and registers the runtime filters of an external join. Both external paths call this exactly once, +//! after the whole build side has been merged into the global hash table and before the finalize event is +//! scheduled. Every input is accumulated during Sink, so it does not matter which partitions are materialized. +static void FinalizeExternalRuntimeFilters(ClientContext &context, const PhysicalHashJoin &op, + HashJoinGlobalSinkState &sink) { + if (!op.filter_pushdown || sink.skip_filter_pushdown) { + return; + } + auto &ht = *sink.hash_table; + if (ht.GetSinkCollection().Count() == 0) { + return; + } + auto filter_min_max = op.filter_pushdown->FinalizeMinMax(*sink.global_filter_state); + // prefix-range filters are built from the materialized hash table, which only ever holds one round + op.filter_pushdown->FinalizeFilters(context, op, std::move(filter_min_max), &ht, true, false, + sink.global_filter_state.get()); +} + class HashJoinRepartitionTask : public ExecutorTask { public: HashJoinRepartitionTask(shared_ptr event_p, ClientContext &context, JoinHashTable &global_ht, @@ -1323,6 +1417,8 @@ class HashJoinRepartitionEvent : public BasePipelineEvent { sink.probe_side_requirement); sink.temporary_memory_state->UpdateReservation(executor.context); + FinalizeExternalRuntimeFilters(sink.context, op, sink); + D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - sink.probe_side_requirement); @@ -1361,9 +1457,6 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i return false; } - // building the bloom filter is costly on the build to make probing faster, - // so only use it if there are less build tuples than probing tuples - static constexpr double BUILD_TO_PROBE_RATIO_THRESHOLD = 1.0; auto build_count = ht->Count(); if (build_count == 0) { build_count = ht->GetSinkCollection().Count(); @@ -1378,8 +1471,6 @@ bool JoinFilterPushdownInfo::CanUseBloomFilter(const JoinFilterPushdownFilter &i } // if we have a build side without a filter, only build the bloom filter if the ratio/size is small enough - static constexpr double NON_FILTERING_RATIO_THRESHOLD = 0.1; - static constexpr idx_t NON_FILTERING_BUILD_SIDE_THRESHOLD = 4194304; if (!build_side_has_filter && build_to_probe_ratio > NON_FILTERING_RATIO_THRESHOLD && build_count > NON_FILTERING_BUILD_SIDE_THRESHOLD) { return false; @@ -1531,13 +1622,15 @@ static unique_ptr CreateRuntimeFilterExpression(ClientContext &conte unique_ptr filter_expr; switch (deferred.type) { case DeferredRuntimeFilterType::BLOOM_FILTER: { - D_ASSERT(ht.GetBloomFilter().IsInitialized()); - if (!ht.GetBloomFilter().IsInitialized()) { + // a filter that is not sealed does not describe the complete build side - publishing it would drop + // rows - and a filter that saturated is not worth the hashing it costs the scan + if (!ht.HasPublishableBloomFilter()) { return nullptr; } + auto bloom_filter = ht.GetBloomFilter(); filter_expr = make_uniq( BoundScalarFunction(BloomFilterScalarFun::GetFunction(filter_input_type)), std::move(children), - make_uniq(ht.GetBloomFilter(), filters_null_values, key_name, key_type, + make_uniq(std::move(bloom_filter), filters_null_values, key_name, key_type, selectivity_threshold, n_vectors_to_check)); break; } @@ -1851,9 +1944,17 @@ unique_ptr JoinFilterPushdownInfo::FinalizeFilters(ClientContext &con } if (allow_bloom_filters && can_emit_runtime_filters && ht && gstate && CanUseBloomFilter(info, context, op, cmp, ht)) { - ht->SetBuildBloomFilter(true); - DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, - filter_col_idx, *gstate); + if (ht->HasPublishableBloomFilter()) { + // a complete filter was accumulated during Sink (external joins build it there) + DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, + filter_col_idx, *gstate); + } else if (allow_prefix_range_filters) { + // like a prefix-range filter, a bloom filter may be built during finalize when the + // finalize materializes the whole build side - an external finalize does not + ht->SetBuildBloomFilter(true); + DeferRuntimeFilter(DeferredRuntimeFilterType::BLOOM_FILTER, op, info, pushdown_column, + filter_col_idx, *gstate); + } } } } @@ -1873,6 +1974,9 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl auto &sink = input.global_state.Cast(); auto &ht = *sink.hash_table; + // the filter covers the whole build side once Sink is done + ht.SealBloomFilter(); + sink.temporary_memory_state->UpdateReservation(context); sink.external = sink.temporary_memory_state->GetReservation() < sink.total_size; if (sink.external) { @@ -1927,12 +2031,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl } sink.local_hash_tables.clear(); sink.owned_local_hash_tables.clear(); - if (filter_pushdown && !sink.skip_filter_pushdown && ht.GetSinkCollection().Count() > 0) { - auto filter_min_max = filter_pushdown->FinalizeMinMax(*sink.global_filter_state); - filter_pushdown->FinalizeFilters(context, *this, std::move(filter_min_max), &ht, false, false, - sink.global_filter_state.get()); - } - ht.PrepareBloomFilterForFinalize(); + FinalizeExternalRuntimeFilters(context, *this, sink); D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - sink.probe_side_requirement); @@ -1989,7 +2088,6 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl // In case of a large build side or duplicates, use regular hash join if (!use_perfect_hash) { - ht.PrepareBloomFilterForFinalize(); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 4a5eb618b..44ac31f20 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha47887" +#define DUCKDB_PATCH_VERSION "0-alpha48043" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha47887" +#define DUCKDB_VERSION "v2.0.0-alpha48043" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "f702a1f801" +#define DUCKDB_SOURCE_ID "43690c453d" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp index 1fe4827a9..15860231e 100644 --- a/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp +++ b/src/duckdb/src/include/duckdb/execution/join_hashtable.hpp @@ -554,10 +554,12 @@ class JoinHashTable { //! An empty tuple that's a "dead end", can be used to stop chains early unsafe_unique_array dead_end; - //! Whether or not to use a bloom filter will be determined by the operator - BloomFilter bloom_filter; + //! Bloom filter over the join keys. An external join fills it during Sink, an in-memory join during + //! finalize. Shared by every thread-local hash table of this operator, and shared again with any scan it + //! gets published to, so it outlives a reset of this table + shared_ptr bloom_filter; + //! Whether finalize should build the bloom filter from the materialized build side bool should_build_bloom_filter = false; - idx_t bloom_filter_init_count = 0; unique_ptr prefix_range_filter; bool should_build_prefix_range_filter = false; @@ -639,14 +641,27 @@ class JoinHashTable { return PointerTableCapacity(count) * sizeof(data_ptr_t); } + void SetBloomFilter(shared_ptr filter) { + bloom_filter = std::move(filter); + } + //! Requests a filter built during finalize, from the materialized build side (in-memory joins only) void SetBuildBloomFilter(const bool should_build) { this->should_build_bloom_filter = should_build; } - void PrepareBloomFilterForFinalize(); - BloomFilter &GetBloomFilter() { + shared_ptr GetBloomFilter() { return bloom_filter; } + //! Marks the bloom filter complete. The reference stays, because the thread-local tables hold it too + void SealBloomFilter() { + if (bloom_filter) { + bloom_filter->Seal(context); + } + } + //! Whether the filter is complete and rejects enough for pushing it into a scan to pay off + bool HasPublishableBloomFilter() const { + return bloom_filter && bloom_filter->IsSealed() && bloom_filter->IsUseful(); + } void SetPrefixRangeFilter(unique_ptr filter) { prefix_range_filter = std::move(filter); diff --git a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp index 4bb415cb7..9ed0dc06b 100644 --- a/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp +++ b/src/duckdb/src/include/duckdb/execution/merge_sort_tree.hpp @@ -506,6 +506,7 @@ pair MergeSortTree::SelectNth(const SubFrames &fr // Go over all children until we found enough in range const auto *level_data = tree[level_no].first.data(); + const auto level_size = tree[level_no].first.size(); while (true) { idx_t matched = 0; std::array matches; @@ -526,7 +527,8 @@ pair MergeSortTree::SelectNth(const SubFrames &fr matched += idx_t(match.second - match.first); } - if (matched > n) { + // Move down if there are enough in this child, or if this is the last child of the level + if (matched > n || (result + 1) * level_width >= level_size) { // Too much in this level, so move down to leftmost child candidate within the cascade range for (idx_t f = 0; f < frames.size(); ++f) { auto &cascade_idx = cascades[f]; diff --git a/src/duckdb/src/include/duckdb/logging/log_type.hpp b/src/duckdb/src/include/duckdb/logging/log_type.hpp index 94bfbef13..bf8758d60 100644 --- a/src/duckdb/src/include/duckdb/logging/log_type.hpp +++ b/src/duckdb/src/include/duckdb/logging/log_type.hpp @@ -17,6 +17,7 @@ namespace duckdb { struct FileHandle; struct BaseRequest; struct HTTPResponse; +struct MetricTiming; class PhysicalOperator; enum class PhysicalOperatorType : uint8_t; class AttachedDatabase; @@ -125,7 +126,8 @@ class MetricsLogType : public LogType { static LogicalType GetLogType(); - static string ConstructLogMessage(const string &metric, const Value &value); + static string ConstructLogMessage(const string &metric, const Value &value, + optional_ptr timing = nullptr); }; class CheckpointLogType : public LogType { diff --git a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp index d9319c0db..9e0261a74 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/gathered_metrics.hpp @@ -12,6 +12,7 @@ #include "duckdb/common/enums/output_type.hpp" #include "duckdb/common/progress_bar/progress_bar.hpp" #include "duckdb/common/types/value.hpp" +#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unordered_set.hpp" #include "duckdb/common/constants.hpp" #include "duckdb/common/enums/metric_type.hpp" @@ -28,6 +29,41 @@ namespace duckdb { struct QueryProfileResult; enum class ProfilingParameterNames : uint8_t { FORMAT, COVERAGE, SAVE_LOCATION, MODE, METRICS }; +struct MetricTiming { + idx_t duration = 0; + timestamp_t start_time = timestamp_t::epoch(); + timestamp_t end_time = timestamp_t::epoch(); + bool has_bounds = false; + + void Add(idx_t addition) { + duration += addition; + } + + void Add(idx_t addition, timestamp_t start, timestamp_t end) { + Add(addition); + UpdateBounds(start, end); + } + + void Merge(const MetricTiming &other) { + Add(other.duration); + if (other.has_bounds) { + UpdateBounds(other.start_time, other.end_time); + } + } + +private: + void UpdateBounds(timestamp_t start, timestamp_t end) { + if (!has_bounds) { + start_time = start; + end_time = end; + has_bounds = true; + return; + } + start_time = MinValue(start_time, start); + end_time = MaxValue(end_time, end); + } +}; + class GatheredMetrics { public: GatheredMetrics() = default; @@ -72,7 +108,7 @@ class GatheredMetrics { } public: - void WriteMetricsToLog(ClientContext &context) const; + void WriteMetricsToLog(ClientContext &context, const unordered_map &timings) const; //! Copy all enabled metrics into a QueryProfileResult node using lowercase string keys void MetricsToProfileResult(QueryProfileResult &result) const; diff --git a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp index 383266135..fa5fdcc00 100644 --- a/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp +++ b/src/duckdb/src/include/duckdb/main/profiler/profiling_utils.hpp @@ -13,6 +13,7 @@ #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/profiler/profiling_node.hpp" #include "duckdb/common/profiler.hpp" +#include "duckdb/common/types/timestamp.hpp" namespace duckdb_yyjson { struct yyjson_mut_doc; @@ -44,7 +45,11 @@ struct QueryMetrics { public: void UpdateMetric(const string &key, idx_t addition) { - string_timings[key] += addition; + string_timings[key].Add(addition); + } + + void UpdateMetric(const string &key, idx_t addition, timestamp_t start_time, timestamp_t end_time) { + string_timings[key].Add(addition, start_time, end_time); } void UpdateMetricCounter(const string &key, idx_t addition) { @@ -68,7 +73,7 @@ struct QueryMetrics { if (it == string_timings.end()) { return 0.0; } - return static_cast(it->second) / 1e9; + return static_cast(it->second.duration) / 1e9; } idx_t GetStringCounter(const string &key) const { @@ -91,7 +96,7 @@ struct QueryMetrics { return total_memory_allocated.load(); } - const unordered_map &GetMetricTimings() const { + const unordered_map &GetMetricTimings() const { return string_timings; } @@ -119,7 +124,7 @@ struct QueryMetrics { void Merge(const QueryMetrics &other) { for (const auto &entry : other.string_timings) { - string_timings[entry.first] += entry.second; + string_timings[entry.first].Merge(entry.second); } for (const auto &entry : other.string_counters) { string_counters[entry.first] += entry.second; @@ -131,7 +136,7 @@ struct QueryMetrics { private: // String-keyed timings for optimizer, storage and phase timing metrics - unordered_map string_timings; + unordered_map string_timings; // String-keyed counters for storage counter metrics (e.g. "storage.wal_replay_entry_count") unordered_map string_counters; @@ -149,6 +154,7 @@ struct MetricsTimer { if (!is_active) { return; } + start_time = Timestamp::GetCurrentTimestamp(); profiler.Start(); } ~MetricsTimer() { @@ -164,12 +170,14 @@ struct MetricsTimer { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); + std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); } MetricsTimer &operator=(MetricsTimer &&other) noexcept { std::swap(query_metrics, other.query_metrics); std::swap(metric_name, other.metric_name); std::swap(profiler, other.profiler); + std::swap(start_time, other.start_time); std::swap(is_active, other.is_active); return *this; } @@ -181,7 +189,7 @@ struct MetricsTimer { } is_active = false; profiler.End(); - query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos()); + query_metrics->UpdateMetric(metric_name, profiler.ElapsedNanos(), start_time, Timestamp::GetCurrentTimestamp()); } void Reset() { @@ -196,6 +204,7 @@ struct MetricsTimer { optional_ptr query_metrics; string metric_name; Profiler profiler; + timestamp_t start_time = timestamp_t::epoch(); bool is_active; }; diff --git a/src/duckdb/src/include/duckdb/main/query_profiler.hpp b/src/duckdb/src/include/duckdb/main/query_profiler.hpp index 1ed2c856e..9446666a1 100644 --- a/src/duckdb/src/include/duckdb/main/query_profiler.hpp +++ b/src/duckdb/src/include/duckdb/main/query_profiler.hpp @@ -113,7 +113,7 @@ class QueryProfiler { //! Add to a metric counter (profiling-only). DUCKDB_API void AddToMetricCounter(const string &key, idx_t amount); //! Add parser time measured before the per-statement profiler was started. - void AddParserTime(const Profiler &parser_timer); + void AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time); //! Set an arbitrary metric value (profiling-only; no-op when profiling is disabled). DUCKDB_API void SetMetric(const string &key, Value new_value); diff --git a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp index cbece416e..a7a8bc886 100644 --- a/src/duckdb/src/include/duckdb/main/statement_iterator.hpp +++ b/src/duckdb/src/include/duckdb/main/statement_iterator.hpp @@ -9,6 +9,7 @@ #pragma once #include "duckdb/common/profiler.hpp" +#include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/unique_ptr.hpp" #include "duckdb/main/parse_iterator.hpp" @@ -62,6 +63,8 @@ class StatementIterator { unique_ptr pending_statement; //! Parser timing for the statement parsed ahead. Profiler parser_timer; + timestamp_t parser_start_time = timestamp_t::epoch(); + timestamp_t parser_end_time = timestamp_t::epoch(); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp index 5032d4713..42cd7a5c1 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp @@ -155,13 +155,14 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 121, 112, 101, 78, 97, 109, 101, 10, 84, 121, 112, 101, 77, 111, 100, 105, 102, 105, 101, 114, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 63, 41, 10, + 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, 82, 111, 119, 84, 121, 112, 101, 32, 60, 45, 32, 82, 111, 119, 79, 114, 83, 116, 114, 117, 99, 116, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 63, 10, - 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 84, 121, 112, 101, 10, + 83, 101, 116, 111, 102, 84, 121, 112, 101, 32, 60, 45, 32, 39, 83, 69, 84, 79, 70, 39, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, 85, 110, 105, 111, 110, 84, 121, 112, 101, 32, 60, 45, 32, 39, 85, 78, 73, 79, 78, 39, 32, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 10, 67, 111, 108, 73, 100, 84, 121, 112, 101, 76, 105, 115, 116, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 84, 121, 112, 101, 41, 41, 10, - 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 63, 10, - 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 84, 121, 112, 101, 41, 41, 10, - 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 84, 121, 112, 101, 10, + 77, 97, 112, 84, 121, 112, 101, 32, 60, 45, 32, 39, 77, 65, 80, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 63, 10, + 84, 117, 112, 108, 101, 84, 121, 112, 101, 32, 60, 45, 32, 39, 84, 85, 80, 76, 69, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 41, 41, 10, + 67, 111, 108, 73, 100, 84, 121, 112, 101, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 78, 101, 115, 116, 101, 100, 84, 121, 112, 101, 10, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 47, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 32, 47, 32, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 10, 65, 114, 114, 97, 121, 75, 101, 121, 119, 111, 114, 100, 87, 105, 116, 104, 66, 111, 117, 110, 100, 115, 32, 60, 45, 32, 39, 65, 82, 82, 65, 89, 39, 32, 83, 113, 117, 97, 114, 101, 66, 114, 97, 99, 107, 101, 116, 115, 65, 114, 114, 97, 121, 10, @@ -179,6 +180,364 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 37, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 32, 60, 45, 32, 91, 32, 92, 116, 92, 110, 92, 114, 93, 42, 10, 76, 105, 115, 116, 40, 68, 41, 32, 60, 45, 32, 68, 32, 40, 39, 44, 39, 32, 68, 41, 42, 32, 39, 44, 39, 63, 10, 80, 97, 114, 101, 110, 115, 40, 68, 41, 32, 60, 45, 32, 39, 40, 39, 32, 68, 32, 39, 41, 39, 10, + 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 97, 98, 111, 114, 116, 39, 32, 47, 10, + 39, 97, 98, 115, 111, 108, 117, 116, 101, 39, 32, 47, 10, + 39, 97, 99, 99, 101, 115, 115, 39, 32, 47, 10, + 39, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 97, 100, 100, 39, 32, 47, 10, + 39, 97, 100, 109, 105, 110, 39, 32, 47, 10, + 39, 97, 102, 116, 101, 114, 39, 32, 47, 10, + 39, 97, 103, 103, 114, 101, 103, 97, 116, 101, 39, 32, 47, 10, + 39, 97, 108, 115, 111, 39, 32, 47, 10, + 39, 97, 108, 116, 101, 114, 39, 32, 47, 10, + 39, 97, 108, 119, 97, 121, 115, 39, 32, 47, 10, + 39, 97, 112, 112, 114, 111, 120, 39, 32, 47, 10, + 39, 97, 115, 115, 101, 114, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 97, 115, 115, 105, 103, 110, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 97, 116, 116, 97, 99, 104, 39, 32, 47, 10, + 39, 97, 116, 116, 114, 105, 98, 117, 116, 101, 39, 32, 47, 10, + 39, 98, 97, 99, 107, 119, 97, 114, 100, 39, 32, 47, 10, + 39, 98, 101, 102, 111, 114, 101, 39, 32, 47, 10, + 39, 98, 101, 103, 105, 110, 39, 32, 47, 10, + 39, 98, 101, 116, 119, 101, 101, 110, 39, 32, 47, 10, + 39, 98, 105, 103, 105, 110, 116, 39, 32, 47, 10, + 39, 98, 105, 116, 39, 32, 47, 10, + 39, 98, 111, 111, 108, 101, 97, 110, 39, 32, 47, 10, + 39, 99, 97, 99, 104, 101, 39, 32, 47, 10, + 39, 99, 97, 108, 108, 39, 32, 47, 10, + 39, 99, 97, 108, 108, 101, 100, 39, 32, 47, 10, + 39, 99, 97, 115, 99, 97, 100, 101, 39, 32, 47, 10, + 39, 99, 97, 115, 99, 97, 100, 101, 100, 39, 32, 47, 10, + 39, 99, 97, 116, 97, 108, 111, 103, 39, 32, 47, 10, + 39, 99, 104, 97, 105, 110, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 39, 32, 47, 10, + 39, 99, 104, 97, 114, 97, 99, 116, 101, 114, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, + 39, 99, 104, 101, 99, 107, 112, 111, 105, 110, 116, 39, 32, 47, 10, + 39, 99, 108, 97, 115, 115, 39, 32, 47, 10, + 39, 99, 108, 111, 115, 101, 39, 32, 47, 10, + 39, 99, 108, 117, 115, 116, 101, 114, 39, 32, 47, 10, + 39, 99, 111, 97, 108, 101, 115, 99, 101, 39, 32, 47, 10, + 39, 99, 111, 108, 117, 109, 110, 115, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 101, 110, 116, 115, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 105, 116, 39, 32, 47, 10, + 39, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, + 39, 99, 111, 109, 112, 114, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 102, 105, 103, 117, 114, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 102, 108, 105, 99, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 110, 115, 116, 114, 97, 105, 110, 116, 115, 39, 32, 47, 10, + 39, 99, 111, 110, 116, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 111, 110, 116, 105, 110, 117, 101, 39, 32, 47, 10, + 39, 99, 111, 110, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 99, 111, 112, 121, 39, 32, 47, 10, + 39, 99, 111, 115, 116, 39, 32, 47, 10, + 39, 99, 115, 118, 39, 32, 47, 10, + 39, 99, 117, 98, 101, 39, 32, 47, 10, + 39, 99, 117, 114, 114, 101, 110, 116, 39, 32, 47, 10, + 39, 99, 117, 114, 115, 111, 114, 39, 32, 47, 10, + 39, 99, 121, 99, 108, 101, 39, 32, 47, 10, + 39, 100, 97, 116, 97, 39, 32, 47, 10, + 39, 100, 97, 116, 97, 98, 97, 115, 101, 39, 32, 47, 10, + 39, 100, 101, 97, 108, 108, 111, 99, 97, 116, 101, 39, 32, 47, 10, + 39, 100, 101, 99, 39, 32, 47, 10, + 39, 100, 101, 99, 105, 109, 97, 108, 39, 32, 47, 10, + 39, 100, 101, 99, 108, 97, 114, 101, 39, 32, 47, 10, + 39, 100, 101, 102, 97, 117, 108, 116, 115, 39, 32, 47, 10, + 39, 100, 101, 102, 101, 114, 114, 101, 100, 39, 32, 47, 10, + 39, 100, 101, 102, 105, 110, 101, 114, 39, 32, 47, 10, + 39, 100, 101, 108, 101, 116, 101, 39, 32, 47, 10, + 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 39, 32, 47, 10, + 39, 100, 101, 108, 105, 109, 105, 116, 101, 114, 115, 39, 32, 47, 10, + 39, 100, 101, 112, 101, 110, 100, 115, 39, 32, 47, 10, + 39, 100, 101, 115, 116, 114, 111, 121, 39, 32, 47, 10, + 39, 100, 101, 116, 97, 99, 104, 39, 32, 47, 10, + 39, 100, 105, 99, 116, 105, 111, 110, 97, 114, 121, 39, 32, 47, 10, + 39, 100, 105, 115, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 100, 105, 115, 99, 97, 114, 100, 39, 32, 47, 10, + 39, 100, 105, 115, 99, 111, 110, 110, 101, 99, 116, 39, 32, 47, 10, + 39, 100, 105, 115, 116, 97, 110, 99, 101, 39, 32, 47, 10, + 39, 100, 111, 99, 117, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 100, 111, 109, 97, 105, 110, 39, 32, 47, 10, + 39, 100, 111, 117, 98, 108, 101, 39, 32, 47, 10, + 39, 100, 114, 111, 112, 39, 32, 47, 10, + 39, 101, 97, 99, 104, 39, 32, 47, 10, + 39, 101, 110, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 101, 110, 99, 111, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, + 39, 101, 110, 117, 109, 39, 32, 47, 10, + 39, 101, 114, 114, 111, 114, 39, 32, 47, 10, + 39, 101, 118, 101, 110, 116, 39, 32, 47, 10, + 39, 101, 120, 97, 99, 116, 39, 32, 47, 10, + 39, 101, 120, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 101, 120, 99, 108, 117, 115, 105, 118, 101, 39, 32, 47, 10, + 39, 101, 120, 101, 99, 117, 116, 101, 39, 32, 47, 10, + 39, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, + 39, 101, 120, 112, 108, 97, 105, 110, 39, 32, 47, 10, + 39, 101, 120, 112, 111, 114, 116, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 101, 120, 116, 101, 114, 110, 97, 108, 39, 32, 47, 10, + 39, 101, 120, 116, 114, 97, 99, 116, 39, 32, 47, 10, + 39, 102, 97, 109, 105, 108, 121, 39, 32, 47, 10, + 39, 102, 105, 114, 115, 116, 39, 32, 47, 10, + 39, 102, 108, 111, 97, 116, 39, 32, 47, 10, + 39, 102, 111, 108, 108, 111, 119, 105, 110, 103, 39, 32, 47, 10, + 39, 102, 111, 114, 99, 101, 39, 32, 47, 10, + 39, 102, 111, 114, 119, 97, 114, 100, 39, 32, 47, 10, + 39, 102, 117, 110, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 102, 117, 110, 99, 116, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 103, 101, 110, 101, 114, 97, 116, 101, 100, 39, 32, 47, 10, + 39, 103, 108, 111, 98, 97, 108, 39, 32, 47, 10, + 39, 103, 114, 97, 110, 116, 39, 32, 47, 10, + 39, 103, 114, 97, 110, 116, 101, 100, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 105, 110, 103, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 105, 110, 103, 95, 105, 100, 39, 32, 47, 10, + 39, 103, 114, 111, 117, 112, 115, 39, 32, 47, 10, + 39, 104, 97, 110, 100, 108, 101, 114, 39, 32, 47, 10, + 39, 104, 101, 97, 100, 101, 114, 39, 32, 47, 10, + 39, 104, 111, 108, 100, 39, 32, 47, 10, + 39, 105, 100, 101, 110, 116, 105, 116, 121, 39, 32, 47, 10, + 39, 105, 102, 39, 32, 47, 10, + 39, 105, 109, 109, 101, 100, 105, 97, 116, 101, 39, 32, 47, 10, + 39, 105, 109, 109, 117, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 105, 109, 112, 108, 105, 99, 105, 116, 39, 32, 47, 10, + 39, 105, 109, 112, 111, 114, 116, 39, 32, 47, 10, + 39, 105, 110, 99, 108, 117, 100, 101, 39, 32, 47, 10, + 39, 105, 110, 99, 108, 117, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 105, 110, 99, 114, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 105, 110, 100, 101, 120, 39, 32, 47, 10, + 39, 105, 110, 100, 101, 120, 101, 115, 39, 32, 47, 10, + 39, 105, 110, 104, 101, 114, 105, 116, 39, 32, 47, 10, + 39, 105, 110, 104, 101, 114, 105, 116, 115, 39, 32, 47, 10, + 39, 105, 110, 108, 105, 110, 101, 39, 32, 47, 10, + 39, 105, 110, 111, 117, 116, 39, 32, 47, 10, + 39, 105, 110, 112, 117, 116, 39, 32, 47, 10, + 39, 105, 110, 115, 101, 110, 115, 105, 116, 105, 118, 101, 39, 32, 47, 10, + 39, 105, 110, 115, 101, 114, 116, 39, 32, 47, 10, + 39, 105, 110, 115, 116, 97, 108, 108, 39, 32, 47, 10, + 39, 105, 110, 115, 116, 101, 97, 100, 39, 32, 47, 10, + 39, 105, 110, 116, 39, 32, 47, 10, + 39, 105, 110, 116, 101, 103, 101, 114, 39, 32, 47, 10, + 39, 105, 110, 116, 101, 114, 118, 97, 108, 39, 32, 47, 10, + 39, 105, 110, 118, 111, 107, 101, 114, 39, 32, 47, 10, + 39, 105, 115, 111, 108, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 106, 115, 111, 110, 39, 32, 47, 10, + 39, 107, 101, 121, 39, 32, 47, 10, + 39, 108, 97, 98, 101, 108, 39, 32, 47, 10, + 39, 108, 97, 110, 103, 117, 97, 103, 101, 39, 32, 47, 10, + 39, 108, 97, 114, 103, 101, 39, 32, 47, 10, + 39, 108, 97, 115, 116, 39, 32, 47, 10, + 39, 108, 101, 97, 107, 112, 114, 111, 111, 102, 39, 32, 47, 10, + 39, 108, 101, 118, 101, 108, 39, 32, 47, 10, + 39, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, + 39, 108, 111, 97, 100, 39, 32, 47, 10, + 39, 108, 111, 99, 97, 108, 39, 32, 47, 10, + 39, 108, 111, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 108, 111, 99, 107, 39, 32, 47, 10, + 39, 108, 111, 99, 107, 101, 100, 39, 32, 47, 10, + 39, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 99, 114, 111, 39, 32, 47, 10, + 39, 109, 97, 112, 39, 32, 47, 10, + 39, 109, 97, 112, 112, 105, 110, 103, 39, 32, 47, 10, + 39, 109, 97, 116, 99, 104, 39, 32, 47, 10, + 39, 109, 97, 116, 99, 104, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 116, 101, 114, 105, 97, 108, 105, 122, 101, 100, 39, 32, 47, 10, + 39, 109, 97, 120, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 109, 101, 114, 103, 101, 39, 32, 47, 10, + 39, 109, 101, 116, 104, 111, 100, 39, 32, 47, 10, + 39, 109, 105, 110, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 109, 111, 100, 101, 39, 32, 47, 10, + 39, 109, 111, 118, 101, 39, 32, 47, 10, + 39, 110, 97, 109, 101, 39, 32, 47, 10, + 39, 110, 97, 109, 101, 115, 39, 32, 47, 10, + 39, 110, 97, 116, 105, 111, 110, 97, 108, 39, 32, 47, 10, + 39, 110, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 110, 101, 97, 114, 101, 115, 116, 39, 32, 47, 10, + 39, 110, 101, 119, 39, 32, 47, 10, + 39, 110, 101, 120, 116, 39, 32, 47, 10, + 39, 110, 111, 39, 32, 47, 10, + 39, 110, 111, 110, 101, 39, 32, 47, 10, + 39, 110, 111, 116, 104, 105, 110, 103, 39, 32, 47, 10, + 39, 110, 111, 116, 105, 102, 121, 39, 32, 47, 10, + 39, 110, 111, 119, 97, 105, 116, 39, 32, 47, 10, + 39, 110, 117, 108, 108, 105, 102, 39, 32, 47, 10, + 39, 110, 117, 108, 108, 115, 39, 32, 47, 10, + 39, 110, 117, 109, 101, 114, 105, 99, 39, 32, 47, 10, + 39, 111, 98, 106, 101, 99, 116, 39, 32, 47, 10, + 39, 111, 102, 39, 32, 47, 10, + 39, 111, 102, 102, 39, 32, 47, 10, + 39, 111, 105, 100, 115, 39, 32, 47, 10, + 39, 111, 108, 100, 39, 32, 47, 10, + 39, 111, 112, 101, 114, 97, 116, 111, 114, 39, 32, 47, 10, + 39, 111, 112, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 111, 112, 116, 105, 111, 110, 115, 39, 32, 47, 10, + 39, 111, 114, 100, 105, 110, 97, 108, 105, 116, 121, 39, 32, 47, 10, + 39, 111, 116, 104, 101, 114, 115, 39, 32, 47, 10, + 39, 111, 117, 116, 39, 32, 47, 10, + 39, 111, 118, 101, 114, 108, 97, 121, 39, 32, 47, 10, + 39, 111, 118, 101, 114, 114, 105, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 111, 119, 110, 101, 100, 39, 32, 47, 10, + 39, 111, 119, 110, 101, 114, 39, 32, 47, 10, + 39, 112, 97, 114, 97, 108, 108, 101, 108, 39, 32, 47, 10, + 39, 112, 97, 114, 115, 101, 114, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 97, 108, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 112, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 39, 32, 47, 10, + 39, 112, 97, 115, 115, 105, 110, 103, 39, 32, 47, 10, + 39, 112, 97, 115, 115, 119, 111, 114, 100, 39, 32, 47, 10, + 39, 112, 101, 114, 99, 101, 110, 116, 39, 32, 47, 10, + 39, 112, 101, 114, 115, 105, 115, 116, 101, 110, 116, 39, 32, 47, 10, + 39, 112, 108, 97, 110, 115, 39, 32, 47, 10, + 39, 112, 111, 108, 105, 99, 121, 39, 32, 47, 10, + 39, 112, 111, 115, 105, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 112, 114, 97, 103, 109, 97, 39, 32, 47, 10, + 39, 112, 114, 101, 99, 101, 100, 105, 110, 103, 39, 32, 47, 10, + 39, 112, 114, 101, 112, 97, 114, 101, 39, 32, 47, 10, + 39, 112, 114, 101, 112, 97, 114, 101, 100, 39, 32, 47, 10, + 39, 112, 114, 101, 115, 101, 114, 118, 101, 39, 32, 47, 10, + 39, 112, 114, 105, 111, 114, 39, 32, 47, 10, + 39, 112, 114, 105, 118, 105, 108, 101, 103, 101, 115, 39, 32, 47, 10, + 39, 112, 114, 111, 99, 101, 100, 117, 114, 97, 108, 39, 32, 47, 10, + 39, 112, 114, 111, 99, 101, 100, 117, 114, 101, 39, 32, 47, 10, + 39, 112, 114, 111, 103, 114, 97, 109, 39, 32, 47, 10, + 39, 112, 117, 98, 108, 105, 99, 97, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 113, 117, 111, 116, 101, 39, 32, 47, 10, + 39, 114, 97, 110, 103, 101, 39, 32, 47, 10, + 39, 114, 101, 97, 100, 39, 32, 47, 10, + 39, 114, 101, 97, 108, 39, 32, 47, 10, + 39, 114, 101, 97, 115, 115, 105, 103, 110, 39, 32, 47, 10, + 39, 114, 101, 99, 104, 101, 99, 107, 39, 32, 47, 10, + 39, 114, 101, 99, 117, 114, 115, 105, 118, 101, 39, 32, 47, 10, + 39, 114, 101, 102, 39, 32, 47, 10, + 39, 114, 101, 102, 101, 114, 101, 110, 99, 105, 110, 103, 39, 32, 47, 10, + 39, 114, 101, 102, 114, 101, 115, 104, 39, 32, 47, 10, + 39, 114, 101, 103, 105, 115, 116, 101, 114, 39, 32, 47, 10, + 39, 114, 101, 105, 110, 100, 101, 120, 39, 32, 47, 10, + 39, 114, 101, 108, 97, 116, 105, 118, 101, 39, 32, 47, 10, + 39, 114, 101, 108, 101, 97, 115, 101, 39, 32, 47, 10, + 39, 114, 101, 112, 101, 97, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 114, 101, 112, 108, 105, 99, 97, 39, 32, 47, 10, + 39, 114, 101, 115, 101, 116, 39, 32, 47, 10, + 39, 114, 101, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, + 39, 114, 101, 115, 111, 117, 114, 99, 101, 115, 39, 32, 47, 10, + 39, 114, 101, 115, 116, 97, 114, 116, 39, 32, 47, 10, + 39, 114, 101, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, + 39, 114, 101, 116, 117, 114, 110, 115, 39, 32, 47, 10, + 39, 114, 101, 118, 111, 107, 101, 39, 32, 47, 10, + 39, 114, 111, 108, 101, 39, 32, 47, 10, + 39, 114, 111, 108, 108, 98, 97, 99, 107, 39, 32, 47, 10, + 39, 114, 111, 108, 108, 117, 112, 39, 32, 47, 10, + 39, 114, 111, 119, 39, 32, 47, 10, + 39, 114, 111, 119, 115, 39, 32, 47, 10, + 39, 114, 117, 108, 101, 39, 32, 47, 10, + 39, 115, 97, 109, 112, 108, 101, 39, 32, 47, 10, + 39, 115, 97, 118, 101, 112, 111, 105, 110, 116, 39, 32, 47, 10, + 39, 115, 99, 104, 101, 109, 97, 39, 32, 47, 10, + 39, 115, 99, 104, 101, 109, 97, 115, 39, 32, 47, 10, + 39, 115, 99, 111, 112, 101, 39, 32, 47, 10, + 39, 115, 99, 114, 111, 108, 108, 39, 32, 47, 10, + 39, 115, 101, 97, 114, 99, 104, 39, 32, 47, 10, + 39, 115, 101, 99, 114, 101, 116, 39, 32, 47, 10, + 39, 115, 101, 99, 117, 114, 101, 39, 32, 47, 10, + 39, 115, 101, 99, 117, 114, 105, 116, 121, 39, 32, 47, 10, + 39, 115, 101, 113, 117, 101, 110, 99, 101, 39, 32, 47, 10, + 39, 115, 101, 113, 117, 101, 110, 99, 101, 115, 39, 32, 47, 10, + 39, 115, 101, 114, 105, 97, 108, 105, 122, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 115, 101, 114, 118, 101, 114, 39, 32, 47, 10, + 39, 115, 101, 115, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 115, 101, 116, 39, 32, 47, 10, + 39, 115, 101, 116, 111, 102, 39, 32, 47, 10, + 39, 115, 101, 116, 115, 39, 32, 47, 10, + 39, 115, 104, 97, 114, 101, 39, 32, 47, 10, + 39, 115, 105, 109, 105, 108, 97, 114, 105, 116, 121, 39, 32, 47, 10, + 39, 115, 105, 109, 112, 108, 101, 39, 32, 47, 10, + 39, 115, 107, 105, 112, 39, 32, 47, 10, + 39, 115, 109, 97, 108, 108, 105, 110, 116, 39, 32, 47, 10, + 39, 115, 110, 97, 112, 115, 104, 111, 116, 39, 32, 47, 10, + 39, 115, 111, 114, 116, 101, 100, 39, 32, 47, 10, + 39, 115, 111, 117, 114, 99, 101, 39, 32, 47, 10, + 39, 115, 113, 108, 39, 32, 47, 10, + 39, 115, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 115, 116, 97, 110, 100, 97, 108, 111, 110, 101, 39, 32, 47, 10, + 39, 115, 116, 97, 114, 116, 39, 32, 47, 10, + 39, 115, 116, 97, 116, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 115, 116, 97, 116, 105, 115, 116, 105, 99, 115, 39, 32, 47, 10, + 39, 115, 116, 100, 105, 110, 39, 32, 47, 10, + 39, 115, 116, 100, 111, 117, 116, 39, 32, 47, 10, + 39, 115, 116, 111, 114, 97, 103, 101, 39, 32, 47, 10, + 39, 115, 116, 111, 114, 101, 100, 39, 32, 47, 10, + 39, 115, 116, 114, 105, 99, 116, 39, 32, 47, 10, + 39, 115, 116, 114, 105, 112, 39, 32, 47, 10, + 39, 115, 116, 114, 117, 99, 116, 39, 32, 47, 10, + 39, 115, 117, 98, 115, 99, 114, 105, 112, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 115, 117, 98, 115, 116, 114, 105, 110, 103, 39, 32, 47, 10, + 39, 115, 121, 115, 105, 100, 39, 32, 47, 10, + 39, 115, 121, 115, 116, 101, 109, 39, 32, 47, 10, + 39, 116, 97, 98, 108, 101, 115, 39, 32, 47, 10, + 39, 116, 97, 98, 108, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, + 39, 116, 97, 114, 103, 101, 116, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 108, 97, 116, 101, 39, 32, 47, 10, + 39, 116, 101, 109, 112, 111, 114, 97, 114, 121, 39, 32, 47, 10, + 39, 116, 101, 120, 116, 39, 32, 47, 10, + 39, 116, 105, 101, 115, 39, 32, 47, 10, + 39, 116, 105, 109, 101, 39, 32, 47, 10, + 39, 116, 105, 109, 101, 115, 116, 97, 109, 112, 39, 32, 47, 10, + 39, 116, 114, 97, 110, 115, 97, 99, 116, 105, 111, 110, 39, 32, 47, 10, + 39, 116, 114, 97, 110, 115, 102, 111, 114, 109, 39, 32, 47, 10, + 39, 116, 114, 101, 97, 116, 39, 32, 47, 10, + 39, 116, 114, 105, 103, 103, 101, 114, 39, 32, 47, 10, + 39, 116, 114, 105, 109, 39, 32, 47, 10, + 39, 116, 114, 117, 110, 99, 97, 116, 101, 39, 32, 47, 10, + 39, 116, 114, 117, 115, 116, 101, 100, 39, 32, 47, 10, + 39, 116, 114, 121, 95, 99, 97, 115, 116, 39, 32, 47, 10, + 39, 116, 117, 112, 108, 101, 39, 32, 47, 10, + 39, 116, 121, 112, 101, 39, 32, 47, 10, + 39, 116, 121, 112, 101, 115, 39, 32, 47, 10, + 39, 117, 110, 98, 111, 117, 110, 100, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 99, 111, 109, 109, 105, 116, 116, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 101, 110, 99, 114, 121, 112, 116, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 107, 110, 111, 119, 110, 39, 32, 47, 10, + 39, 117, 110, 108, 105, 115, 116, 101, 110, 39, 32, 47, 10, + 39, 117, 110, 108, 111, 103, 103, 101, 100, 39, 32, 47, 10, + 39, 117, 110, 116, 105, 108, 39, 32, 47, 10, + 39, 117, 112, 100, 97, 116, 101, 39, 32, 47, 10, + 39, 117, 115, 101, 39, 32, 47, 10, + 39, 117, 115, 101, 114, 39, 32, 47, 10, + 39, 118, 97, 99, 117, 117, 109, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 97, 116, 101, 39, 32, 47, 10, + 39, 118, 97, 108, 105, 100, 97, 116, 111, 114, 39, 32, 47, 10, + 39, 118, 97, 108, 117, 101, 39, 32, 47, 10, + 39, 118, 97, 108, 117, 101, 115, 39, 32, 47, 10, + 39, 118, 97, 114, 99, 104, 97, 114, 39, 32, 47, 10, + 39, 118, 97, 114, 105, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 118, 101, 114, 115, 105, 111, 110, 39, 32, 47, 10, + 39, 118, 105, 101, 119, 39, 32, 47, 10, + 39, 118, 105, 101, 119, 115, 39, 32, 47, 10, + 39, 118, 105, 114, 116, 117, 97, 108, 39, 32, 47, 10, + 39, 118, 111, 108, 97, 116, 105, 108, 101, 39, 32, 47, 10, + 39, 119, 104, 105, 116, 101, 115, 112, 97, 99, 101, 39, 32, 47, 10, + 39, 119, 111, 114, 107, 39, 32, 47, 10, + 39, 119, 114, 97, 112, 112, 101, 114, 39, 32, 47, 10, + 39, 119, 114, 105, 116, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 39, 32, 47, 10, + 39, 120, 109, 108, 97, 116, 116, 114, 105, 98, 117, 116, 101, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 99, 111, 110, 99, 97, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 101, 108, 101, 109, 101, 110, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 101, 120, 105, 115, 116, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 102, 111, 114, 101, 115, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 110, 97, 109, 101, 115, 112, 97, 99, 101, 115, 39, 32, 47, 10, + 39, 120, 109, 108, 112, 97, 114, 115, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 112, 105, 39, 32, 47, 10, + 39, 120, 109, 108, 114, 111, 111, 116, 39, 32, 47, 10, + 39, 120, 109, 108, 115, 101, 114, 105, 97, 108, 105, 122, 101, 39, 32, 47, 10, + 39, 120, 109, 108, 116, 97, 98, 108, 101, 39, 32, 47, 10, + 39, 121, 101, 115, 39, 32, 47, 10, + 39, 122, 111, 110, 101, 39, 10, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 60, 45, 32, 39, 66, 69, 84, 87, 69, 69, 78, 39, 32, 47, 10, 39, 66, 73, 71, 73, 78, 84, 39, 32, 47, 10, 39, 66, 73, 84, 39, 32, 47, 10, @@ -718,7 +1077,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 83, 116, 109, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 76, 105, 115, 116, 40, 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 41, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 83, 116, 109, 116, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 10, 65, 108, 116, 101, 114, 83, 99, 104, 101, 109, 97, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, - 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 67, 104, 97, 110, 103, 101, 78, 117, 108, 108, 97, 98, 105, 108, 105, 116, 121, 32, 47, 10, + 65, 108, 116, 101, 114, 84, 97, 98, 108, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 65, 100, 100, 67, 111, 108, 117, 109, 110, 32, 47, 32, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 32, 68, 114, 111, 112, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 108, 116, 101, 114, 67, 111, 108, 117, 109, 110, 32, 47, 32, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 47, 10, 32, 32, 32, 32, 32, 32, 32, 32, 82, 101, 110, 97, 109, 101, 67, 111, 108, 117, 109, 110, 32, 47, 32, 82, 101, 110, 97, 109, 101, 65, 108, 116, 101, 114, 32, 47, 32, 83, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 80, 97, 114, 116, 105, 116, 105, 111, 110, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 82, 101, 115, 101, 116, 83, 111, 114, 116, 101, 100, 66, 121, 32, 47, 32, 83, 101, 116, 79, 112, 116, 105, 111, 110, 115, 32, 47, 32, 82, 101, 115, 101, 116, 79, 112, 116, 105, 111, 110, 115, 10, 65, 100, 100, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 65, 68, 68, 39, 32, 84, 111, 112, 76, 101, 118, 101, 108, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 10, 68, 114, 111, 112, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 32, 60, 45, 32, 39, 68, 82, 79, 80, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, @@ -791,7 +1150,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 97, 116, 97, 108, 111, 103, 83, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, 67, 111, 112, 121, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 67, 79, 80, 89, 39, 32, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 10, 67, 111, 112, 121, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 60, 45, 32, 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 47, 32, 67, 111, 112, 121, 83, 101, 108, 101, 99, 116, 32, 47, 32, 67, 111, 112, 121, 70, 114, 111, 109, 68, 97, 116, 97, 98, 97, 115, 101, 10, - 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, + 67, 111, 112, 121, 84, 97, 98, 108, 101, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 70, 114, 111, 109, 79, 114, 84, 111, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 32, 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 63, 10, 70, 114, 111, 109, 79, 114, 84, 111, 32, 60, 45, 32, 67, 111, 112, 121, 70, 114, 111, 109, 32, 47, 32, 67, 111, 112, 121, 84, 111, 10, 67, 111, 112, 121, 70, 114, 111, 109, 32, 60, 45, 32, 39, 70, 82, 79, 77, 39, 10, 67, 111, 112, 121, 84, 111, 32, 60, 45, 32, 39, 84, 79, 39, 10, @@ -803,6 +1162,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 10, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 67, 111, 108, 73, 100, 32, 60, 45, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 43, 10, 67, 111, 112, 121, 70, 105, 108, 101, 78, 97, 109, 101, 83, 117, 102, 102, 105, 120, 32, 60, 45, 32, 39, 46, 39, 32, 67, 111, 108, 73, 100, 10, + 67, 111, 112, 121, 76, 101, 103, 97, 99, 121, 79, 112, 116, 105, 111, 110, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 63, 32, 39, 68, 69, 76, 73, 77, 73, 84, 69, 82, 83, 39, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 63, 32, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 67, 111, 112, 121, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 67, 111, 112, 121, 71, 101, 110, 101, 114, 105, 99, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 47, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 10, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 76, 105, 115, 116, 32, 60, 45, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 32, 83, 112, 101, 99, 105, 97, 108, 105, 122, 101, 100, 79, 112, 116, 105, 111, 110, 84, 97, 105, 108, 42, 10, @@ -984,6 +1344,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 82, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 85, 110, 114, 101, 115, 101, 114, 118, 101, 100, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 70, 117, 110, 99, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 84, 121, 112, 101, 78, 97, 109, 101, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 76, 97, 98, 101, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 67, 111, 108, 76, 97, 98, 101, 108, 10, + 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 32, 60, 45, 32, 66, 97, 114, 101, 76, 97, 98, 101, 108, 75, 101, 121, 119, 111, 114, 100, 32, 47, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 63, 32, 39, 65, 83, 39, 32, 80, 97, 114, 101, 110, 115, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 67, 111, 108, 117, 109, 110, 84, 121, 112, 101, 63, 10, 71, 101, 110, 101, 114, 97, 116, 101, 100, 32, 60, 45, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 65, 108, 119, 97, 121, 115, 32, 47, 32, 71, 101, 110, 101, 114, 97, 116, 101, 100, 66, 121, 68, 101, 102, 97, 117, 108, 116, 10, @@ -1027,7 +1388,11 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 68, 69, 65, 76, 76, 79, 67, 65, 84, 69, 39, 32, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 63, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 10, 68, 101, 97, 108, 108, 111, 99, 97, 116, 101, 80, 114, 101, 112, 97, 114, 101, 32, 60, 45, 32, 39, 80, 82, 69, 80, 65, 82, 69, 39, 10, 68, 101, 108, 101, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 68, 69, 76, 69, 84, 69, 39, 32, 39, 70, 82, 79, 77, 39, 32, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, - 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 82, 85, 78, 67, 65, 84, 69, 39, 32, 39, 84, 65, 66, 76, 69, 39, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 63, 10, + 35, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 102, 105, 114, 115, 116, 44, 32, 115, 111, 32, 67, 65, 83, 67, 65, 68, 69, 47, 82, 69, 83, 84, 82, 73, 67, 84, 32, 97, 114, 101, 32, 110, 111, 116, 32, 99, 111, 110, 115, 117, 109, 101, 100, 32, 97, 115, 32, 97, 110, 32, 97, 108, 105, 97, 115, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 47, 32, 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 65, 108, 105, 97, 115, 79, 112, 116, 105, 111, 110, 115, 32, 60, 45, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 63, 10, + 84, 114, 117, 110, 99, 97, 116, 101, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 32, 60, 45, 32, 68, 114, 111, 112, 66, 101, 104, 97, 118, 105, 111, 114, 10, 84, 97, 114, 103, 101, 116, 79, 112, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 63, 10, 84, 97, 114, 103, 101, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, 68, 101, 108, 101, 116, 101, 85, 115, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 10, @@ -1421,10 +1786,11 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 77, 101, 116, 104, 111, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 68, 105, 115, 116, 105, 110, 99, 116, 79, 114, 65, 108, 108, 63, 32, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 63, 32, 79, 114, 100, 101, 114, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 73, 103, 110, 111, 114, 101, 79, 114, 82, 101, 115, 112, 101, 99, 116, 78, 117, 108, 108, 115, 63, 10, 77, 101, 116, 104, 111, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 10, 83, 108, 105, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 91, 39, 32, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 39, 93, 39, 10, - 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 63, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 86, 97, 108, 117, 101, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 10, - 69, 110, 100, 83, 108, 105, 99, 101, 77, 105, 110, 117, 115, 32, 60, 45, 32, 39, 45, 39, 10, + 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 63, 10, + 83, 108, 105, 99, 101, 85, 112, 112, 101, 114, 66, 111, 117, 110, 100, 32, 60, 45, 32, 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 47, 32, 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, + 77, 105, 110, 117, 115, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 39, 45, 39, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 10, + 69, 110, 100, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 63, 10, + 69, 110, 100, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 83, 116, 101, 112, 83, 108, 105, 99, 101, 66, 111, 117, 110, 100, 32, 60, 45, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 80, 111, 115, 116, 102, 105, 120, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 32, 39, 33, 39, 10, 83, 112, 101, 99, 105, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 97, 108, 101, 115, 99, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 85, 110, 112, 97, 99, 107, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 67, 111, 108, 117, 109, 110, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 116, 114, 97, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 76, 97, 109, 98, 100, 97, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 78, 117, 108, 108, 73, 102, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 82, 111, 119, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 83, 117, 98, 115, 116, 114, 105, 110, 103, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 84, 114, 105, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 79, 118, 101, 114, 108, 97, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, @@ -1491,7 +1857,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 83, 111, 117, 114, 99, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 47, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 78, 69, 87, 39, 32, 39, 84, 69, 77, 80, 79, 82, 65, 82, 89, 39, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 67, 114, 101, 97, 116, 105, 111, 110, 79, 112, 116, 105, 111, 110, 115, 63, 10, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 82, 101, 102, 101, 114, 101, 110, 99, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 69, 120, 116, 101, 114, 110, 97, 108, 82, 101, 115, 111, 117, 114, 99, 101, 75, 101, 121, 119, 111, 114, 100, 32, 67, 111, 108, 73, 100, 10, - 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 73, 110, 115, 101, 114, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 73, 78, 83, 69, 82, 84, 39, 32, 79, 114, 65, 99, 116, 105, 111, 110, 63, 32, 39, 73, 78, 84, 79, 39, 32, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 66, 121, 78, 97, 109, 101, 79, 114, 80, 111, 115, 105, 116, 105, 111, 110, 63, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 73, 110, 115, 101, 114, 116, 86, 97, 108, 117, 101, 115, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 79, 114, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 47, 32, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 10, 73, 110, 115, 101, 114, 116, 79, 114, 82, 101, 112, 108, 97, 99, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 82, 69, 80, 76, 65, 67, 69, 39, 10, 73, 110, 115, 101, 114, 116, 79, 114, 73, 103, 110, 111, 114, 101, 32, 60, 45, 32, 39, 79, 82, 39, 32, 39, 73, 71, 78, 79, 82, 69, 39, 10, @@ -1500,6 +1866,8 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 79, 114, 100, 101, 114, 32, 60, 45, 32, 39, 66, 89, 39, 32, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 10, 73, 110, 115, 101, 114, 116, 66, 121, 78, 97, 109, 101, 32, 60, 45, 32, 39, 78, 65, 77, 69, 39, 10, 73, 110, 115, 101, 114, 116, 66, 121, 80, 111, 115, 105, 116, 105, 111, 110, 32, 60, 45, 32, 39, 80, 79, 83, 73, 84, 73, 79, 78, 39, 10, + 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 79, 86, 69, 82, 82, 73, 68, 73, 78, 71, 39, 32, 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 39, 86, 65, 76, 85, 69, 39, 10, + 79, 118, 101, 114, 114, 105, 100, 105, 110, 103, 83, 99, 111, 112, 101, 32, 60, 45, 32, 39, 85, 83, 69, 82, 39, 32, 47, 32, 39, 83, 89, 83, 84, 69, 77, 39, 10, 73, 110, 115, 101, 114, 116, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 63, 10, 73, 110, 115, 101, 114, 116, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 67, 111, 108, 73, 100, 41, 10, @@ -1512,7 +1880,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 73, 100, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 73, 110, 100, 101, 120, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 39, 79, 78, 39, 32, 39, 67, 79, 78, 83, 84, 82, 65, 73, 78, 84, 39, 32, 67, 111, 110, 115, 116, 114, 97, 105, 110, 116, 78, 97, 109, 101, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 65, 99, 116, 105, 111, 110, 32, 60, 45, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 47, 32, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 10, - 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 85, 112, 100, 97, 116, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 10, 79, 110, 67, 111, 110, 102, 108, 105, 99, 116, 78, 111, 116, 104, 105, 110, 103, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 82, 69, 84, 85, 82, 78, 73, 78, 71, 39, 32, 84, 97, 114, 103, 101, 116, 76, 105, 115, 116, 10, 76, 111, 97, 100, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 76, 79, 65, 68, 39, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 70, 114, 111, 109, 83, 111, 117, 114, 99, 101, 63, 32, 69, 120, 116, 101, 110, 115, 105, 111, 110, 65, 108, 105, 97, 115, 63, 10, @@ -1547,7 +1915,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 111, 78, 111, 116, 104, 105, 110, 103, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 68, 79, 39, 32, 39, 78, 79, 84, 72, 73, 78, 71, 39, 10, 69, 114, 114, 111, 114, 77, 97, 116, 99, 104, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 69, 82, 82, 79, 82, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 10, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 10, - 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, + 85, 112, 100, 97, 116, 101, 77, 97, 116, 99, 104, 83, 101, 116, 73, 110, 102, 111, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 47, 32, 83, 116, 97, 114, 83, 121, 109, 98, 111, 108, 10, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 39, 65, 78, 68, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 111, 116, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 72, 69, 78, 39, 32, 39, 78, 79, 84, 39, 32, 39, 77, 65, 84, 67, 72, 69, 68, 39, 32, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 63, 32, 65, 110, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 39, 84, 72, 69, 78, 39, 32, 77, 97, 116, 99, 104, 101, 100, 67, 108, 97, 117, 115, 101, 65, 99, 116, 105, 111, 110, 10, 66, 121, 83, 111, 117, 114, 99, 101, 79, 114, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 121, 83, 111, 117, 114, 99, 101, 32, 47, 32, 66, 121, 84, 97, 114, 103, 101, 116, 10, @@ -1606,7 +1974,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 84, 97, 98, 108, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 79, 112, 116, 105, 111, 110, 97, 108, 80, 97, 114, 101, 110, 115, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 47, 32, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 10, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 80, 97, 114, 101, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 41, 10, - 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, + 83, 105, 109, 112, 108, 101, 83, 101, 108, 101, 99, 116, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 63, 32, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 32, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 63, 32, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 63, 32, 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 47, 32, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 10, 83, 101, 108, 101, 99, 116, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 10, 70, 114, 111, 109, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 32, 83, 101, 108, 101, 99, 116, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1627,7 +1995,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 68, 105, 115, 116, 105, 110, 99, 116, 79, 110, 84, 97, 114, 103, 101, 116, 115, 32, 60, 45, 32, 39, 79, 78, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 47, 32, 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 47, 32, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 10, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 73, 110, 110, 101, 114, 84, 97, 98, 108, 101, 82, 101, 102, 32, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 42, 10, - 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 65, 116, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 39, 58, 39, 10, 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, @@ -1653,7 +2021,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 83, 99, 104, 101, 109, 97, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 67, 97, 116, 97, 108, 111, 103, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 84, 97, 98, 108, 101, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 43, 32, 82, 101, 115, 101, 114, 118, 101, 100, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 47, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 10, - 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 76, 97, 116, 101, 114, 97, 108, 79, 112, 116, 32, 60, 45, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 87, 105, 116, 104, 79, 114, 100, 105, 110, 97, 108, 105, 116, 121, 32, 60, 45, 32, 39, 87, 73, 84, 72, 39, 32, 39, 79, 82, 68, 73, 78, 65, 76, 73, 84, 89, 39, 10, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 63, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 42, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 10, @@ -1714,7 +2082,8 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 71, 114, 111, 117, 112, 66, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 71, 82, 79, 85, 80, 39, 32, 39, 66, 89, 39, 32, 71, 114, 111, 117, 112, 66, 121, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 10, 72, 97, 118, 105, 110, 103, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 72, 65, 86, 73, 78, 71, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 81, 117, 97, 108, 105, 102, 121, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 81, 85, 65, 76, 73, 70, 89, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 40, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 47, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 41, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, + 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, + 83, 101, 108, 101, 99, 116, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 83, 97, 109, 112, 108, 101, 69, 110, 116, 114, 121, 10, 85, 115, 105, 110, 103, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 85, 83, 73, 78, 71, 39, 32, 39, 83, 65, 77, 80, 76, 69, 39, 10, 84, 97, 98, 108, 101, 83, 97, 109, 112, 108, 101, 32, 60, 45, 32, 39, 84, 65, 66, 76, 69, 83, 65, 77, 80, 76, 69, 39, 10, 87, 105, 110, 100, 111, 119, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 87, 73, 78, 68, 79, 87, 39, 32, 76, 105, 115, 116, 40, 87, 105, 110, 100, 111, 119, 68, 101, 102, 105, 110, 105, 116, 105, 111, 110, 41, 10, @@ -1770,7 +2139,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 65, 108, 105, 97, 115, 101, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 47, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 67, 111, 108, 73, 100, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 32, 39, 58, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 65, 115, 67, 111, 108, 108, 97, 98, 101, 108, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 39, 65, 83, 39, 32, 67, 111, 108, 76, 97, 98, 101, 108, 79, 114, 83, 116, 114, 105, 110, 103, 10, - 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 63, 10, + 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 79, 112, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 66, 97, 114, 101, 67, 111, 108, 76, 97, 98, 101, 108, 63, 10, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 86, 65, 76, 85, 69, 83, 39, 32, 76, 105, 115, 116, 40, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 41, 10, 86, 97, 108, 117, 101, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 41, 41, 10, 83, 101, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 83, 69, 84, 39, 32, 83, 101, 116, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 79, 114, 84, 105, 109, 101, 90, 111, 110, 101, 10, @@ -1809,20 +2178,20 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 82, 101, 97, 100, 87, 114, 105, 116, 101, 32, 60, 45, 32, 39, 87, 82, 73, 84, 69, 39, 10, 65, 98, 111, 114, 116, 79, 114, 82, 111, 108, 108, 98, 97, 99, 107, 32, 60, 45, 32, 39, 65, 66, 79, 82, 84, 39, 32, 47, 32, 39, 82, 79, 76, 76, 66, 65, 67, 75, 39, 10, 67, 111, 109, 109, 105, 116, 79, 114, 69, 110, 100, 32, 60, 45, 32, 39, 67, 79, 77, 77, 73, 84, 39, 32, 47, 32, 39, 69, 78, 68, 39, 10, - 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, + 85, 112, 100, 97, 116, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 87, 105, 116, 104, 67, 108, 97, 117, 115, 101, 63, 32, 39, 85, 80, 68, 65, 84, 69, 39, 32, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 70, 114, 111, 109, 67, 108, 97, 117, 115, 101, 63, 32, 87, 104, 101, 114, 101, 67, 108, 97, 117, 115, 101, 63, 32, 82, 101, 116, 117, 114, 110, 105, 110, 103, 67, 108, 97, 117, 115, 101, 63, 10, 85, 112, 100, 97, 116, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 47, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 39, 83, 69, 84, 39, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 83, 101, 116, 32, 60, 45, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 63, 32, 39, 83, 69, 84, 39, 10, 85, 112, 100, 97, 116, 101, 65, 108, 105, 97, 115, 32, 60, 45, 32, 39, 65, 83, 39, 63, 32, 67, 111, 108, 73, 100, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 41, 10, + 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 47, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 84, 117, 112, 108, 101, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 41, 41, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, - 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 76, 105, 115, 116, 32, 60, 45, 32, 76, 105, 115, 116, 40, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 41, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 69, 108, 101, 109, 101, 110, 116, 32, 60, 45, 32, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 39, 61, 39, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 85, 112, 100, 97, 116, 101, 83, 101, 116, 67, 111, 108, 117, 109, 110, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 111, 108, 117, 109, 110, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 85, 115, 101, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 85, 83, 69, 39, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 47, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 47, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 10, - 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 10, - 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 10, + 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, + 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 65, 115, 85, 115, 101, 84, 97, 114, 103, 101, 116, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 47, 32, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 10, 85, 115, 101, 84, 97, 114, 103, 101, 116, 67, 97, 116, 97, 108, 111, 103, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 78, 97, 109, 101, 32, 39, 46, 39, 32, 82, 101, 115, 101, 114, 118, 101, 100, 83, 99, 104, 101, 109, 97, 78, 97, 109, 101, 32, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 42, 10, 68, 111, 116, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 60, 45, 32, 39, 46, 39, 32, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 10, 86, 97, 99, 117, 117, 109, 83, 116, 97, 116, 101, 109, 101, 110, 116, 32, 60, 45, 32, 39, 86, 65, 67, 85, 85, 77, 39, 32, 86, 97, 99, 117, 117, 109, 79, 112, 116, 105, 111, 110, 115, 63, 32, 65, 110, 97, 108, 121, 122, 101, 84, 97, 114, 103, 101, 116, 63, 10, diff --git a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp index 1c3d2d9bd..2cd19d5a1 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/keyword_helper/default_keyword_maps.hpp @@ -24,6 +24,8 @@ class DefaultKeywordMaps { case_insensitive_set_t colname_keyword_map; case_insensitive_set_t typefunc_keyword_map; case_insensitive_set_t typename_keyword_map; + //! Keywords that can be used as a column alias without AS (orthogonal to the other categories) + case_insensitive_set_t bare_label_keyword_map; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp index 758766438..b4150dd88 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher.hpp @@ -46,7 +46,8 @@ enum class SuggestionState : uint8_t { SUGGEST_TABLE_FUNCTION_NAME, SUGGEST_PRAGMA_NAME, SUGGEST_SETTING_NAME, - SUGGEST_RESERVED_VARIABLE + SUGGEST_RESERVED_VARIABLE, + SUGGEST_BARE_LABEL }; enum class CandidateType { KEYWORD, IDENTIFIER, LITERAL }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp index f37612674..a8163c699 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/identifier_matcher.hpp @@ -54,6 +54,10 @@ class IdentifierMatcher : public AtomicMatcher { if (Tokenizer::CharacterIsInitialNumber(text[0])) { return false; } + if (text.back() == '\'') { + // a prefixed string literal such as x'41' or e'ab' + return false; + } return Tokenizer::CharacterIsKeyword(text[0]); } diff --git a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp index 66714802d..815164e5e 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp @@ -165,11 +165,8 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.arena_ptr>", case_insensitive_map_t>); -DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.child_list_t", child_list_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.int64_t", int64_t); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.optional_idx", optional_idx); -DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair", - pair); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", pair>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.pair>", @@ -238,6 +235,8 @@ DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); +DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>>", + vector>>); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector", vector); DUCKDB_REGISTER_TRANSFORM_RESULT_TYPE("duckdb.transform_result.vector>", vector>); @@ -1139,6 +1138,9 @@ class PEGTransformerFactory { static void InitializeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTypeModifiersTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeNestedTypeTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1265,6 +1267,9 @@ class PEGTransformerFactory { static void InitializeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -1969,6 +1974,17 @@ class PEGTransformerFactory { static void InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2940,15 +2956,19 @@ class PEGTransformerFactory { static void InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeEndSliceValueTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static void InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static arena_ptr FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeStepSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -3799,6 +3819,9 @@ class PEGTransformerFactory { static void InitializeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4081,16 +4104,16 @@ class PEGTransformerFactory { static void InitializeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static void InitializeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetTupleTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); - static void InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); - static arena_ptr FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process); static void InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4353,19 +4376,26 @@ class PEGTransformerFactory { const Identifier &reserved_type_name); static vector> TransformTypeModifiers(PEGTransformer &transformer, optional>> expression); - static unique_ptr TransformRowType(PEGTransformer &transformer, - const optional> &col_id_type_list); - static unique_ptr TransformSetofType(PEGTransformer &transformer, const LogicalType &type); - static unique_ptr TransformUnionType(PEGTransformer &transformer, - const child_list_t &col_id_type_list); - static child_list_t TransformColIdTypeList(PEGTransformer &transformer, - const vector> &col_id_type); + static unique_ptr TransformNestedType(PEGTransformer &transformer, + unique_ptr type_variations, + const optional> &array_bounds); + static unique_ptr + TransformRowType(PEGTransformer &transformer, + optional>>> col_id_type_list); + static unique_ptr TransformSetofType(PEGTransformer &transformer, + unique_ptr nested_type); + static unique_ptr + TransformUnionType(PEGTransformer &transformer, + vector>> col_id_type_list); + static vector>> + TransformColIdTypeList(PEGTransformer &transformer, + vector>> col_id_type); static unique_ptr TransformMapType(PEGTransformer &transformer, - const optional> &type); + optional>> nested_type); static unique_ptr TransformTupleType(PEGTransformer &transformer, - const vector &type); - static pair TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, - const LogicalType &type); + vector> nested_type); + static pair> + TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, unique_ptr nested_type); static int64_t TransformArrayKeyword(PEGTransformer &transformer); static int64_t TransformArrayKeywordWithBounds(PEGTransformer &transformer, const int64_t &square_brackets_array); static int64_t TransformSquareBracketsArray(PEGTransformer &transformer, @@ -4390,12 +4420,11 @@ class PEGTransformerFactory { const Identifier &catalog_name); static unique_ptr TransformCopyStatement(PEGTransformer &transformer, unique_ptr copy_variations); - static unique_ptr TransformCopyTable(PEGTransformer &transformer, - unique_ptr base_table_name, - const optional> &insert_column_list, - const bool &from_or_to, - unique_ptr copy_file_name, - const optional> ©_options); + static unique_ptr + TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, + const optional> &insert_column_list, const bool &from_or_to, + unique_ptr copy_file_name, const optional &legacy_option, + const optional> ©_options); static bool TransformCopyFrom(PEGTransformer &transformer); static bool TransformCopyTo(PEGTransformer &transformer); static unique_ptr TransformCopySelect(PEGTransformer &transformer, @@ -4410,6 +4439,8 @@ class PEGTransformerFactory { const Identifier &identifier_col_id); static Identifier TransformIdentifierColId(PEGTransformer &transformer, const Identifier &identifier, const vector ©_file_name_suffix); + static GenericCopyOption TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, + const string &delimiter); static vector TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list); static vector @@ -4736,7 +4767,11 @@ class PEGTransformerFactory { optional> where_clause, optional>> returning_clause); static unique_ptr TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name); + unique_ptr base_table_name, + optional truncate_options); + static bool TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, + optional truncate_drop_behavior); + static bool TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior); static unique_ptr TransformTargetOptAlias(PEGTransformer &transformer, unique_ptr base_table_name, const optional &target_alias); @@ -5246,11 +5281,14 @@ class PEGTransformerFactory { vector> slice_bound); static vector> TransformSliceBound(PEGTransformer &transformer, optional> expression, - optional> end_slice_bound, - optional> step_slice_bound); + optional>> slice_upper_bound); + static vector> + TransformMinusStepSliceBound(PEGTransformer &transformer, unique_ptr step_slice_bound); + static vector> + TransformEndStepSliceBound(PEGTransformer &transformer, unique_ptr end_slice_bound, + optional> step_slice_bound); static unique_ptr TransformEndSliceBound(PEGTransformer &transformer, - optional> end_slice_value); - static unique_ptr TransformEndSliceMinus(PEGTransformer &transformer); + optional> expression); static unique_ptr TransformStepSliceBound(PEGTransformer &transformer, optional> expression); static unique_ptr TransformPostfixOperator(PEGTransformer &transformer); @@ -5351,8 +5389,8 @@ class PEGTransformerFactory { TransformInsertStatement(PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, - const optional> &insert_column_list, InsertValues insert_values, - optional> on_conflict_clause, + const optional> &insert_column_list, bool has_overriding_clause, + InsertValues insert_values, optional> on_conflict_clause, optional>> returning_clause); static OnConflictAction TransformInsertOrReplace(PEGTransformer &transformer); static OnConflictAction TransformInsertOrIgnore(PEGTransformer &transformer); @@ -5557,11 +5595,10 @@ class PEGTransformerFactory { optional>> distinct_on_targets); static vector> TransformDistinctOnTargets(PEGTransformer &transformer, vector> expression); - static unique_ptr TransformTableSubquery(PEGTransformer &transformer, - const optional &table_alias_colon, - const optional &lateral, - unique_ptr subquery_reference, - const optional &table_alias); + static unique_ptr + TransformTableSubquery(PEGTransformer &transformer, const optional &table_alias_colon, + const optional &lateral, unique_ptr subquery_reference, + const optional &table_alias, optional> sample_clause); static unique_ptr TransformBaseTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr base_table_name, const optional &table_alias, @@ -5615,12 +5652,10 @@ class PEGTransformerFactory { TransformCatalogReservedSchemaTable(PEGTransformer &transformer, const Identifier &catalog_qualification, const vector &reserved_schema_qualification, const Identifier &reserved_table_name); - static unique_ptr TransformTableFunctionLateralOpt(PEGTransformer &transformer, - const optional &lateral, - const QualifiedName &qualified_table_function, - vector table_function_arguments, - const optional &with_ordinality, - const optional &table_alias); + static unique_ptr TransformTableFunctionLateralOpt( + PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, + vector table_function_arguments, const optional &with_ordinality, + const optional &table_alias, optional> sample_clause); static unique_ptr TransformTableFunctionAliasColon(PEGTransformer &transformer, const Identifier &table_alias_colon, const QualifiedName &qualified_table_function, @@ -5712,6 +5747,8 @@ class PEGTransformerFactory { unique_ptr expression); static unique_ptr TransformSampleClause(PEGTransformer &transformer, unique_ptr sample_entry); + static unique_ptr TransformSelectSampleClause(PEGTransformer &transformer, + unique_ptr sample_entry); static vector> TransformWindowClause(PEGTransformer &transformer, vector> window_definition); static unique_ptr @@ -5837,20 +5874,19 @@ class PEGTransformerFactory { const optional &update_alias); static Identifier TransformUpdateAlias(PEGTransformer &transformer, const bool &has_result, const Identifier &col_id); + static unique_ptr TransformUpdateSetClauseList(PEGTransformer &transformer, + vector> update_set_info); static unique_ptr TransformUpdateSetTuple(PEGTransformer &transformer, const vector &column_name, unique_ptr expression); - static unique_ptr - TransformUpdateSetElementList(PEGTransformer &transformer, - vector>> update_set_element); - static pair> - TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, - unique_ptr expression); + static unique_ptr TransformUpdateSetElement(PEGTransformer &transformer, + const string &update_set_column_target, + unique_ptr expression); static string TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, const optional> &dot_identifier); static unique_ptr TransformUseStatement(PEGTransformer &transformer, const QualifiedName &use_target); - static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const Identifier &schema_name); - static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const Identifier &catalog_name); + static QualifiedName TransformSchemaNameAsUseTarget(PEGTransformer &transformer, const string &schema_name); + static QualifiedName TransformCatalogNameAsUseTarget(PEGTransformer &transformer, const string &catalog_name); static QualifiedName TransformUseTargetCatalogSchema(PEGTransformer &transformer, const Identifier &catalog_name, const Identifier &reserved_schema_name, const optional> &dot_identifier); diff --git a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp index 28ab414a8..8b9d9ab16 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp @@ -233,6 +233,9 @@ class ExpressionBinder { //! Returns true if the function name is an alias for the UNNEST function static bool IsUnnestFunction(const Identifier &function_name); + //! Continues the expression depth of the binder this one binds a nested expression for + void ContinueStackDepth(const ExpressionBinder &parent); + private: //! Current stack depth idx_t stack_depth = DConstants::INVALID_INDEX; diff --git a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp index bdd01b0fd..ef1494222 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/bloom_filter.hpp @@ -20,7 +20,7 @@ class BloomFilter; //! DEPRECATED - only preserved for backwards-compatible expression conversion class LegacyBFTableFilter final : public TableFilter { private: - optional_ptr filter; + shared_ptr filter; bool filters_null_values; string key_column_name; @@ -30,9 +30,9 @@ class LegacyBFTableFilter final : public TableFilter { static constexpr auto TYPE = TableFilterType::LEGACY_BLOOM_FILTER; public: - explicit LegacyBFTableFilter(optional_ptr filter_p, const bool filters_null_values_p, + explicit LegacyBFTableFilter(shared_ptr filter_p, const bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p) - : TableFilter(TYPE), filter(filter_p), filters_null_values(filters_null_values_p), + : TableFilter(TYPE), filter(std::move(filter_p)), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p) { } diff --git a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp index 0c2e3c147..262c0d5b3 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp @@ -87,44 +87,70 @@ struct TableFilterFunctions { }; //! Runtime bloom-filter state used by join pushdown and internal tablefilter functions. +//! A filter is first built (Initialize, then InsertHashes) and afterwards sealed. Only a sealed filter +//! describes a complete key set, so only a sealed filter may be looked up or handed to a scan. class BloomFilter { public: BloomFilter() = default; void Initialize(ClientContext &context_p, idx_t number_of_rows); - void InsertHashes(const Vector &hashes_v) const; + //! VectorOperations::Hash writes at the source row positions, so the hashes to insert are the ones sel + //! points at rather than the first count entries + void InsertHashes(const Vector &hashes_v, const SelectionVector &sel, idx_t count); + //! Inserts every hash the vector carries + void InsertHashes(const Vector &hashes_v); idx_t LookupHashes(const Vector &hashes_v, SelectionVector &result_sel, idx_t count) const; //! result_sel contains local positions into sel rather than source row ids. idx_t LookupHashes(const Vector &hashes_v, const SelectionVector &sel, SelectionVector &result_sel, idx_t count) const; - void InsertOne(hash_t hash) const; + void InsertOne(hash_t hash); bool LookupOne(hash_t hash) const; - void Merge(const BloomFilter &other); - void Reset(); + //! Folds the filter down to the smallest size that still meets the target false positive rate, releases the + //! memory that folding freed up, and marks the filter complete. + void Seal(ClientContext &context); bool IsInitialized() const { return initialized; } + bool IsSealed() const { + return sealed; + } + //! Whether the sealed filter still rejects enough for pushing it into a scan to pay off + bool IsUseful() const { + D_ASSERT(sealed); + return useful; + } static idx_t GetNumberOfSectors(idx_t number_of_rows); +private: + //! Halves the filter until only new_num_sectors sectors remain. Bit-exact with a filter that was built at + //! the smaller size from the start, because the sector index is the low bits of the hash and the bits set + //! within a sector are taken from the high bits. + void Fold(idx_t new_num_sectors); + //! Fraction of bits that are set, over the sectors currently in use + double Density() const; + private: idx_t num_sectors; uint64_t bitmask; // num_sectors - 1 -> used to get the sector offset bool initialized = false; + bool sealed = false; + bool useful = false; AllocatedData buf_; uint64_t *bf; }; //! FunctionData for bloom filter internal function struct BloomFilterFunctionData : public FunctionData { - BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, + BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p); - optional_ptr filter; + //! Published filters are shared and immutable: the hash table may drop its own reference at any time + shared_ptr filter; bool filters_null_values; string key_column_name; LogicalType key_type; diff --git a/src/duckdb/src/logging/log_types.cpp b/src/duckdb/src/logging/log_types.cpp index ec4f514db..bb25617f0 100644 --- a/src/duckdb/src/logging/log_types.cpp +++ b/src/duckdb/src/logging/log_types.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/file_opener.hpp" #include "duckdb/main/http/http_util.hpp" +#include "duckdb/main/profiler/profiling_utils.hpp" #include "duckdb/execution/physical_operator.hpp" #include "duckdb/logging/file_system_logger.hpp" #include "duckdb/main/attached_database.hpp" @@ -362,14 +363,19 @@ LogicalType MetricsLogType::GetLogType() { child_list_t child_list = { {"metric", LogicalType::VARCHAR}, {"value", LogicalType::VARCHAR}, + {"start_time", LogicalType::TIMESTAMP_TZ}, + {"end_time", LogicalType::TIMESTAMP_TZ}, }; return LogicalType::STRUCT(child_list); } -string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value) { +string MetricsLogType::ConstructLogMessage(const string &metric, const Value &value, + optional_ptr timing) { child_list_t child_list = { {"metric", metric}, {"value", value.ToString()}, + {"start_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->start_time) : Value()}, + {"end_time", timing && timing->has_bounds ? Value::TIMESTAMP(timing->end_time) : Value()}, }; return Value::STRUCT(std::move(child_list)).ToString(); } diff --git a/src/duckdb/src/main/profiler/gathered_metrics.cpp b/src/duckdb/src/main/profiler/gathered_metrics.cpp index dc8f5f98f..e58f4e541 100644 --- a/src/duckdb/src/main/profiler/gathered_metrics.cpp +++ b/src/duckdb/src/main/profiler/gathered_metrics.cpp @@ -105,12 +105,15 @@ void GatheredMetrics::SetMetric(const string &key, const string &value) { metrics[key] = Value(value); } -void GatheredMetrics::WriteMetricsToLog(ClientContext &context) const { +void GatheredMetrics::WriteMetricsToLog(ClientContext &context, + const unordered_map &timings) const { auto &logger = Logger::Get(context); if (logger.ShouldLog(MetricsLogType::NAME, MetricsLogType::LEVEL)) { for (const auto &entry : metrics) { + auto timing = timings.find(entry.first); logger.WriteLog(MetricsLogType::NAME, MetricsLogType::LEVEL, - MetricsLogType::ConstructLogMessage(entry.first, entry.second)); + MetricsLogType::ConstructLogMessage(entry.first, entry.second, + timing == timings.end() ? nullptr : &timing->second)); } } } diff --git a/src/duckdb/src/main/profiler/profiling_utils.cpp b/src/duckdb/src/main/profiler/profiling_utils.cpp index 29a40645c..7887993f6 100644 --- a/src/duckdb/src/main/profiler/profiling_utils.cpp +++ b/src/duckdb/src/main/profiler/profiling_utils.cpp @@ -5,8 +5,8 @@ namespace duckdb { void QueryMetrics::FinalizeMetrics(GatheredMetrics &info) { info.SetMetric(query_sql); - for (const auto &[key, ns] : string_timings) { - info.SetMetric(key, static_cast(ns) / 1e9); + for (const auto &[key, timing] : string_timings) { + info.SetMetric(key, static_cast(timing.duration) / 1e9); } for (const auto &[key, count] : string_counters) { info.SetMetric(key, count); diff --git a/src/duckdb/src/main/query_profiler.cpp b/src/duckdb/src/main/query_profiler.cpp index 4325e7b62..7d9010747 100644 --- a/src/duckdb/src/main/query_profiler.cpp +++ b/src/duckdb/src/main/query_profiler.cpp @@ -159,7 +159,7 @@ void QueryProfiler::StartQuery(const string &query, bool is_explain_analyze_p, b } Start(query); } -void QueryProfiler::AddParserTime(const Profiler &parser_timer) { +void QueryProfiler::AddParserTime(const Profiler &parser_timer, timestamp_t start_time, timestamp_t end_time) { if (!running || !IsEnabled()) { return; } @@ -167,7 +167,7 @@ void QueryProfiler::AddParserTime(const Profiler &parser_timer) { if (!parser_time_ns) { return; } - query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns); + query_metrics.UpdateMetric(MetricParserTotalTime::Name, parser_time_ns, start_time, end_time); } bool QueryProfiler::OperatorRequiresProfiling(const PhysicalOperatorType op_type) { @@ -843,7 +843,7 @@ void QueryProfiler::ToLogInternal() const { if (!root) { return; } - metrics->WriteMetricsToLog(context); + metrics->WriteMetricsToLog(context, query_metrics.GetMetricTimings()); } void QueryProfiler::ToLog() const { diff --git a/src/duckdb/src/main/statement_iterator.cpp b/src/duckdb/src/main/statement_iterator.cpp index cf66543a4..5a49b4d2c 100644 --- a/src/duckdb/src/main/statement_iterator.cpp +++ b/src/duckdb/src/main/statement_iterator.cpp @@ -26,12 +26,14 @@ bool StatementIterator::Peek() { if (pending_statement) { return true; } + parser_start_time = Timestamp::GetCurrentTimestamp(); parser_timer.Start(); if (!source.Peek()) { parser_timer.Reset(); return false; } parser_timer.End(); + parser_end_time = Timestamp::GetCurrentTimestamp(); pending_statement = source.GetStatement(); if (!pending_statement) { parser_timer.Reset(); @@ -55,7 +57,7 @@ unique_ptr StatementIterator::GetStatementInternal(bool profile_st if (profile_statement) { auto &profiler = QueryProfiler::Get(context); profiler.StartQuery(statement->query, StatementIteratorIsExplainAnalyze(statement.get())); - profiler.AddParserTime(parser_timer); + profiler.AddParserTime(parser_timer, parser_start_time, parser_end_time); } parser_timer.Reset(); return statement; diff --git a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp index e8c97f9bc..11825d66e 100644 --- a/src/duckdb/src/optimizer/rule/monotone_preimage.cpp +++ b/src/duckdb/src/optimizer/rule/monotone_preimage.cpp @@ -51,8 +51,10 @@ static bool GetFiniteDomain(const LogicalType &type, int64_t &lo, int64_t &hi, b return true; case LogicalTypeId::TIMESTAMP: case LogicalTypeId::TIMESTAMP_TZ: - // the ±infinity sentinels keep the finite domain bounded away from INT64_MIN/MAX, so the - // never-true sentinels (hi+1 / lo-1) in the bisection can't overflow + // every non-sentinel int64 is a valid, comparable timestamp (make_timestamp accepts all of them), so the + // finite domain is everything except the ±infinity sentinels; that also keeps the never-true sentinels + // (hi+1 / lo-1) of the bisection away from INT64_MIN/MAX. Not all of these values render, though: see + // IsRenderableBound. lo = timestamp_t::ninfinity().value + 1; hi = timestamp_t::infinity().value - 1; has_infinity = true; @@ -145,10 +147,37 @@ static int64_t LastTruePrefix(PRED pred, int64_t lo, int64_t hi, bool &ok) { return l; } -static unique_ptr MakeColComparison(ExpressionType type, const BoundColumnRefExpression &col, - const LogicalType &col_type, int64_t bound) { - return BoundComparisonExpression::Create(type, col.Copy(), - make_uniq(Value::Numeric(col_type, bound))); +//! Whether a bound with internal representation `bound` can be rendered (Expression::ToString, EXPLAIN). +//! TIMESTAMP values between INT64_MIN and the first midnight of Timestamp::MIN_YEAR are valid and comparable +//! (make_timestamp accepts them) but have no date/time form: Timestamp::Convert overflows on them. +static bool IsRenderableBound(const LogicalType &col_type, int64_t bound) { + switch (col_type.id()) { + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + return bound >= Value::MinimumValue(LogicalType::TIMESTAMP).GetValue().value; + default: + return true; + } +} + +//! Emit `col >= bound` (lower) or `col <= bound` (upper). On types with ±infinity a bound on the edge of the +//! finite domain is written as a strict comparison against the adjacent sentinel instead (`col > -infinity`, +//! `col < infinity`): nothing lies between the two, and the sentinel always renders while the edge of the +//! TIMESTAMP domain does not. Returns nullptr if the bound cannot be rendered - the caller then keeps f(col). +static unique_ptr MakeColBound(bool lower, const BoundColumnRefExpression &col, const LogicalType &col_type, + int64_t bound, int64_t dom_edge, bool has_infinity) { + if (has_infinity && bound == dom_edge) { + auto sentinel = lower ? Value::NegativeInfinity(col_type) : Value::Infinity(col_type); + return BoundComparisonExpression::Create(lower ? ExpressionType::COMPARE_GREATERTHAN + : ExpressionType::COMPARE_LESSTHAN, + col.Copy(), make_uniq(std::move(sentinel))); + } + if (!IsRenderableBound(col_type, bound)) { + return nullptr; + } + return BoundComparisonExpression::Create( + lower ? ExpressionType::COMPARE_GREATERTHANOREQUALTO : ExpressionType::COMPARE_LESSTHANOREQUALTO, col.Copy(), + make_uniq(Value::Numeric(col_type, bound))); } unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector> &bindings, @@ -323,10 +352,16 @@ unique_ptr MonotonePreimageRule::Apply(LogicalOperator &op, vector lower; unique_ptr upper; if (need_lower) { - lower = MakeColComparison(ExpressionType::COMPARE_GREATERTHANOREQUALTO, col, col_type, a); + lower = MakeColBound(true, col, col_type, a, dom_lo, has_infinity); + if (!lower) { + return nullptr; + } } if (need_upper) { - upper = MakeColComparison(ExpressionType::COMPARE_LESSTHANOREQUALTO, col, col_type, b); + upper = MakeColBound(false, col, col_type, b, dom_hi, has_infinity); + if (!upper) { + return nullptr; + } } if (lower && upper) { return make_uniq(ExpressionType::CONJUNCTION_AND, std::move(lower), diff --git a/src/duckdb/src/parser/peg/autocomplete_core.cpp b/src/duckdb/src/parser/peg/autocomplete_core.cpp index 076aad940..a637e7ba1 100644 --- a/src/duckdb/src/parser/peg/autocomplete_core.cpp +++ b/src/duckdb/src/parser/peg/autocomplete_core.cpp @@ -229,6 +229,7 @@ vector GenerateAutoCompleteSuggestions(AutoCompleteCatal vector new_suggestions; switch (suggestion.type) { case SuggestionState::SUGGEST_VARIABLE: + case SuggestionState::SUGGEST_BARE_LABEL: // variables have no suggestions available break; case SuggestionState::SUGGEST_KEYWORD: diff --git a/src/duckdb/src/parser/peg/compiled_grammar.cpp b/src/duckdb/src/parser/peg/compiled_grammar.cpp index 623048f7e..3cf3f2878 100644 --- a/src/duckdb/src/parser/peg/compiled_grammar.cpp +++ b/src/duckdb/src/parser/peg/compiled_grammar.cpp @@ -55,6 +55,8 @@ terminal_rule_overrides_t ParsedGrammar::BuildTerminalRuleOverrides(const PEGKey make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); AddTerminalRuleOverride(overrides, "ReservedIdentifier", make_uniq(SuggestionState::SUGGEST_VARIABLE, keyword_helper)); + AddTerminalRuleOverride(overrides, "BareColLabel", + make_uniq(SuggestionState::SUGGEST_BARE_LABEL, keyword_helper)); AddTerminalRuleOverride(overrides, "CatalogName", make_uniq(SuggestionState::SUGGEST_CATALOG_NAME, keyword_helper)); AddTerminalRuleOverride(overrides, "SchemaName", diff --git a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp index 732eb3d66..3517431b4 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/default_keyword_maps.cpp @@ -8,6 +8,7 @@ static constexpr keyword_categories_t KEYWORD_RESERVED = keyword_categories_t::C static constexpr keyword_categories_t KEYWORD_TYPE_FUNC = keyword_categories_t::CreateCategory(2); static constexpr keyword_categories_t KEYWORD_COL_NAME = keyword_categories_t::CreateCategory(3); static constexpr keyword_categories_t KEYWORD_TYPE_NAME = keyword_categories_t::CreateCategory(4); +static constexpr keyword_categories_t KEYWORD_BARE_LABEL = keyword_categories_t::CreateCategory(5); LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t literal_id) const { keyword_categories_t flags; @@ -26,11 +27,16 @@ LiteralInfo DefaultKeywordMaps::LookupKeyword(const string &text, uint16_t liter if (typename_keyword_map.count(text)) { flags |= KEYWORD_TYPE_NAME; } + if (bare_label_keyword_map.count(text)) { + flags |= KEYWORD_BARE_LABEL; + } return LiteralInfo(literal_id, flags); } keyword_categories_t DefaultKeywordMaps::GetIdentifierMask(SuggestionState type) { switch (type) { + case SuggestionState::SUGGEST_BARE_LABEL: + return KEYWORD_BARE_LABEL; case SuggestionState::SUGGEST_TYPE_NAME: return KEYWORD_UNRESERVED | KEYWORD_TYPE_NAME; case SuggestionState::SUGGEST_SCALAR_FUNCTION_NAME: diff --git a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp index 528d26657..f1c75cd79 100644 --- a/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp +++ b/src/duckdb/src/parser/peg/keyword_helper/parsed_grammar_keyword_helper.cpp @@ -48,16 +48,32 @@ static void PopulateKeywordMap(const ParsedGrammar &grammar, const string &root_ static DefaultKeywordMaps BuildKeywordMaps(const ParsedGrammar &grammar) { DefaultKeywordMaps keyword_maps; - unordered_map> mapping { - {"ReservedKeyword", keyword_maps.reserved_keyword_map}, - {"UnreservedKeyword", keyword_maps.unreserved_keyword_map}, - {"ColumnNameKeyword", keyword_maps.colname_keyword_map}, - {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map}, - {"TypeNameKeyword", keyword_maps.typename_keyword_map}, + struct KeywordRuleMapping { + const char *rule_name; + reference keyword_map; + bool optional; + }; + // grammars without a bare-label rule only accept non-keywords as bare labels + const KeywordRuleMapping mapping[] = { + {"ReservedKeyword", keyword_maps.reserved_keyword_map, false}, + {"UnreservedKeyword", keyword_maps.unreserved_keyword_map, false}, + {"ColumnNameKeyword", keyword_maps.colname_keyword_map, false}, + {"TypeFuncKeyword", keyword_maps.typefunc_keyword_map, false}, + {"TypeNameKeyword", keyword_maps.typename_keyword_map, false}, + {"BareLabelKeyword", keyword_maps.bare_label_keyword_map, true}, }; for (auto &entry : mapping) { + if (entry.optional && !grammar.GetRule(entry.rule_name)) { + continue; + } case_insensitive_set_t active_rules; - PopulateKeywordMap(grammar, entry.first, entry.first, entry.second.get(), active_rules); + PopulateKeywordMap(grammar, entry.rule_name, entry.rule_name, entry.keyword_map.get(), active_rules); + } + // the bare-label classification is orthogonal: every entry must also be an unreserved or column-name keyword + for (auto &keyword : keyword_maps.bare_label_keyword_map) { + if (!keyword_maps.unreserved_keyword_map.count(keyword) && !keyword_maps.colname_keyword_map.count(keyword)) { + throw InvalidInputException("Bare label keyword '%s' is not an unreserved or column-name keyword", keyword); + } } return keyword_maps; } diff --git a/src/duckdb/src/parser/peg/keyword_map.cpp b/src/duckdb/src/parser/peg/keyword_map.cpp index f6d8b83f7..160b0cd36 100644 --- a/src/duckdb/src/parser/peg/keyword_map.cpp +++ b/src/duckdb/src/parser/peg/keyword_map.cpp @@ -10,6 +10,7 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { auto &colname_keyword_map = keyword_maps.colname_keyword_map; auto &typefunc_keyword_map = keyword_maps.typefunc_keyword_map; auto &typename_keyword_map = keyword_maps.typename_keyword_map; + auto &bare_label_keyword_map = keyword_maps.bare_label_keyword_map; // Populating reserved_keyword_map reserved_keyword_map.insert("all"); @@ -560,6 +561,366 @@ DefaultKeywordMaps DuckDBKeywordHelper::InitializeKeywordMaps() { typename_keyword_map.insert("try_cast"); typename_keyword_map.insert("unpack"); typename_keyword_map.insert("verbose"); + + // Populating bare_label_keyword_map + bare_label_keyword_map.insert("abort"); + bare_label_keyword_map.insert("absolute"); + bare_label_keyword_map.insert("access"); + bare_label_keyword_map.insert("action"); + bare_label_keyword_map.insert("add"); + bare_label_keyword_map.insert("admin"); + bare_label_keyword_map.insert("after"); + bare_label_keyword_map.insert("aggregate"); + bare_label_keyword_map.insert("also"); + bare_label_keyword_map.insert("alter"); + bare_label_keyword_map.insert("always"); + bare_label_keyword_map.insert("approx"); + bare_label_keyword_map.insert("assertion"); + bare_label_keyword_map.insert("assignment"); + bare_label_keyword_map.insert("attach"); + bare_label_keyword_map.insert("attribute"); + bare_label_keyword_map.insert("backward"); + bare_label_keyword_map.insert("before"); + bare_label_keyword_map.insert("begin"); + bare_label_keyword_map.insert("between"); + bare_label_keyword_map.insert("bigint"); + bare_label_keyword_map.insert("bit"); + bare_label_keyword_map.insert("boolean"); + bare_label_keyword_map.insert("cache"); + bare_label_keyword_map.insert("call"); + bare_label_keyword_map.insert("called"); + bare_label_keyword_map.insert("cascade"); + bare_label_keyword_map.insert("cascaded"); + bare_label_keyword_map.insert("catalog"); + bare_label_keyword_map.insert("chain"); + bare_label_keyword_map.insert("char"); + bare_label_keyword_map.insert("character"); + bare_label_keyword_map.insert("characteristics"); + bare_label_keyword_map.insert("checkpoint"); + bare_label_keyword_map.insert("class"); + bare_label_keyword_map.insert("close"); + bare_label_keyword_map.insert("cluster"); + bare_label_keyword_map.insert("coalesce"); + bare_label_keyword_map.insert("columns"); + bare_label_keyword_map.insert("comment"); + bare_label_keyword_map.insert("comments"); + bare_label_keyword_map.insert("commit"); + bare_label_keyword_map.insert("committed"); + bare_label_keyword_map.insert("compression"); + bare_label_keyword_map.insert("configuration"); + bare_label_keyword_map.insert("conflict"); + bare_label_keyword_map.insert("connect"); + bare_label_keyword_map.insert("connection"); + bare_label_keyword_map.insert("constraints"); + bare_label_keyword_map.insert("content"); + bare_label_keyword_map.insert("continue"); + bare_label_keyword_map.insert("conversion"); + bare_label_keyword_map.insert("copy"); + bare_label_keyword_map.insert("cost"); + bare_label_keyword_map.insert("csv"); + bare_label_keyword_map.insert("cube"); + bare_label_keyword_map.insert("current"); + bare_label_keyword_map.insert("cursor"); + bare_label_keyword_map.insert("cycle"); + bare_label_keyword_map.insert("data"); + bare_label_keyword_map.insert("database"); + bare_label_keyword_map.insert("deallocate"); + bare_label_keyword_map.insert("dec"); + bare_label_keyword_map.insert("decimal"); + bare_label_keyword_map.insert("declare"); + bare_label_keyword_map.insert("defaults"); + bare_label_keyword_map.insert("deferred"); + bare_label_keyword_map.insert("definer"); + bare_label_keyword_map.insert("delete"); + bare_label_keyword_map.insert("delimiter"); + bare_label_keyword_map.insert("delimiters"); + bare_label_keyword_map.insert("depends"); + bare_label_keyword_map.insert("destroy"); + bare_label_keyword_map.insert("detach"); + bare_label_keyword_map.insert("dictionary"); + bare_label_keyword_map.insert("disable"); + bare_label_keyword_map.insert("discard"); + bare_label_keyword_map.insert("disconnect"); + bare_label_keyword_map.insert("distance"); + bare_label_keyword_map.insert("document"); + bare_label_keyword_map.insert("domain"); + bare_label_keyword_map.insert("double"); + bare_label_keyword_map.insert("drop"); + bare_label_keyword_map.insert("each"); + bare_label_keyword_map.insert("enable"); + bare_label_keyword_map.insert("encoding"); + bare_label_keyword_map.insert("encrypted"); + bare_label_keyword_map.insert("enum"); + bare_label_keyword_map.insert("error"); + bare_label_keyword_map.insert("event"); + bare_label_keyword_map.insert("exact"); + bare_label_keyword_map.insert("excluding"); + bare_label_keyword_map.insert("exclusive"); + bare_label_keyword_map.insert("execute"); + bare_label_keyword_map.insert("exists"); + bare_label_keyword_map.insert("explain"); + bare_label_keyword_map.insert("export"); + bare_label_keyword_map.insert("extension"); + bare_label_keyword_map.insert("extensions"); + bare_label_keyword_map.insert("external"); + bare_label_keyword_map.insert("extract"); + bare_label_keyword_map.insert("family"); + bare_label_keyword_map.insert("first"); + bare_label_keyword_map.insert("float"); + bare_label_keyword_map.insert("following"); + bare_label_keyword_map.insert("force"); + bare_label_keyword_map.insert("forward"); + bare_label_keyword_map.insert("function"); + bare_label_keyword_map.insert("functions"); + bare_label_keyword_map.insert("generated"); + bare_label_keyword_map.insert("global"); + bare_label_keyword_map.insert("grant"); + bare_label_keyword_map.insert("granted"); + bare_label_keyword_map.insert("grouping"); + bare_label_keyword_map.insert("grouping_id"); + bare_label_keyword_map.insert("groups"); + bare_label_keyword_map.insert("handler"); + bare_label_keyword_map.insert("header"); + bare_label_keyword_map.insert("hold"); + bare_label_keyword_map.insert("identity"); + bare_label_keyword_map.insert("if"); + bare_label_keyword_map.insert("immediate"); + bare_label_keyword_map.insert("immutable"); + bare_label_keyword_map.insert("implicit"); + bare_label_keyword_map.insert("import"); + bare_label_keyword_map.insert("include"); + bare_label_keyword_map.insert("including"); + bare_label_keyword_map.insert("increment"); + bare_label_keyword_map.insert("index"); + bare_label_keyword_map.insert("indexes"); + bare_label_keyword_map.insert("inherit"); + bare_label_keyword_map.insert("inherits"); + bare_label_keyword_map.insert("inline"); + bare_label_keyword_map.insert("inout"); + bare_label_keyword_map.insert("input"); + bare_label_keyword_map.insert("insensitive"); + bare_label_keyword_map.insert("insert"); + bare_label_keyword_map.insert("install"); + bare_label_keyword_map.insert("instead"); + bare_label_keyword_map.insert("int"); + bare_label_keyword_map.insert("integer"); + bare_label_keyword_map.insert("interval"); + bare_label_keyword_map.insert("invoker"); + bare_label_keyword_map.insert("isolation"); + bare_label_keyword_map.insert("json"); + bare_label_keyword_map.insert("key"); + bare_label_keyword_map.insert("label"); + bare_label_keyword_map.insert("language"); + bare_label_keyword_map.insert("large"); + bare_label_keyword_map.insert("last"); + bare_label_keyword_map.insert("leakproof"); + bare_label_keyword_map.insert("level"); + bare_label_keyword_map.insert("listen"); + bare_label_keyword_map.insert("load"); + bare_label_keyword_map.insert("local"); + bare_label_keyword_map.insert("location"); + bare_label_keyword_map.insert("lock"); + bare_label_keyword_map.insert("locked"); + bare_label_keyword_map.insert("logged"); + bare_label_keyword_map.insert("macro"); + bare_label_keyword_map.insert("map"); + bare_label_keyword_map.insert("mapping"); + bare_label_keyword_map.insert("match"); + bare_label_keyword_map.insert("matched"); + bare_label_keyword_map.insert("materialized"); + bare_label_keyword_map.insert("maxvalue"); + bare_label_keyword_map.insert("merge"); + bare_label_keyword_map.insert("method"); + bare_label_keyword_map.insert("minvalue"); + bare_label_keyword_map.insert("mode"); + bare_label_keyword_map.insert("move"); + bare_label_keyword_map.insert("name"); + bare_label_keyword_map.insert("names"); + bare_label_keyword_map.insert("national"); + bare_label_keyword_map.insert("nchar"); + bare_label_keyword_map.insert("nearest"); + bare_label_keyword_map.insert("new"); + bare_label_keyword_map.insert("next"); + bare_label_keyword_map.insert("no"); + bare_label_keyword_map.insert("none"); + bare_label_keyword_map.insert("nothing"); + bare_label_keyword_map.insert("notify"); + bare_label_keyword_map.insert("nowait"); + bare_label_keyword_map.insert("nullif"); + bare_label_keyword_map.insert("nulls"); + bare_label_keyword_map.insert("numeric"); + bare_label_keyword_map.insert("object"); + bare_label_keyword_map.insert("of"); + bare_label_keyword_map.insert("off"); + bare_label_keyword_map.insert("oids"); + bare_label_keyword_map.insert("old"); + bare_label_keyword_map.insert("operator"); + bare_label_keyword_map.insert("option"); + bare_label_keyword_map.insert("options"); + bare_label_keyword_map.insert("ordinality"); + bare_label_keyword_map.insert("others"); + bare_label_keyword_map.insert("out"); + bare_label_keyword_map.insert("overlay"); + bare_label_keyword_map.insert("overriding"); + bare_label_keyword_map.insert("owned"); + bare_label_keyword_map.insert("owner"); + bare_label_keyword_map.insert("parallel"); + bare_label_keyword_map.insert("parser"); + bare_label_keyword_map.insert("partial"); + bare_label_keyword_map.insert("partition"); + bare_label_keyword_map.insert("partitioned"); + bare_label_keyword_map.insert("passing"); + bare_label_keyword_map.insert("password"); + bare_label_keyword_map.insert("percent"); + bare_label_keyword_map.insert("persistent"); + bare_label_keyword_map.insert("plans"); + bare_label_keyword_map.insert("policy"); + bare_label_keyword_map.insert("position"); + bare_label_keyword_map.insert("pragma"); + bare_label_keyword_map.insert("preceding"); + bare_label_keyword_map.insert("prepare"); + bare_label_keyword_map.insert("prepared"); + bare_label_keyword_map.insert("preserve"); + bare_label_keyword_map.insert("prior"); + bare_label_keyword_map.insert("privileges"); + bare_label_keyword_map.insert("procedural"); + bare_label_keyword_map.insert("procedure"); + bare_label_keyword_map.insert("program"); + bare_label_keyword_map.insert("publication"); + bare_label_keyword_map.insert("quote"); + bare_label_keyword_map.insert("range"); + bare_label_keyword_map.insert("read"); + bare_label_keyword_map.insert("real"); + bare_label_keyword_map.insert("reassign"); + bare_label_keyword_map.insert("recheck"); + bare_label_keyword_map.insert("recursive"); + bare_label_keyword_map.insert("ref"); + bare_label_keyword_map.insert("referencing"); + bare_label_keyword_map.insert("refresh"); + bare_label_keyword_map.insert("register"); + bare_label_keyword_map.insert("reindex"); + bare_label_keyword_map.insert("relative"); + bare_label_keyword_map.insert("release"); + bare_label_keyword_map.insert("repeatable"); + bare_label_keyword_map.insert("replica"); + bare_label_keyword_map.insert("reset"); + bare_label_keyword_map.insert("resource"); + bare_label_keyword_map.insert("resources"); + bare_label_keyword_map.insert("restart"); + bare_label_keyword_map.insert("restrict"); + bare_label_keyword_map.insert("returns"); + bare_label_keyword_map.insert("revoke"); + bare_label_keyword_map.insert("role"); + bare_label_keyword_map.insert("rollback"); + bare_label_keyword_map.insert("rollup"); + bare_label_keyword_map.insert("row"); + bare_label_keyword_map.insert("rows"); + bare_label_keyword_map.insert("rule"); + bare_label_keyword_map.insert("sample"); + bare_label_keyword_map.insert("savepoint"); + bare_label_keyword_map.insert("schema"); + bare_label_keyword_map.insert("schemas"); + bare_label_keyword_map.insert("scope"); + bare_label_keyword_map.insert("scroll"); + bare_label_keyword_map.insert("search"); + bare_label_keyword_map.insert("secret"); + bare_label_keyword_map.insert("secure"); + bare_label_keyword_map.insert("security"); + bare_label_keyword_map.insert("sequence"); + bare_label_keyword_map.insert("sequences"); + bare_label_keyword_map.insert("serializable"); + bare_label_keyword_map.insert("server"); + bare_label_keyword_map.insert("session"); + bare_label_keyword_map.insert("set"); + bare_label_keyword_map.insert("setof"); + bare_label_keyword_map.insert("sets"); + bare_label_keyword_map.insert("share"); + bare_label_keyword_map.insert("similarity"); + bare_label_keyword_map.insert("simple"); + bare_label_keyword_map.insert("skip"); + bare_label_keyword_map.insert("smallint"); + bare_label_keyword_map.insert("snapshot"); + bare_label_keyword_map.insert("sorted"); + bare_label_keyword_map.insert("source"); + bare_label_keyword_map.insert("sql"); + bare_label_keyword_map.insert("stable"); + bare_label_keyword_map.insert("standalone"); + bare_label_keyword_map.insert("start"); + bare_label_keyword_map.insert("statement"); + bare_label_keyword_map.insert("statistics"); + bare_label_keyword_map.insert("stdin"); + bare_label_keyword_map.insert("stdout"); + bare_label_keyword_map.insert("storage"); + bare_label_keyword_map.insert("stored"); + bare_label_keyword_map.insert("strict"); + bare_label_keyword_map.insert("strip"); + bare_label_keyword_map.insert("struct"); + bare_label_keyword_map.insert("subscription"); + bare_label_keyword_map.insert("substring"); + bare_label_keyword_map.insert("sysid"); + bare_label_keyword_map.insert("system"); + bare_label_keyword_map.insert("tables"); + bare_label_keyword_map.insert("tablespace"); + bare_label_keyword_map.insert("target"); + bare_label_keyword_map.insert("temp"); + bare_label_keyword_map.insert("template"); + bare_label_keyword_map.insert("temporary"); + bare_label_keyword_map.insert("text"); + bare_label_keyword_map.insert("ties"); + bare_label_keyword_map.insert("time"); + bare_label_keyword_map.insert("timestamp"); + bare_label_keyword_map.insert("transaction"); + bare_label_keyword_map.insert("transform"); + bare_label_keyword_map.insert("treat"); + bare_label_keyword_map.insert("trigger"); + bare_label_keyword_map.insert("trim"); + bare_label_keyword_map.insert("truncate"); + bare_label_keyword_map.insert("trusted"); + bare_label_keyword_map.insert("try_cast"); + bare_label_keyword_map.insert("tuple"); + bare_label_keyword_map.insert("type"); + bare_label_keyword_map.insert("types"); + bare_label_keyword_map.insert("unbounded"); + bare_label_keyword_map.insert("uncommitted"); + bare_label_keyword_map.insert("unencrypted"); + bare_label_keyword_map.insert("unknown"); + bare_label_keyword_map.insert("unlisten"); + bare_label_keyword_map.insert("unlogged"); + bare_label_keyword_map.insert("until"); + bare_label_keyword_map.insert("update"); + bare_label_keyword_map.insert("use"); + bare_label_keyword_map.insert("user"); + bare_label_keyword_map.insert("vacuum"); + bare_label_keyword_map.insert("valid"); + bare_label_keyword_map.insert("validate"); + bare_label_keyword_map.insert("validator"); + bare_label_keyword_map.insert("value"); + bare_label_keyword_map.insert("values"); + bare_label_keyword_map.insert("varchar"); + bare_label_keyword_map.insert("variable"); + bare_label_keyword_map.insert("version"); + bare_label_keyword_map.insert("view"); + bare_label_keyword_map.insert("views"); + bare_label_keyword_map.insert("virtual"); + bare_label_keyword_map.insert("volatile"); + bare_label_keyword_map.insert("whitespace"); + bare_label_keyword_map.insert("work"); + bare_label_keyword_map.insert("wrapper"); + bare_label_keyword_map.insert("write"); + bare_label_keyword_map.insert("xml"); + bare_label_keyword_map.insert("xmlattributes"); + bare_label_keyword_map.insert("xmlconcat"); + bare_label_keyword_map.insert("xmlelement"); + bare_label_keyword_map.insert("xmlexists"); + bare_label_keyword_map.insert("xmlforest"); + bare_label_keyword_map.insert("xmlnamespaces"); + bare_label_keyword_map.insert("xmlparse"); + bare_label_keyword_map.insert("xmlpi"); + bare_label_keyword_map.insert("xmlroot"); + bare_label_keyword_map.insert("xmlserialize"); + bare_label_keyword_map.insert("xmltable"); + bare_label_keyword_map.insert("yes"); + bare_label_keyword_map.insert("zone"); return keyword_maps; } } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp index 408bf3963..c02196b46 100644 --- a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp +++ b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp @@ -85,6 +85,15 @@ bool Tokenizer::CharacterIsInitialNumber(char c) { return c == '.'; } +static bool NumberHasDotOrExponent(const string &sql, idx_t start, idx_t end) { + for (idx_t i = start; i < end; i++) { + if (sql[i] == '.' || sql[i] == 'e' || sql[i] == 'E') { + return true; + } + } + return false; +} + bool Tokenizer::CharacterIsSpecialStringCharacter(char c) { if (c == 'N' || c == 'n') { return true; @@ -438,8 +447,8 @@ bool Tokenizer::TokenizeInputInternal(TokenizerBehavior &behavior) const { last_pos = i; break; case TokenizeState::NUMERIC: - // Check for "always allowed" numeric characters - if (CharacterIsInitialNumber(c)) { + // Check for "always allowed" numeric characters - a number has at most one dot, and none in its exponent + if (CharacterIsInitialNumber(c) && (c != '.' || !NumberHasDotOrExponent(sql, last_pos, i))) { break; // Continue tokenizing } // Allow underscore only when immediately followed by a digit (no consecutive underscores) diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp index cc687f4b9..cc0ac5f8f 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp @@ -162,6 +162,7 @@ PEGTransformerFactory::PEGTransformerFactory(ParsedGrammar &grammar_p) : grammar //===--------------------------------------------------------------------===// grammar.SetExpressionDepth("SingleExpression", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("Type", ExpressionDepthKind::NESTING); + grammar.SetExpressionDepth("NestedType", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("LambdaArrowExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("LogicalOrExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("LogicalAndExpression", ExpressionDepthKind::CHAIN); diff --git a/src/duckdb/src/parser/peg/transformer/transform_common.cpp b/src/duckdb/src/parser/peg/transformer/transform_common.cpp index 0dc6dd071..b466696ab 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_common.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_common.cpp @@ -53,6 +53,12 @@ string PEGTransformerFactory::TransformIdentifierOrKeyword(PEGTransformer &trans LogicalType PEGTransformerFactory::TransformType(PEGTransformer &transformer, unique_ptr type_variations, const optional> &array_bounds) { + return LogicalType::UNBOUND(TransformNestedType(transformer, std::move(type_variations), array_bounds)); +} + +unique_ptr PEGTransformerFactory::TransformNestedType(PEGTransformer &transformer, + unique_ptr type_variations, + const optional> &array_bounds) { auto array_depth_guard = transformer.StackCheck(array_bounds ? array_bounds->size() : 0); auto type = std::move(type_variations); if (array_bounds) { @@ -68,7 +74,7 @@ LogicalType PEGTransformerFactory::TransformType(PEGTransformer &transformer, } } } - return LogicalType::UNBOUND(std::move(type)); + return type; } int64_t PEGTransformerFactory::TransformArrayKeyword(PEGTransformer &transformer) { @@ -132,6 +138,9 @@ PEGTransformerFactory::TransformTimeType(PEGTransformer &transformer, const Logi return make_uniq(Identifier(EnumUtil::ToString(LogicalType::TIMESTAMP)), vector> {}); } + if (with_timezone) { + throw ParserException("TIMESTAMP WITH TIME ZONE does not take a precision"); + } if (modifiers.size() > 1) { throw ParserException("TIMESTAMP only supports a single modifier"); } @@ -316,36 +325,29 @@ QualifiedName PEGTransformerFactory::TransformCatalogReservedSchemaTypeName( return QualifiedName(std::move(qualification), reserved_type_name); } -unique_ptr PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, - const optional> &type) { +unique_ptr +PEGTransformerFactory::TransformMapType(PEGTransformer &transformer, + optional>> nested_type) { vector> map_children; - if (type) { - for (auto &child_type : *type) { - map_children.push_back(UnboundType::GetTypeExpression(child_type)->Copy()); - } + if (nested_type) { + map_children = std::move(*nested_type); } return make_uniq(Identifier("MAP"), std::move(map_children)); } -unique_ptr PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, - const vector &type) { - vector> tuple_children; - for (auto &child : type) { - tuple_children.push_back(UnboundType::GetTypeExpression(child)->Copy()); - } - return make_uniq(Identifier("TUPLE"), std::move(tuple_children)); +unique_ptr +PEGTransformerFactory::TransformTupleType(PEGTransformer &transformer, + vector> nested_type) { + return make_uniq(Identifier("TUPLE"), std::move(nested_type)); } -unique_ptr -PEGTransformerFactory::TransformRowType(PEGTransformer &transformer, - const optional> &col_id_type_list) { +unique_ptr PEGTransformerFactory::TransformRowType( + PEGTransformer &transformer, optional>>> col_id_type_list) { vector> struct_children; if (col_id_type_list) { for (auto &child : *col_id_type_list) { - auto &type_expr = UnboundType::GetTypeExpression(child.second); - auto new_type_expr = type_expr->Copy(); - new_type_expr->SetAlias(child.first); - struct_children.push_back(std::move(new_type_expr)); + child.second->SetAlias(child.first); + struct_children.push_back(std::move(child.second)); } } return make_uniq(Identifier("STRUCT"), std::move(struct_children)); @@ -372,29 +374,25 @@ unique_ptr PEGTransformerFactory::TransformVariantType(PEGTran unique_ptr PEGTransformerFactory::TransformUnionType(PEGTransformer &transformer, - const child_list_t &col_id_type_list) { - identifier_set_t union_names; + vector>> col_id_type_list) { vector> union_children; for (auto &colid : col_id_type_list) { - union_names.insert(colid.first); - auto &type_expr = UnboundType::GetTypeExpression(colid.second); - auto new_type_expr = type_expr->Copy(); - new_type_expr->SetAlias(colid.first); - union_children.push_back(std::move(new_type_expr)); + colid.second->SetAlias(colid.first); + union_children.push_back(std::move(colid.second)); } return make_uniq(Identifier("UNION"), std::move(union_children)); } -child_list_t +vector>> PEGTransformerFactory::TransformColIdTypeList(PEGTransformer &transformer, - const vector> &col_id_type) { + vector>> col_id_type) { return col_id_type; } -pair PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, - const Identifier &col_id, - const LogicalType &type) { - return make_pair(Identifier(col_id), type); +pair> +PEGTransformerFactory::TransformColIdType(PEGTransformer &transformer, const Identifier &col_id, + unique_ptr nested_type) { + return make_pair(Identifier(col_id), std::move(nested_type)); } unique_ptr PEGTransformerFactory::TransformBitType( @@ -531,8 +529,8 @@ unique_ptr PEGTransformerFactory::TransformNumberLiteral(PEGTr } unique_ptr PEGTransformerFactory::TransformSetofType(PEGTransformer &transformer, - const LogicalType &type) { - return UnboundType::GetTypeExpression(type)->Copy(); + unique_ptr nested_type) { + return nested_type; } // StringLiteral <- '\'' [^\']* '\'' diff --git a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp index 3b1f0e552..cbae0a00c 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp @@ -132,6 +132,7 @@ unique_ptr PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_ptr base_table_name, const optional> &insert_column_list, const bool &from_or_to, unique_ptr copy_file_name, + const optional &legacy_option, const optional> ©_options) { auto result = make_uniq(); auto info = make_uniq(); @@ -149,8 +150,17 @@ PEGTransformerFactory::TransformCopyTable(PEGTransformer &transformer, unique_pt } info->format = ExtractFormat(info->file_path); + vector generic_options; + + if (legacy_option) { + generic_options.push_back(*legacy_option); + } + if (copy_options) { - auto generic_options = *copy_options; + generic_options.insert(generic_options.end(), copy_options->begin(), copy_options->end()); + } + + if (!generic_options.empty()) { SetCopyOptions(info, generic_options); } @@ -193,6 +203,11 @@ Identifier PEGTransformerFactory::TransformIdentifierColId(PEGTransformer &trans return Identifier(result); } +GenericCopyOption PEGTransformerFactory::TransformCopyLegacyOption(PEGTransformer &transformer, const bool &has_result, + const string &delimiter) { + return GenericCopyOption("delimiter", delimiter); +} + vector PEGTransformerFactory::TransformCopyOptions(PEGTransformer &transformer, const bool &has_result, const vector ©_option_list) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp index 5a76f6ac1..d8169e9d9 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_sequence.cpp @@ -130,6 +130,13 @@ pair> PEGTransformerFactory::TransformSeqNoCy return make_pair("cycle", make_uniq(SequenceInfo::SEQ_CYCLE, Value(false))); } +//! Sequence options take integer constants; anything else used to be rounded or cast silently +static void CheckIntegerSequenceOption(const char *option, const Literal &literal) { + if (!literal.IsNull() && literal.kind != LiteralKind::INTEGER) { + throw ParserException("Expected an integer argument for %s, got %s", option, literal.ToString()); + } +} + pair> PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, const bool &has_result, unique_ptr expression) { @@ -152,6 +159,7 @@ PEGTransformerFactory::TransformSeqSetIncrement(PEGTransformer &transformer, con throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption("INCREMENT", const_expr.GetLiteral()); return make_pair("increment", make_uniq(SequenceInfo::SEQ_INC, const_expr.GetLiteral().ToValue())); } @@ -179,6 +187,7 @@ PEGTransformerFactory::TransformSeqSetMinMax(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption(seq_min_or_max == "minvalue" ? "MINVALUE" : "MAXVALUE", const_expr.GetLiteral()); auto seq_info = seq_min_or_max == "minvalue" ? SequenceInfo::SEQ_MIN : SequenceInfo::SEQ_MAX; return make_pair(seq_min_or_max, make_uniq(seq_info, const_expr.GetLiteral().ToValue())); } @@ -196,6 +205,7 @@ PEGTransformerFactory::TransformSeqStartWith(PEGTransformer &transformer, const throw ParserException("Expected constant expression."); } auto &const_expr = expression->Cast(); + CheckIntegerSequenceOption("START", const_expr.GetLiteral()); return make_pair("start", make_uniq(SequenceInfo::SEQ_START, const_expr.GetLiteral().ToValue())); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp index 9d4023599..3115c9282 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_table.cpp @@ -33,6 +33,9 @@ PEGTransformerFactory::TransformCreateStatement(PEGTransformer &transformer, con } conflict_policy = OnCreateConflict::REPLACE_ON_CONFLICT; } + if (temporary && *temporary == SecretPersistType::PERSISTENT && result->info->type != CatalogType::SECRET_ENTRY) { + throw ParserException("PERSISTENT is only supported for CREATE SECRET"); + } if (result->info->type == CatalogType::SECRET_ENTRY) { auto &secret_info = result->info->Cast(); secret_info.persist_type = temporary ? *temporary : SecretPersistType::DEFAULT; diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp index 2c634282e..f342bf280 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_view.cpp @@ -102,6 +102,9 @@ PEGTransformerFactory::TransformCreateViewStmt(PEGTransformer &transformer, cons if (create_secure) { info->security_type = ViewSecurityType::SECURE_VIEW; } + if (create_recursive && !insert_column_list) { + throw ParserException("CREATE RECURSIVE VIEW requires a column list"); + } if (insert_column_list) { info->aliases = StringsToIdentifiers(*insert_column_list); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp index 55ce4fdb0..372c91309 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_delete.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_delete.cpp @@ -48,10 +48,24 @@ vector> PEGTransformerFactory::TransformDeleteUsingClause(P unique_ptr PEGTransformerFactory::TransformTruncateStatement(PEGTransformer &transformer, const bool &has_result, - unique_ptr base_table_name) { + unique_ptr base_table_name, + optional truncate_options) { + if (truncate_options && *truncate_options) { + throw NotImplementedException("TRUNCATE ... CASCADE is not implemented"); + } auto result = make_uniq(); result->node->table = std::move(base_table_name); return std::move(result); } +bool PEGTransformerFactory::TransformTruncateAliasOptions(PEGTransformer &transformer, const Identifier &target_alias, + optional truncate_drop_behavior) { + // the alias is ignored, the result is whether CASCADE was given + return truncate_drop_behavior && *truncate_drop_behavior; +} + +bool PEGTransformerFactory::TransformTruncateDropBehavior(PEGTransformer &transformer, const bool &drop_behavior) { + return drop_behavior; +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp index 0c8854dd4..c0473c3f3 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp @@ -8,6 +8,9 @@ unique_ptr PEGTransformerFactory::TransformDropStatement(PEGTransf unique_ptr drop_entries, const optional &drop_behavior) { if (drop_behavior) { + if (drop_entries->info->type == CatalogType::SECRET_ENTRY) { + throw ParserException("CASCADE and RESTRICT are not supported for DROP SECRET"); + } drop_entries->info->cascade = *drop_behavior; } return std::move(drop_entries); diff --git a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp index 42e080ed7..1a2a75c7b 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp @@ -1146,6 +1146,12 @@ BetweenInLikeOperator PEGTransformerFactory::TransformInPredicate(PEGTransformer BetweenInLikeOperator PEGTransformerFactory::TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression) { + if (has_result && between_like_op_expression->GetExpressionClass() == ExpressionClass::FUNCTION) { + auto &function_name = between_like_op_expression->Cast().FunctionName(); + if (StringUtil::StartsWith(function_name.GetIdentifierName(), "!")) { + throw ParserException("NOT cannot precede an operator that is already negated (!~~, !~~*, !~ or !~*)"); + } + } return {has_result, std::move(between_like_op_expression)}; } @@ -1947,11 +1953,12 @@ PEGTransformerFactory::TransformSliceExpression(PEGTransformer &transformer, return make_uniq(ExpressionType::ARRAY_SLICE, std::move(slice_bound)); } -vector> PEGTransformerFactory::TransformSliceBound( - PEGTransformer &transformer, optional> expression, - optional> end_slice_bound, optional> step_slice_bound) { +vector> +PEGTransformerFactory::TransformSliceBound(PEGTransformer &transformer, + optional> expression, + optional>> slice_upper_bound) { vector> slice_bounds; - if (!end_slice_bound && !step_slice_bound) { + if (!slice_upper_bound) { if (expression && *expression) { slice_bounds.push_back(std::move(*expression)); } @@ -1962,11 +1969,27 @@ vector> PEGTransformerFactory::TransformSliceBound( } else { slice_bounds.push_back(OperatorExpression::EmptySliceBound()); } - if (end_slice_bound && *end_slice_bound) { - slice_bounds.push_back(std::move(*end_slice_bound)); - } else { - slice_bounds.push_back(OperatorExpression::EmptySliceBound()); + for (auto &bound : *slice_upper_bound) { + slice_bounds.push_back(std::move(bound)); } + return slice_bounds; +} + +vector> +PEGTransformerFactory::TransformMinusStepSliceBound(PEGTransformer &transformer, + unique_ptr step_slice_bound) { + vector> slice_bounds; + slice_bounds.push_back(OperatorExpression::EmptySliceBound()); + slice_bounds.push_back(std::move(step_slice_bound)); + return slice_bounds; +} + +vector> +PEGTransformerFactory::TransformEndStepSliceBound(PEGTransformer &transformer, + unique_ptr end_slice_bound, + optional> step_slice_bound) { + vector> slice_bounds; + slice_bounds.push_back(std::move(end_slice_bound)); if (step_slice_bound && *step_slice_bound) { slice_bounds.push_back(std::move(*step_slice_bound)); } @@ -1975,19 +1998,15 @@ vector> PEGTransformerFactory::TransformSliceBound( unique_ptr PEGTransformerFactory::TransformEndSliceBound(PEGTransformer &transformer, - optional> end_slice_value) { + optional> expression) { // If either the lower or upper bound is not specified, we use an empty constant LIST, // which we handle in the execution. - if (end_slice_value && *end_slice_value) { - return std::move(*end_slice_value); + if (expression && *expression) { + return std::move(*expression); } return OperatorExpression::EmptySliceBound(); } -unique_ptr PEGTransformerFactory::TransformEndSliceMinus(PEGTransformer &transformer) { - return OperatorExpression::EmptySliceBound(); -} - unique_ptr PEGTransformerFactory::TransformStepSliceBound(PEGTransformer &transformer, optional> expression) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp index 44549a67b..84e0c6aa4 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp @@ -35,6 +35,9 @@ static const TransformFrameOps FUNC_NAME_KEYWORD_OPS = {"FuncNameKeyword", static const TransformFrameOps TYPE_NAME_KEYWORD_OPS = {"TypeNameKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; +static const TransformFrameOps BARE_LABEL_KEYWORD_OPS = { + "BareLabelKeyword", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, + &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; static const TransformFrameOps SETTING_NAME_OPS = {"SettingName", &PEGTransformerFactory::InitializeIdentifierOrKeywordTrampoline, &PEGTransformerFactory::FinalizeIdentifierOrKeywordTrampoline}; @@ -406,6 +409,8 @@ static const TransformFrameOps SCHEMA_RESERVED_TYPE_NAME_OPS = { static const TransformFrameOps TYPE_MODIFIERS_OPS = {"TypeModifiers", &PEGTransformerFactory::InitializeTypeModifiersTrampoline, &PEGTransformerFactory::FinalizeTypeModifiersTrampoline}; +static const TransformFrameOps NESTED_TYPE_OPS = {"NestedType", &PEGTransformerFactory::InitializeNestedTypeTrampoline, + &PEGTransformerFactory::FinalizeNestedTypeTrampoline}; static const TransformFrameOps ROW_TYPE_OPS = {"RowType", &PEGTransformerFactory::InitializeRowTypeTrampoline, &PEGTransformerFactory::FinalizeRowTypeTrampoline}; static const TransformFrameOps SETOF_TYPE_OPS = {"SetofType", &PEGTransformerFactory::InitializeSetofTypeTrampoline, @@ -508,6 +513,9 @@ static const TransformFrameOps IDENTIFIER_COL_ID_OPS = {"IdentifierColId", static const TransformFrameOps COPY_FILE_NAME_SUFFIX_OPS = { "CopyFileNameSuffix", &PEGTransformerFactory::InitializeCopyFileNameSuffixTrampoline, &PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline}; +static const TransformFrameOps COPY_LEGACY_OPTION_OPS = {"CopyLegacyOption", + &PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline, + &PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline}; static const TransformFrameOps COPY_OPTIONS_OPS = {"CopyOptions", &PEGTransformerFactory::InitializeCopyOptionsTrampoline, &PEGTransformerFactory::FinalizeCopyOptionsTrampoline}; @@ -1118,6 +1126,15 @@ static const TransformFrameOps DELETE_STATEMENT_OPS = {"DeleteStatement", static const TransformFrameOps TRUNCATE_STATEMENT_OPS = {"TruncateStatement", &PEGTransformerFactory::InitializeTruncateStatementTrampoline, &PEGTransformerFactory::FinalizeTruncateStatementTrampoline}; +static const TransformFrameOps TRUNCATE_OPTIONS_OPS = {"TruncateOptions", + &PEGTransformerFactory::InitializeTruncateOptionsTrampoline, + &PEGTransformerFactory::FinalizeTruncateOptionsTrampoline}; +static const TransformFrameOps TRUNCATE_ALIAS_OPTIONS_OPS = { + "TruncateAliasOptions", &PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline, + &PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline}; +static const TransformFrameOps TRUNCATE_DROP_BEHAVIOR_OPS = { + "TruncateDropBehavior", &PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline, + &PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline}; static const TransformFrameOps TARGET_OPT_ALIAS_OPS = {"TargetOptAlias", &PEGTransformerFactory::InitializeTargetOptAliasTrampoline, &PEGTransformerFactory::FinalizeTargetOptAliasTrampoline}; @@ -1972,15 +1989,18 @@ static const TransformFrameOps SLICE_EXPRESSION_OPS = {"SliceExpression", &PEGTransformerFactory::FinalizeSliceExpressionTrampoline}; static const TransformFrameOps SLICE_BOUND_OPS = {"SliceBound", &PEGTransformerFactory::InitializeSliceBoundTrampoline, &PEGTransformerFactory::FinalizeSliceBoundTrampoline}; +static const TransformFrameOps SLICE_UPPER_BOUND_OPS = {"SliceUpperBound", + &PEGTransformerFactory::InitializeSliceUpperBoundTrampoline, + &PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline}; +static const TransformFrameOps MINUS_STEP_SLICE_BOUND_OPS = { + "MinusStepSliceBound", &PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline, + &PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline}; +static const TransformFrameOps END_STEP_SLICE_BOUND_OPS = { + "EndStepSliceBound", &PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline, + &PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline}; static const TransformFrameOps END_SLICE_BOUND_OPS = {"EndSliceBound", &PEGTransformerFactory::InitializeEndSliceBoundTrampoline, &PEGTransformerFactory::FinalizeEndSliceBoundTrampoline}; -static const TransformFrameOps END_SLICE_VALUE_OPS = {"EndSliceValue", - &PEGTransformerFactory::InitializeEndSliceValueTrampoline, - &PEGTransformerFactory::FinalizeEndSliceValueTrampoline}; -static const TransformFrameOps END_SLICE_MINUS_OPS = {"EndSliceMinus", - &PEGTransformerFactory::InitializeEndSliceMinusTrampoline, - &PEGTransformerFactory::FinalizeEndSliceMinusTrampoline}; static const TransformFrameOps STEP_SLICE_BOUND_OPS = {"StepSliceBound", &PEGTransformerFactory::InitializeStepSliceBoundTrampoline, &PEGTransformerFactory::FinalizeStepSliceBoundTrampoline}; @@ -2743,6 +2763,9 @@ static const TransformFrameOps QUALIFY_CLAUSE_OPS = {"QualifyClause", static const TransformFrameOps SAMPLE_CLAUSE_OPS = {"SampleClause", &PEGTransformerFactory::InitializeSampleClauseTrampoline, &PEGTransformerFactory::FinalizeSampleClauseTrampoline}; +static const TransformFrameOps SELECT_SAMPLE_CLAUSE_OPS = { + "SelectSampleClause", &PEGTransformerFactory::InitializeSelectSampleClauseTrampoline, + &PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline}; static const TransformFrameOps WINDOW_CLAUSE_OPS = {"WindowClause", &PEGTransformerFactory::InitializeWindowClauseTrampoline, &PEGTransformerFactory::FinalizeWindowClauseTrampoline}; @@ -2995,15 +3018,15 @@ static const TransformFrameOps BASE_TABLE_ALIAS_SET_OPS = { static const TransformFrameOps UPDATE_ALIAS_OPS = {"UpdateAlias", &PEGTransformerFactory::InitializeUpdateAliasTrampoline, &PEGTransformerFactory::FinalizeUpdateAliasTrampoline}; +static const TransformFrameOps UPDATE_SET_CLAUSE_LIST_OPS = { + "UpdateSetClauseList", &PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline, + &PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline}; static const TransformFrameOps UPDATE_SET_CLAUSE_OPS = {"UpdateSetClause", &PEGTransformerFactory::InitializeUpdateSetClauseTrampoline, &PEGTransformerFactory::FinalizeUpdateSetClauseTrampoline}; static const TransformFrameOps UPDATE_SET_TUPLE_OPS = {"UpdateSetTuple", &PEGTransformerFactory::InitializeUpdateSetTupleTrampoline, &PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline}; -static const TransformFrameOps UPDATE_SET_ELEMENT_LIST_OPS = { - "UpdateSetElementList", &PEGTransformerFactory::InitializeUpdateSetElementListTrampoline, - &PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline}; static const TransformFrameOps UPDATE_SET_ELEMENT_OPS = {"UpdateSetElement", &PEGTransformerFactory::InitializeUpdateSetElementTrampoline, &PEGTransformerFactory::FinalizeUpdateSetElementTrampoline}; @@ -3065,6 +3088,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"ColumnNameKeyword", &COLUMN_NAME_KEYWORD_OPS}, {"FuncNameKeyword", &FUNC_NAME_KEYWORD_OPS}, {"TypeNameKeyword", &TYPE_NAME_KEYWORD_OPS}, + {"BareLabelKeyword", &BARE_LABEL_KEYWORD_OPS}, {"SettingName", &SETTING_NAME_OPS}, {"AlterStatement", &ALTER_STATEMENT_OPS}, {"AlterOptions", &ALTER_OPTIONS_OPS}, @@ -3195,6 +3219,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CatalogReservedSchemaTypeName", &CATALOG_RESERVED_SCHEMA_TYPE_NAME_OPS}, {"SchemaReservedTypeName", &SCHEMA_RESERVED_TYPE_NAME_OPS}, {"TypeModifiers", &TYPE_MODIFIERS_OPS}, + {"NestedType", &NESTED_TYPE_OPS}, {"RowType", &ROW_TYPE_OPS}, {"SetofType", &SETOF_TYPE_OPS}, {"UnionType", &UNION_TYPE_OPS}, @@ -3234,6 +3259,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"CopyFileNameIdentifierColId", ©_FILE_NAME_IDENTIFIER_COL_ID_OPS}, {"IdentifierColId", &IDENTIFIER_COL_ID_OPS}, {"CopyFileNameSuffix", ©_FILE_NAME_SUFFIX_OPS}, + {"CopyLegacyOption", ©_LEGACY_OPTION_OPS}, {"CopyOptions", ©_OPTIONS_OPS}, {"CopyOptionList", ©_OPTION_LIST_OPS}, {"SpecializedOptionList", &SPECIALIZED_OPTION_LIST_OPS}, @@ -3447,6 +3473,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"DeallocatePrepare", &DEALLOCATE_PREPARE_OPS}, {"DeleteStatement", &DELETE_STATEMENT_OPS}, {"TruncateStatement", &TRUNCATE_STATEMENT_OPS}, + {"TruncateOptions", &TRUNCATE_OPTIONS_OPS}, + {"TruncateAliasOptions", &TRUNCATE_ALIAS_OPTIONS_OPS}, + {"TruncateDropBehavior", &TRUNCATE_DROP_BEHAVIOR_OPS}, {"TargetOptAlias", &TARGET_OPT_ALIAS_OPS}, {"TargetAlias", &TARGET_ALIAS_OPS}, {"DeleteUsingClause", &DELETE_USING_CLAUSE_OPS}, @@ -3744,9 +3773,10 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"MethodFunctionArguments", &METHOD_FUNCTION_ARGUMENTS_OPS}, {"SliceExpression", &SLICE_EXPRESSION_OPS}, {"SliceBound", &SLICE_BOUND_OPS}, + {"SliceUpperBound", &SLICE_UPPER_BOUND_OPS}, + {"MinusStepSliceBound", &MINUS_STEP_SLICE_BOUND_OPS}, + {"EndStepSliceBound", &END_STEP_SLICE_BOUND_OPS}, {"EndSliceBound", &END_SLICE_BOUND_OPS}, - {"EndSliceValue", &END_SLICE_VALUE_OPS}, - {"EndSliceMinus", &END_SLICE_MINUS_OPS}, {"StepSliceBound", &STEP_SLICE_BOUND_OPS}, {"PostfixOperator", &POSTFIX_OPERATOR_OPS}, {"SpecialFunctionExpression", &SPECIAL_FUNCTION_EXPRESSION_OPS}, @@ -4014,6 +4044,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"HavingClause", &HAVING_CLAUSE_OPS}, {"QualifyClause", &QUALIFY_CLAUSE_OPS}, {"SampleClause", &SAMPLE_CLAUSE_OPS}, + {"SelectSampleClause", &SELECT_SAMPLE_CLAUSE_OPS}, {"WindowClause", &WINDOW_CLAUSE_OPS}, {"WindowDefinition", &WINDOW_DEFINITION_OPS}, {"SampleEntry", &SAMPLE_ENTRY_OPS}, @@ -4104,9 +4135,9 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"BaseTableSet", &BASE_TABLE_SET_OPS}, {"BaseTableAliasSet", &BASE_TABLE_ALIAS_SET_OPS}, {"UpdateAlias", &UPDATE_ALIAS_OPS}, + {"UpdateSetClauseList", &UPDATE_SET_CLAUSE_LIST_OPS}, {"UpdateSetClause", &UPDATE_SET_CLAUSE_OPS}, {"UpdateSetTuple", &UPDATE_SET_TUPLE_OPS}, - {"UpdateSetElementList", &UPDATE_SET_ELEMENT_LIST_OPS}, {"UpdateSetElement", &UPDATE_SET_ELEMENT_OPS}, {"UpdateSetColumnTarget", &UPDATE_SET_COLUMN_TARGET_OPS}, {"UseStatement", &USE_STATEMENT_OPS}, @@ -6595,6 +6626,49 @@ PEGTransformerFactory::FinalizeTypeModifiersTrampoline(PEGTransformer &transform return transformer.MakeResult>>(std::move(result)); } +void PEGTransformerFactory::InitializeNestedTypeTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &repeat_opt = list_pr.GetChild(1).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(2 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 1 + child_idx); + } + } else { + process.ReserveChildSlots(2 - 1); + } + process.PushChild({transformer.GetRule("TypeVariations"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeNestedTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(1).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } + auto type_variations = process.TakeResult>(0); + optional> array_bounds {}; + if (dynamic_child_count > 0) { + vector array_bounds_value; + for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { + array_bounds_value.push_back(process.TakeResult(i)); + } + array_bounds = std::move(array_bounds_value); + } + auto result = TransformNestedType(transformer, std::move(type_variations), array_bounds); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -6607,11 +6681,11 @@ void PEGTransformerFactory::InitializeRowTypeTrampoline(PEGTransformer &transfor arena_ptr PEGTransformerFactory::FinalizeRowTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - optional> col_id_type_list {}; + optional>>> col_id_type_list {}; if (process.child_results[0]) { - col_id_type_list = process.TakeResult>(0); + col_id_type_list = process.TakeResult>>>(0); } - auto result = TransformRowType(transformer, col_id_type_list); + auto result = TransformRowType(transformer, std::move(col_id_type_list)); return transformer.MakeResult>(std::move(result)); } @@ -6619,13 +6693,13 @@ void PEGTransformerFactory::InitializeSetofTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 0); + process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeSetofTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto type = process.TakeResult(0); - auto result = TransformSetofType(transformer, type); + auto nested_type = process.TakeResult>(0); + auto result = TransformSetofType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6638,8 +6712,8 @@ void PEGTransformerFactory::InitializeUnionTypeTrampoline(PEGTransformer &transf arena_ptr PEGTransformerFactory::FinalizeUnionTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto col_id_type_list = process.TakeResult>(0); - auto result = TransformUnionType(transformer, col_id_type_list); + auto col_id_type_list = process.TakeResult>>>(0); + auto result = TransformUnionType(transformer, std::move(col_id_type_list)); return transformer.MakeResult>(std::move(result)); } @@ -6661,12 +6735,12 @@ PEGTransformerFactory::FinalizeColIdTypeListTrampoline(PEGTransformer &transform auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(0))); auto dynamic_child_count = dynamic_list_items.size(); - vector> col_id_type; + vector>> col_id_type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - col_id_type.push_back(process.TakeResult>(i)); + col_id_type.push_back(process.TakeResult>>(i)); } - auto result = TransformColIdTypeList(transformer, col_id_type); - return transformer.MakeResult>(result); + auto result = TransformColIdTypeList(transformer, std::move(col_id_type)); + return transformer.MakeResult>>>(std::move(result)); } void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transformer, @@ -6680,7 +6754,7 @@ void PEGTransformerFactory::InitializeMapTypeTrampoline(PEGTransformer &transfor process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); } } else { process.ReserveChildSlots(1 - 1); @@ -6696,16 +6770,16 @@ arena_ptr PEGTransformerFactory::FinalizeMapTypeTrampoline auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(dynamic_list_opt.GetResult())); dynamic_child_count = dynamic_list_items.size(); } - optional> type {}; - auto &type_opt = list_pr.GetChild(1).Cast(); - if (type_opt.HasResult()) { - vector type_value; + optional>> nested_type {}; + auto &nested_type_opt = list_pr.GetChild(1).Cast(); + if (nested_type_opt.HasResult()) { + vector> nested_type_value; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - type_value.push_back(process.TakeResult(i)); + nested_type_value.push_back(process.TakeResult>(i)); } - type = std::move(type_value); + nested_type = std::move(nested_type_value); } - auto result = TransformMapType(transformer, type); + auto result = TransformMapType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6717,7 +6791,7 @@ void PEGTransformerFactory::InitializeTupleTypeTrampoline(PEGTransformer &transf process.ReserveChildSlots(1 + dynamic_child_count - 1); for (idx_t i = list_items.size(); i > 0; i--) { auto child_idx = i - 1; - process.PushChild({transformer.GetRule("Type"), list_items[child_idx].get()}, 0 + child_idx); + process.PushChild({transformer.GetRule("NestedType"), list_items[child_idx].get()}, 0 + child_idx); } } @@ -6726,11 +6800,11 @@ arena_ptr PEGTransformerFactory::FinalizeTupleTypeTrampoli auto &list_pr = process.parse_result.Cast(); auto dynamic_list_items = ExtractParseResultsFromList(ExtractResultFromParens(list_pr.GetChild(1))); auto dynamic_child_count = dynamic_list_items.size(); - vector type; + vector> nested_type; for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - type.push_back(process.TakeResult(i)); + nested_type.push_back(process.TakeResult>(i)); } - auto result = TransformTupleType(transformer, type); + auto result = TransformTupleType(transformer, std::move(nested_type)); return transformer.MakeResult>(std::move(result)); } @@ -6738,16 +6812,16 @@ void PEGTransformerFactory::InitializeColIdTypeTrampoline(PEGTransformer &transf GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(2); - process.PushChild({transformer.GetRule("Type"), list_pr.GetChild(1)}, 1); + process.PushChild({transformer.GetRule("NestedType"), list_pr.GetChild(1)}, 1); process.PushChild({transformer.GetRule("ColId"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeColIdTypeTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto col_id = process.TakeResult(0); - auto type = process.TakeResult(1); - auto result = TransformColIdType(transformer, col_id, type); - return transformer.MakeResult>(result); + auto nested_type = process.TakeResult>(1); + auto result = TransformColIdType(transformer, col_id, std::move(nested_type)); + return transformer.MakeResult>>(std::move(result)); } void PEGTransformerFactory::InitializeArrayBoundsTrampoline(PEGTransformer &transformer, @@ -7090,10 +7164,14 @@ PEGTransformerFactory::FinalizeCopyVariationsTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeCopyTableTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(5); - auto ©_options_opt = list_pr.GetChild(4).Cast(); + process.ReserveChildSlots(6); + auto ©_options_opt = list_pr.GetChild(5).Cast(); if (copy_options_opt.HasResult()) { - process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 4); + process.PushChild({transformer.GetRule("CopyOptions"), copy_options_opt.GetResult()}, 5); + } + auto ©_legacy_option_opt = list_pr.GetChild(4).Cast(); + if (copy_legacy_option_opt.HasResult()) { + process.PushChild({transformer.GetRule("CopyLegacyOption"), copy_legacy_option_opt.GetResult()}, 4); } process.PushChild({transformer.GetRule("CopyFileName"), list_pr.GetChild(3)}, 3); process.PushChild({transformer.GetRule("FromOrTo"), list_pr.GetChild(2)}, 2); @@ -7113,12 +7191,16 @@ arena_ptr PEGTransformerFactory::FinalizeCopyTableTrampoli } auto from_or_to = process.TakeResult(2); auto copy_file_name = process.TakeResult>(3); - optional> copy_options {}; + optional copy_legacy_option {}; if (process.child_results[4]) { - copy_options = process.TakeResult>(4); + copy_legacy_option = process.TakeResult(4); + } + optional> copy_options {}; + if (process.child_results[5]) { + copy_options = process.TakeResult>(5); } auto result = TransformCopyTable(transformer, std::move(base_table_name), insert_column_list, from_or_to, - std::move(copy_file_name), copy_options); + std::move(copy_file_name), copy_legacy_option, copy_options); return transformer.MakeResult>(std::move(result)); } @@ -7317,6 +7399,23 @@ PEGTransformerFactory::FinalizeCopyFileNameSuffixTrampoline(PEGTransformer &tran return transformer.MakeResult(result); } +void PEGTransformerFactory::InitializeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + process.ReserveChildSlots(0); +} + +arena_ptr +PEGTransformerFactory::FinalizeCopyLegacyOptionTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + bool has_result {}; + auto &has_result_opt = list_pr.GetChild(0).Cast(); + has_result = has_result_opt.HasResult(); + auto string_literal = TransformStringLiteral(transformer, list_pr.GetChild(2)); + auto result = TransformCopyLegacyOption(transformer, has_result, string_literal); + return transformer.MakeResult(result); +} + void PEGTransformerFactory::InitializeCopyOptionsTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -11666,7 +11765,11 @@ PEGTransformerFactory::FinalizeDeleteStatementTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeTruncateStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); + process.ReserveChildSlots(2); + auto &truncate_options_opt = list_pr.GetChild(3).Cast(); + if (truncate_options_opt.HasResult()) { + process.PushChild({transformer.GetRule("TruncateOptions"), truncate_options_opt.GetResult()}, 1); + } process.PushChild({transformer.GetRule("BaseTableName"), list_pr.GetChild(2)}, 0); } @@ -11678,10 +11781,73 @@ PEGTransformerFactory::FinalizeTruncateStatementTrampoline(PEGTransformer &trans auto &has_result_opt = list_pr.GetChild(1).Cast(); has_result = has_result_opt.HasResult(); auto base_table_name = process.TakeResult>(0); - auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name)); + optional truncate_options {}; + if (process.child_results[1]) { + truncate_options = process.TakeResult(1); + } + auto result = TransformTruncateStatement(transformer, has_result, std::move(base_table_name), truncate_options); return transformer.MakeResult>(std::move(result)); } +void PEGTransformerFactory::InitializeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult(0); + return transformer.MakeResult(result); +} + +void PEGTransformerFactory::InitializeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + auto &truncate_drop_behavior_opt = list_pr.GetChild(1).Cast(); + if (truncate_drop_behavior_opt.HasResult()) { + process.PushChild({transformer.GetRule("TruncateDropBehavior"), truncate_drop_behavior_opt.GetResult()}, 1); + } + process.PushChild({transformer.GetRule("TargetAlias"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateAliasOptionsTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto target_alias = process.TakeResult(0); + optional truncate_drop_behavior {}; + if (process.child_results[1]) { + truncate_drop_behavior = process.TakeResult(1); + } + auto result = TransformTruncateAliasOptions(transformer, target_alias, truncate_drop_behavior); + return transformer.MakeResult(result); +} + +void PEGTransformerFactory::InitializeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("DropBehavior"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeTruncateDropBehaviorTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto drop_behavior = process.TakeResult(0); + auto result = TransformTruncateDropBehavior(transformer, drop_behavior); + return transformer.MakeResult(result); +} + void PEGTransformerFactory::InitializeTargetOptAliasTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -17781,14 +17947,10 @@ PEGTransformerFactory::FinalizeSliceExpressionTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); - auto &step_slice_bound_opt = list_pr.GetChild(2).Cast(); - if (step_slice_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 2); - } - auto &end_slice_bound_opt = list_pr.GetChild(1).Cast(); - if (end_slice_bound_opt.HasResult()) { - process.PushChild({transformer.GetRule("EndSliceBound"), end_slice_bound_opt.GetResult()}, 1); + process.ReserveChildSlots(2); + auto &slice_upper_bound_opt = list_pr.GetChild(1).Cast(); + if (slice_upper_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("SliceUpperBound"), slice_upper_bound_opt.GetResult()}, 1); } auto &expression_opt = list_pr.GetChild(0).Cast(); if (expression_opt.HasResult()) { @@ -17802,70 +17964,91 @@ PEGTransformerFactory::FinalizeSliceBoundTrampoline(PEGTransformer &transformer, if (process.child_results[0]) { expression = process.TakeResult>(0); } - optional> end_slice_bound {}; + optional>> slice_upper_bound {}; if (process.child_results[1]) { - end_slice_bound = process.TakeResult>(1); + slice_upper_bound = process.TakeResult>>(1); } - optional> step_slice_bound {}; - if (process.child_results[2]) { - step_slice_bound = process.TakeResult>(2); - } - auto result = TransformSliceBound(transformer, std::move(expression), std::move(end_slice_bound), - std::move(step_slice_bound)); + auto result = TransformSliceBound(transformer, std::move(expression), std::move(slice_upper_bound)); return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - auto &end_slice_value_opt = list_pr.GetChild(1).Cast(); - if (end_slice_value_opt.HasResult()) { - process.PushChild({transformer.GetRule("EndSliceValue"), end_slice_value_opt.GetResult()}, 0); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr -PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - optional> end_slice_value {}; - if (process.child_results[0]) { - end_slice_value = process.TakeResult>(0); - } - auto result = TransformEndSliceBound(transformer, std::move(end_slice_value)); - return transformer.MakeResult>(std::move(result)); +PEGTransformerFactory::FinalizeSliceUpperBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult>>(0); + return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { +void PEGTransformerFactory::InitializeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - auto &choice_pr = list_pr.Child(0); - auto &choice_result = choice_pr.GetResult(); process.ReserveChildSlots(1); - auto child_rule = choice_result.GetRule(); - auto has_transform_process = child_rule && child_rule->transform_process; - if (!has_transform_process) { - throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + process.PushChild({transformer.GetRule("StepSliceBound"), list_pr.GetChild(2)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeMinusStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto step_slice_bound = process.TakeResult>(0); + auto result = TransformMinusStepSliceBound(transformer, std::move(step_slice_bound)); + return transformer.MakeResult>>(std::move(result)); +} + +void PEGTransformerFactory::InitializeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + auto &step_slice_bound_opt = list_pr.GetChild(1).Cast(); + if (step_slice_bound_opt.HasResult()) { + process.PushChild({transformer.GetRule("StepSliceBound"), step_slice_bound_opt.GetResult()}, 1); } - process.PushChild({*child_rule, choice_result}, 0); + process.PushChild({transformer.GetRule("EndSliceBound"), list_pr.GetChild(0)}, 0); } arena_ptr -PEGTransformerFactory::FinalizeEndSliceValueTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto result = process.TakeResult>(0); - return transformer.MakeResult>(std::move(result)); +PEGTransformerFactory::FinalizeEndStepSliceBoundTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto end_slice_bound = process.TakeResult>(0); + optional> step_slice_bound {}; + if (process.child_results[1]) { + step_slice_bound = process.TakeResult>(1); + } + auto result = TransformEndStepSliceBound(transformer, std::move(end_slice_bound), std::move(step_slice_bound)); + return transformer.MakeResult>>(std::move(result)); } -void PEGTransformerFactory::InitializeEndSliceMinusTrampoline(PEGTransformer &transformer, +void PEGTransformerFactory::InitializeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + auto &expression_opt = list_pr.GetChild(1).Cast(); + if (expression_opt.HasResult()) { + process.PushChild({transformer.GetRule("Expression"), expression_opt.GetResult()}, 0); + } } arena_ptr -PEGTransformerFactory::FinalizeEndSliceMinusTrampoline(PEGTransformer &transformer, +PEGTransformerFactory::FinalizeEndSliceBoundTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto result = TransformEndSliceMinus(transformer); + optional> expression {}; + if (process.child_results[0]) { + expression = process.TakeResult>(0); + } + auto result = TransformEndSliceBound(transformer, std::move(expression)); return transformer.MakeResult>(std::move(result)); } @@ -18915,15 +19098,15 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); process.ReserveChildSlots(8); - auto &returning_clause_opt = list_pr.GetChild(9).Cast(); + auto &returning_clause_opt = list_pr.GetChild(10).Cast(); if (returning_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("ReturningClause"), returning_clause_opt.GetResult()}, 7); } - auto &on_conflict_clause_opt = list_pr.GetChild(8).Cast(); + auto &on_conflict_clause_opt = list_pr.GetChild(9).Cast(); if (on_conflict_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("OnConflictClause"), on_conflict_clause_opt.GetResult()}, 6); } - process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(7)}, 5); + process.PushChild({transformer.GetRule("InsertValues"), list_pr.GetChild(8)}, 5); auto &insert_column_list_opt = list_pr.GetChild(6).Cast(); if (insert_column_list_opt.HasResult()) { process.PushChild({transformer.GetRule("InsertColumnList"), insert_column_list_opt.GetResult()}, 4); @@ -18946,6 +19129,7 @@ void PEGTransformerFactory::InitializeInsertStatementTrampoline(PEGTransformer & arena_ptr PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); optional with_clause {}; if (process.child_results[0]) { with_clause = process.TakeResult(0); @@ -18963,6 +19147,9 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[4]) { insert_column_list = process.TakeResult>(4); } + bool has_result {}; + auto &has_result_opt = list_pr.GetChild(7).Cast(); + has_result = has_result_opt.HasResult(); auto insert_values = process.TakeResult(5); optional> on_conflict_clause {}; if (process.child_results[6]) { @@ -18972,9 +19159,10 @@ PEGTransformerFactory::FinalizeInsertStatementTrampoline(PEGTransformer &transfo if (process.child_results[7]) { returning_clause = process.TakeResult>>(7); } - auto result = TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), - by_name_or_position, insert_column_list, std::move(insert_values), - std::move(on_conflict_clause), std::move(returning_clause)); + auto result = + TransformInsertStatement(transformer, std::move(with_clause), or_action, std::move(insert_target), + by_name_or_position, insert_column_list, has_result, std::move(insert_values), + std::move(on_conflict_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -19327,18 +19515,18 @@ void PEGTransformerFactory::InitializeOnConflictUpdateTrampoline(PEGTransformer if (where_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("WhereClause"), where_clause_opt.GetResult()}, 1); } - process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 0); + process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 0); } arena_ptr PEGTransformerFactory::FinalizeOnConflictUpdateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto update_set_clause = process.TakeResult>(0); + auto update_set_clause_list = process.TakeResult>(0); optional> where_clause {}; if (process.child_results[1]) { where_clause = process.TakeResult>(1); } - auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause), std::move(where_clause)); + auto result = TransformOnConflictUpdate(transformer, std::move(update_set_clause_list), std::move(where_clause)); return transformer.MakeResult>(std::move(result)); } @@ -21479,7 +21667,11 @@ PEGTransformerFactory::FinalizeInnerTableRefTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableSubqueryTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(4); + process.ReserveChildSlots(5); + auto &sample_clause_opt = list_pr.GetChild(4).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 4); + } auto &table_alias_opt = list_pr.GetChild(3).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 3); @@ -21511,8 +21703,12 @@ PEGTransformerFactory::FinalizeTableSubqueryTrampoline(PEGTransformer &transform if (process.child_results[3]) { table_alias = process.TakeResult(3); } - auto result = - TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), table_alias); + optional> sample_clause {}; + if (process.child_results[4]) { + sample_clause = process.TakeResult>(4); + } + auto result = TransformTableSubquery(transformer, table_alias_colon, lateral, std::move(subquery_reference), + table_alias, std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -22077,7 +22273,11 @@ PEGTransformerFactory::FinalizeTableFunctionTrampoline(PEGTransformer &transform void PEGTransformerFactory::InitializeTableFunctionLateralOptTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(5); + process.ReserveChildSlots(6); + auto &sample_clause_opt = list_pr.GetChild(5).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 5); + } auto &table_alias_opt = list_pr.GetChild(4).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 4); @@ -22111,8 +22311,13 @@ PEGTransformerFactory::FinalizeTableFunctionLateralOptTrampoline(PEGTransformer if (process.child_results[4]) { table_alias = process.TakeResult(4); } + optional> sample_clause {}; + if (process.child_results[5]) { + sample_clause = process.TakeResult>(5); + } auto result = TransformTableFunctionLateralOpt(transformer, lateral, qualified_table_function, - std::move(table_function_arguments), with_ordinality, table_alias); + std::move(table_function_arguments), with_ordinality, table_alias, + std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -23298,6 +23503,21 @@ PEGTransformerFactory::FinalizeSampleClauseTrampoline(PEGTransformer &transforme return transformer.MakeResult>(std::move(result)); } +void PEGTransformerFactory::InitializeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("SampleEntry"), list_pr.GetChild(1)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeSelectSampleClauseTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto sample_entry = process.TakeResult>(0); + auto result = TransformSelectSampleClause(transformer, std::move(sample_entry)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeWindowClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -24250,12 +24470,12 @@ PEGTransformerFactory::FinalizeExpressionOptIdentifierTrampoline(PEGTransformer GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); auto expression = process.TakeResult>(0); - optional identifier {}; - auto &identifier_opt = list_pr.GetChild(1).Cast(); - if (identifier_opt.HasResult()) { - identifier = identifier_opt.GetResult().Cast().identifier; + optional bare_col_label {}; + auto &bare_col_label_opt = list_pr.GetChild(1).Cast(); + if (bare_col_label_opt.HasResult()) { + bare_col_label = bare_col_label_opt.GetResult().Cast().identifier; } - auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), identifier); + auto result = TransformExpressionOptIdentifier(transformer, std::move(expression), bare_col_label); return transformer.MakeResult>(std::move(result)); } @@ -24827,7 +25047,7 @@ void PEGTransformerFactory::InitializeUpdateStatementTrampoline(PEGTransformer & if (from_clause_opt.HasResult()) { process.PushChild({transformer.GetRule("FromClause"), from_clause_opt.GetResult()}, 3); } - process.PushChild({transformer.GetRule("UpdateSetClause"), list_pr.GetChild(3)}, 2); + process.PushChild({transformer.GetRule("UpdateSetClauseList"), list_pr.GetChild(3)}, 2); process.PushChild({transformer.GetRule("UpdateTarget"), list_pr.GetChild(2)}, 1); auto &with_clause_opt = list_pr.GetChild(0).Cast(); if (with_clause_opt.HasResult()) { @@ -24843,7 +25063,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo with_clause = process.TakeResult(0); } auto update_target = process.TakeResult>(1); - auto update_set_clause = process.TakeResult>(2); + auto update_set_clause_list = process.TakeResult>(2); optional> from_clause {}; if (process.child_results[3]) { from_clause = process.TakeResult>(3); @@ -24857,7 +25077,7 @@ PEGTransformerFactory::FinalizeUpdateStatementTrampoline(PEGTransformer &transfo returning_clause = process.TakeResult>>(5); } auto result = TransformUpdateStatement(transformer, std::move(with_clause), std::move(update_target), - std::move(update_set_clause), std::move(from_clause), + std::move(update_set_clause_list), std::move(from_clause), std::move(where_clause), std::move(returning_clause)); return transformer.MakeResult>(std::move(result)); } @@ -24937,6 +25157,32 @@ PEGTransformerFactory::FinalizeUpdateAliasTrampoline(PEGTransformer &transformer return transformer.MakeResult(result); } +void PEGTransformerFactory::InitializeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = list_items.size(); + process.ReserveChildSlots(1 + dynamic_child_count - 1); + for (idx_t i = list_items.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("UpdateSetClause"), list_items[child_idx].get()}, 0 + child_idx); + } +} + +arena_ptr +PEGTransformerFactory::FinalizeUpdateSetClauseListTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); + auto dynamic_child_count = dynamic_list_items.size(); + vector> update_set_clause; + for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { + update_set_clause.push_back(process.TakeResult>(i)); + } + auto result = TransformUpdateSetClauseList(transformer, std::move(update_set_clause)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeUpdateSetClauseTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -24979,32 +25225,6 @@ PEGTransformerFactory::FinalizeUpdateSetTupleTrampoline(PEGTransformer &transfor return transformer.MakeResult>(std::move(result)); } -void PEGTransformerFactory::InitializeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = list_items.size(); - process.ReserveChildSlots(1 + dynamic_child_count - 1); - for (idx_t i = list_items.size(); i > 0; i--) { - auto child_idx = i - 1; - process.PushChild({transformer.GetRule("UpdateSetElement"), list_items[child_idx].get()}, 0 + child_idx); - } -} - -arena_ptr -PEGTransformerFactory::FinalizeUpdateSetElementListTrampoline(PEGTransformer &transformer, - GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(0)); - auto dynamic_child_count = dynamic_list_items.size(); - vector>> update_set_element; - for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { - update_set_element.push_back(process.TakeResult>>(i)); - } - auto result = TransformUpdateSetElementList(transformer, std::move(update_set_element)); - return transformer.MakeResult>(std::move(result)); -} - void PEGTransformerFactory::InitializeUpdateSetElementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -25019,7 +25239,7 @@ PEGTransformerFactory::FinalizeUpdateSetElementTrampoline(PEGTransformer &transf auto update_set_column_target = process.TakeResult(0); auto expression = process.TakeResult>(1); auto result = TransformUpdateSetElement(transformer, update_set_column_target, std::move(expression)); - return transformer.MakeResult>>(std::move(result)); + return transformer.MakeResult>(std::move(result)); } void PEGTransformerFactory::InitializeUpdateSetColumnTargetTrampoline(PEGTransformer &transformer, @@ -25101,29 +25321,91 @@ arena_ptr PEGTransformerFactory::FinalizeUseTargetTrampoli void PEGTransformerFactory::InitializeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process && + (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || + choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || + choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { + return; + } + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr PEGTransformerFactory::FinalizeSchemaNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto schema_name = list_pr.GetChild(0).Cast().identifier; - auto result = TransformSchemaNameAsUseTarget(transformer, schema_name); + string child; + if (process.child_results[0]) { + child = process.TakeResult(0); + } else { + auto &list_pr = process.parse_result.Cast(); + auto &choice_result = list_pr.Child(0).GetResult(); + if (choice_result.type == ParseResultType::IDENTIFIER) { + child = choice_result.Cast().identifier.GetIdentifierName(); + } else if (choice_result.type == ParseResultType::KEYWORD) { + child = choice_result.Cast().keyword; + } else if (choice_result.type == ParseResultType::STRING) { + child = choice_result.Cast().result; + } else if (choice_result.type == ParseResultType::OPERATOR) { + child = choice_result.Cast().operator_token; + } else { + child = TransformIdentifierOrKeyword(transformer, choice_result); + } + } + auto result = TransformSchemaNameAsUseTarget(transformer, child); return transformer.MakeResult(result); } void PEGTransformerFactory::InitializeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process && + (choice_result.type == ParseResultType::IDENTIFIER || choice_result.type == ParseResultType::KEYWORD || + choice_result.type == ParseResultType::STRING || choice_result.type == ParseResultType::OPERATOR || + choice_result.type == ParseResultType::CHOICE || choice_result.type == ParseResultType::LIST)) { + return; + } + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); } arena_ptr PEGTransformerFactory::FinalizeCatalogNameAsUseTargetTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto &list_pr = process.parse_result.Cast(); - auto catalog_name = list_pr.GetChild(0).Cast().identifier; - auto result = TransformCatalogNameAsUseTarget(transformer, catalog_name); + string child; + if (process.child_results[0]) { + child = process.TakeResult(0); + } else { + auto &list_pr = process.parse_result.Cast(); + auto &choice_result = list_pr.Child(0).GetResult(); + if (choice_result.type == ParseResultType::IDENTIFIER) { + child = choice_result.Cast().identifier.GetIdentifierName(); + } else if (choice_result.type == ParseResultType::KEYWORD) { + child = choice_result.Cast().keyword; + } else if (choice_result.type == ParseResultType::STRING) { + child = choice_result.Cast().result; + } else if (choice_result.type == ParseResultType::OPERATOR) { + child = choice_result.Cast().operator_token; + } else { + child = TransformIdentifierOrKeyword(transformer, choice_result); + } + } + auto result = TransformCatalogNameAsUseTarget(transformer, child); return transformer.MakeResult(result); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp index 4bd7f30df..0f152888e 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_insert.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_insert.cpp @@ -10,7 +10,7 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( PEGTransformer &transformer, optional with_clause, const optional &or_action, unique_ptr insert_target, const optional &by_name_or_position, const optional> &insert_column_list, - InsertValues insert_values, optional> on_conflict_clause, + bool has_overriding_clause, InsertValues insert_values, optional> on_conflict_clause, optional>> returning_clause) { auto result = make_uniq(); auto &node = *result->node; @@ -26,6 +26,10 @@ unique_ptr PEGTransformerFactory::TransformInsertStatement( throw ParserException( "You can not provide both a column list and DEFAULT VALUES, please remove one of the two"); } + if (has_overriding_clause && insert_values.default_values) { + throw ParserException("OVERRIDING SYSTEM|USER VALUE can not be combined with DEFAULT VALUES, " + "please remove one of the two"); + } if (insert_values.default_values) { node.default_values = true; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp index 0424b6a21..0c168a38d 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_pivot.cpp @@ -85,12 +85,15 @@ arena_ptr PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto &pivot_columns = list_pr.Child(2); + auto &pivot_aggregates = list_pr.Child(3); + auto &pivot_group = list_pr.Child(4); + if (!pivot_columns.HasResult() && !pivot_aggregates.HasResult() && !pivot_group.HasResult()) { + throw ParserException("PIVOT requires an ON, USING or GROUP BY clause"); + } if (!process.child_results[4]) { bool has_parameters = transformer.ParamCount() > process.manual_state; process.SetChildResult(4, transformer.MakeResult(has_parameters)); - auto &pivot_columns = list_pr.Child(2); - auto &pivot_aggregates = list_pr.Child(3); - auto &pivot_group = list_pr.Child(4); bool pushed_child = false; if (pivot_group.HasResult()) { process.PushChild({pivot_group.GetResult()}, 3); @@ -110,8 +113,6 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor } auto source = process.TakeResult>(0); auto has_parameters = process.TakeResult(4); - auto &pivot_columns = list_pr.Child(2); - auto &pivot_group = list_pr.Child(4); auto select_node = make_uniq(); if (!pivot_columns.HasResult()) { select_node->from_table = std::move(source); @@ -160,7 +161,6 @@ PEGTransformerFactory::FinalizePivotStatementTrampoline(PEGTransformer &transfor select_node->select_list.push_back(make_uniq()); auto pivot_ref = make_uniq(); pivot_ref->source = std::move(source); - auto &pivot_aggregates = list_pr.Child(3); if (pivot_aggregates.HasResult()) { pivot_ref->aggregates = process.TakeResult>>(2); } else { diff --git a/src/duckdb/src/parser/peg/transformer/transform_select.cpp b/src/duckdb/src/parser/peg/transformer/transform_select.cpp index b06a869b8..49debbefe 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_select.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_select.cpp @@ -318,6 +318,10 @@ PEGTransformerFactory::FinalizeSimpleSelectTrampoline(PEGTransformer &transforme FunctionArgument PEGTransformerFactory::TransformNamedFunctionArgument(PEGTransformer &transformer, MacroParameter named_parameter) { + if (named_parameter.type != LogicalType::UNKNOWN) { + throw ParserException("Named argument \"%s\" of a function call cannot have a type", + named_parameter.name.GetIdentifierName()); + } named_parameter.expression->SetAlias(named_parameter.name); return FunctionArgument(named_parameter.name, std::move(named_parameter.expression)); } @@ -769,7 +773,7 @@ JoinType PEGTransformerFactory::TransformInnerJoin(PEGTransformer &transformer) unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, vector table_function_arguments, const optional &with_ordinality, - const optional &table_alias) { + const optional &table_alias, optional> sample_clause) { auto result = make_uniq(); result->with_ordinality = @@ -779,6 +783,9 @@ unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( result->alias = table_alias->name; result->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + result->sample = std::move(*sample_clause); + } return std::move(result); } @@ -1348,7 +1355,8 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme const optional &table_alias_colon, const optional &lateral, unique_ptr subquery_reference, - const optional &table_alias) { + const optional &table_alias, + optional> sample_clause) { if (table_alias_colon && table_alias) { throw ParserException("Table reference %s cannot have two aliases", subquery_reference->ToString()); } @@ -1359,6 +1367,9 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme subquery_reference->alias = table_alias->name; subquery_reference->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + subquery_reference->sample = std::move(*sample_clause); + } return subquery_reference; } @@ -1657,6 +1668,11 @@ unique_ptr PEGTransformerFactory::TransformSampleClause(PEGTransf return sample_entry; } +unique_ptr PEGTransformerFactory::TransformSelectSampleClause(PEGTransformer &transformer, + unique_ptr sample_entry) { + return sample_entry; +} + vector> PEGTransformerFactory::TransformWindowClause(PEGTransformer &transformer, vector> window_definition) { diff --git a/src/duckdb/src/parser/peg/transformer/transform_update.cpp b/src/duckdb/src/parser/peg/transformer/transform_update.cpp index d8c447127..439621d07 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_update.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_update.cpp @@ -81,20 +81,26 @@ unique_ptr PEGTransformerFactory::TransformUpdateSetTuple(PEGTran return result; } -unique_ptr PEGTransformerFactory::TransformUpdateSetElementList( - PEGTransformer &transformer, vector>> update_set_element) { +unique_ptr +PEGTransformerFactory::TransformUpdateSetClauseList(PEGTransformer &transformer, + vector> update_set_info) { auto result = make_uniq(); - for (auto &element : update_set_element) { - result->columns.emplace_back(std::move(element.first)); - result->expressions.push_back(std::move(element.second)); + for (auto &info : update_set_info) { + for (idx_t i = 0; i < info->columns.size(); i++) { + result->columns.emplace_back(std::move(info->columns[i])); + result->expressions.push_back(std::move(info->expressions[i])); + } } return result; } -pair> -PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, const string &update_set_column_target, - unique_ptr expression) { - return {update_set_column_target, std::move(expression)}; +unique_ptr PEGTransformerFactory::TransformUpdateSetElement(PEGTransformer &transformer, + const string &update_set_column_target, + unique_ptr expression) { + auto result = make_uniq(); + result->columns.emplace_back(update_set_column_target); + result->expressions.push_back(std::move(expression)); + return result; } string PEGTransformerFactory::TransformUpdateSetColumnTarget(PEGTransformer &transformer, const Identifier &column_name, diff --git a/src/duckdb/src/parser/peg/transformer/transform_use.cpp b/src/duckdb/src/parser/peg/transformer/transform_use.cpp index e702245ac..67b487a30 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_use.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_use.cpp @@ -17,19 +17,17 @@ unique_ptr PEGTransformerFactory::TransformUseStatement(PEGTransfo return make_uniq("schema", std::move(value_expr), SetScope::AUTOMATIC); } -// UseTarget <- UseTargetCatalogSchema / SchemaName / CatalogName +// UseTarget <- UseTargetCatalogSchema / SchemaNameAsUseTarget / CatalogNameAsUseTarget +// SchemaNameAsUseTarget <- SchemaName / StringLiteral QualifiedName PEGTransformerFactory::TransformSchemaNameAsUseTarget(PEGTransformer &transformer, - const Identifier &schema_name) { - QualifiedName result; - result = QualifiedName(schema_name); - return result; + const string &schema_name) { + return QualifiedName(Identifier(schema_name)); } +// CatalogNameAsUseTarget <- CatalogName / StringLiteral QualifiedName PEGTransformerFactory::TransformCatalogNameAsUseTarget(PEGTransformer &transformer, - const Identifier &catalog_name) { - QualifiedName result; - result = QualifiedName(catalog_name); - return result; + const string &catalog_name) { + return QualifiedName(Identifier(catalog_name)); } // UseTargetCatalogSchema <- CatalogName '.' ReservedSchemaName DotIdentifier* diff --git a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp index eca1e184c..22b371bbb 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_vacuum.cpp @@ -58,6 +58,8 @@ VacuumOptions PEGTransformerFactory::TransformVacuumParensOptions(PEGTransformer } if (StringUtil::CIEquals(option, "analyze")) { options.analyze = true; + } else { + throw ParserException("unrecognized VACUUM option \"%s\"", option); } } return options; diff --git a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp index 634332130..12c63762b 100644 --- a/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_type_expression.cpp @@ -80,6 +80,7 @@ BindResult ExpressionBinder::BindExpression(TypeExpression &type_expr, idx_t dep for (auto ¶m : unbound_parameters) { // Otherwise, try to fold it to a constant value ConstantBinder binder(this->binder, context, StringUtil::Format("Type parameter for type '%s'", type_name)); + binder.ContinueStackDepth(*this); auto expr = param->Copy(); auto bound_expr = binder.Bind(expr); diff --git a/src/duckdb/src/planner/expression_binder.cpp b/src/duckdb/src/planner/expression_binder.cpp index 1c227e951..7af091211 100644 --- a/src/duckdb/src/planner/expression_binder.cpp +++ b/src/duckdb/src/planner/expression_binder.cpp @@ -32,6 +32,10 @@ void ExpressionBinder::InitializeStackCheck() { } } +void ExpressionBinder::ContinueStackDepth(const ExpressionBinder &parent) { + stack_depth = parent.stack_depth; +} + StackChecker ExpressionBinder::StackCheck(const ParsedExpression &expr, idx_t extra_stack) { D_ASSERT(stack_depth != DConstants::INVALID_INDEX); auto max_expression_depth = Settings::Get(context); diff --git a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp index 222b68dbc..3a0a18b78 100644 --- a/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_bloom_function.cpp @@ -19,6 +19,9 @@ #include "duckdb/storage/statistics/numeric_stats.hpp" #include "duckdb/storage/buffer_manager.hpp" +#include +#include + namespace duckdb { static constexpr idx_t MAX_NUM_SECTORS = (1ULL << 26); @@ -27,11 +30,23 @@ static constexpr idx_t MIN_NUM_BITS = 512; static constexpr idx_t LOG_SECTOR_SIZE = 6; // a sector is 64 bits, log2(64) = 6 static constexpr idx_t SHIFT_MASK = 0x3F3F3F3F3F3F3F3F; // 6 bits for 64 positions static constexpr idx_t N_BITS = 4; // the number of bits to set per hash +static constexpr idx_t BITS_PER_SECTOR = 1ULL << LOG_SECTOR_SIZE; + +//! A sector is one entry of the bit array, so the two have to agree on how wide it is +static_assert(BITS_PER_SECTOR == sizeof(uint64_t) * 8, "a bloom filter sector must be exactly one array entry"); + +//! A filter is sized from a cardinality estimate, so never let it claim more than this share of the memory budget +static constexpr idx_t MAX_MEMORY_FRACTION = 64; void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { BufferManager &buffer_manager = BufferManager::GetBufferManager(context_p); - num_sectors = GetNumberOfSectors(number_of_rows); + // a capped filter stays complete - fewer sectors only raise the false positive rate, and Seal drops a + // filter that ended up too dense to reject anything + const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; + const idx_t budget_bytes = buffer_manager.GetMaxMemory() / MAX_MEMORY_FRACTION; + const idx_t budget_sectors = MaxValue(PreviousPowerOfTwo(budget_bytes / sizeof(uint64_t)), min_sectors); + num_sectors = MinValue(GetNumberOfSectors(number_of_rows), budget_sectors); bitmask = num_sectors - 1; buf_ = buffer_manager.GetBufferAllocator().Allocate(64 + num_sectors * sizeof(uint64_t)); @@ -40,24 +55,79 @@ void BloomFilter::Initialize(ClientContext &context_p, idx_t number_of_rows) { std::fill_n(bf, num_sectors, 0); initialized = true; + sealed = false; +} + +void BloomFilter::Fold(idx_t new_num_sectors) { + D_ASSERT(initialized); + D_ASSERT(!sealed); + D_ASSERT(IsPowerOfTwo(new_num_sectors)); + D_ASSERT(new_num_sectors <= num_sectors); + const uint64_t new_bitmask = new_num_sectors - 1; + for (idx_t i = new_num_sectors; i < num_sectors; i++) { + bf[i & new_bitmask] |= bf[i]; + } + num_sectors = new_num_sectors; + bitmask = new_bitmask; } -void BloomFilter::Merge(const BloomFilter &other) { +double BloomFilter::Density() const { D_ASSERT(initialized); - D_ASSERT(other.initialized); - D_ASSERT(num_sectors == other.num_sectors); - D_ASSERT(bitmask == other.bitmask); + idx_t bits_set = 0; for (idx_t i = 0; i < num_sectors; i++) { - bf[i] |= other.bf[i]; + bits_set += static_cast(std::bitset(bf[i]).count()); } + return static_cast(bits_set) / static_cast(num_sectors * BITS_PER_SECTOR); } -void BloomFilter::Reset() { - buf_.Reset(); - num_sectors = 0; - bitmask = 0; - initialized = false; - bf = nullptr; +//! False positive rate the sizing in GetNumberOfSectors aims for. With m bits, n keys and k probes per key a +//! given bit stays zero with probability (1 - 1/m)^(n*k) ~ e^(-k*n/m), so all k probed bits are set - a false +//! positive - with probability (1 - e^(-k*n/m))^k. Here m/n is MIN_NUM_BITS_PER_KEY and k is N_BITS. +static double TargetFalsePositiveRate() { + const double load = static_cast(N_BITS) / static_cast(MIN_NUM_BITS_PER_KEY); + return std::pow(1.0 - std::exp(-load), static_cast(N_BITS)); +} + +void BloomFilter::Seal(ClientContext &context) { + if (!initialized || sealed) { + sealed = true; + return; + } + const idx_t min_sectors = MIN_NUM_BITS >> LOG_SECTOR_SIZE; + double density = Density(); + // a density of exactly zero means no key was ever inserted, so there is nothing to fold down + if (density > 0.0) { + // each fold ORs two sectors together, so the expected density goes from p to 1 - (1 - p)^2 + const double target = TargetFalsePositiveRate(); + // target_sectors is only a candidate size - nothing is folded until the loop has settled on one + idx_t target_sectors = num_sectors; + while (target_sectors > min_sectors) { + const double folded_density = 1.0 - (1.0 - density) * (1.0 - density); + if (std::pow(folded_density, static_cast(N_BITS)) > target) { + break; + } + density = folded_density; + target_sectors >>= 1; + } + if (target_sectors < num_sectors) { + Fold(target_sectors); + // the filter was sized from an estimate, so hand the memory folding freed up back to the allocator + auto &allocator = BufferManager::GetBufferManager(context).GetBufferAllocator(); + auto compacted_buf = allocator.Allocate(64 + num_sectors * sizeof(uint64_t)); + auto compacted_bf = + reinterpret_cast((64ULL + reinterpret_cast(compacted_buf.get())) & ~63ULL); + std::copy_n(bf, num_sectors, compacted_bf); + buf_ = std::move(compacted_buf); + bf = compacted_bf; + } + } + sealed = true; + // a filter that accepts more than the scan-time policy tolerates gets paused on its first vectors anyway, + // so it is not worth the memory it holds or the hashing it costs + float selectivity_threshold; + idx_t n_vectors_to_check; + GetThresholdAndVectorsToCheck(SelectivityOptionalFilterType::BF, selectivity_threshold, n_vectors_to_check); + useful = std::pow(density, static_cast(N_BITS)) < static_cast(selectivity_threshold); } idx_t BloomFilter::GetNumberOfSectors(idx_t number_of_rows) { @@ -79,7 +149,25 @@ inline uint64_t GetMask(const hash_t hash) { return mask; } -void BloomFilter::InsertHashes(const Vector &hashes_v) const { +void BloomFilter::InsertHashes(const Vector &hashes_v, const SelectionVector &sel, const idx_t count) { + D_ASSERT(hashes_v.GetType() == LogicalType::HASH); + if (count == 0) { + return; + } + // VectorOperations::Hash produces a constant vector for a constant input, and a flat one otherwise + if (hashes_v.GetVectorType() == VectorType::CONSTANT_VECTOR) { + InsertOne(*ConstantVector::GetData(hashes_v)); + return; + } + D_ASSERT(hashes_v.GetVectorType() == VectorType::FLAT_VECTOR); + const auto hashes = FlatVector::GetData(hashes_v); + for (idx_t i = 0; i < count; i++) { + // sel can be the incremental selection vector, so this cannot use get_index_unsafe + InsertOne(hashes[sel.get_index(i)]); + } +} + +void BloomFilter::InsertHashes(const Vector &hashes_v) { for (auto hash : hashes_v.Values()) { InsertOne(hash.GetValue()); } @@ -112,17 +200,23 @@ idx_t BloomFilter::LookupHashes(const Vector &hashes_v, const SelectionVector &s return found_count; } -inline void BloomFilter::InsertOne(const hash_t hash) const { +inline void BloomFilter::InsertOne(const hash_t hash) { D_ASSERT(initialized); + D_ASSERT(!sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); - slot.fetch_or(mask, std::memory_order_relaxed); + // duplicate keys find their bits already set - checking first keeps the shared sector read-only then, + // instead of a contended read-modify-write + if ((slot.load(std::memory_order_relaxed) & mask) != mask) { + slot.fetch_or(mask, std::memory_order_relaxed); + } } bool BloomFilter::LookupOne(const uint64_t hash) const { D_ASSERT(initialized); + D_ASSERT(sealed); const uint64_t bf_offset = hash & bitmask; const uint64_t mask = GetMask(hash); atomic &slot = *reinterpret_cast *>(&bf[bf_offset]); @@ -131,10 +225,10 @@ bool BloomFilter::LookupOne(const uint64_t hash) const { return (bf_entry & mask) == mask; } -BloomFilterFunctionData::BloomFilterFunctionData(optional_ptr filter_p, bool filters_null_values_p, +BloomFilterFunctionData::BloomFilterFunctionData(shared_ptr filter_p, bool filters_null_values_p, const string &key_column_name_p, const LogicalType &key_type_p, float selectivity_threshold_p, idx_t n_vectors_to_check_p) - : filter(filter_p), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), + : filter(std::move(filter_p)), filters_null_values(filters_null_values_p), key_column_name(key_column_name_p), key_type(key_type_p), selectivity_threshold(selectivity_threshold_p), n_vectors_to_check(n_vectors_to_check_p) { } diff --git a/src/duckdb/src/planner/filter/table_filter_functions.cpp b/src/duckdb/src/planner/filter/table_filter_functions.cpp index a4237539e..5e84362cf 100644 --- a/src/duckdb/src/planner/filter/table_filter_functions.cpp +++ b/src/duckdb/src/planner/filter/table_filter_functions.cpp @@ -118,7 +118,8 @@ void TableFilterFunctionSerialize(Serializer &serializer, const optional_ptr TableFilterFunctionDeserialize(Deserializer &deserializer, BoundScalarFunction &function) { auto key_type = function.GetArguments().empty() ? LogicalType::ANY : function.GetArguments()[0]; if (function.GetName() == BloomFilterScalarFun::NAME) { - return make_uniq(nullptr, false, string(), key_type, 0.0f, idx_t(0)); + return make_uniq(shared_ptr(), false, string(), key_type, 0.0f, + idx_t(0)); } if (function.GetName() == PrefixRangeScalarFun::NAME) { return make_uniq(nullptr, true, string(), key_type, 0.0f, idx_t(0)); diff --git a/src/duckdb/src/planner/planner.cpp b/src/duckdb/src/planner/planner.cpp index 1b5881abb..1ff2280ba 100644 --- a/src/duckdb/src/planner/planner.cpp +++ b/src/duckdb/src/planner/planner.cpp @@ -240,9 +240,12 @@ void Planner::CreatePlan(SQLStatement &statement) { auto max_tree_depth = Settings::Get(context); CheckTreeDepth(*plan, max_tree_depth); - RewriteTriggersToDependent(*this->binder, *this->plan); - RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); - this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); + // A LogicalPlanStatement carries an already planned (and possibly optimized) plan + if (statement.type != StatementType::LOGICAL_PLAN_STATEMENT) { + RewriteTriggersToDependent(*this->binder, *this->plan); + RecursiveDependentJoinPlanner::Plan(*this->binder, this->plan); + this->plan = FlattenDependentJoins::DecorrelateIndependent(*this->binder, std::move(this->plan)); + } D_ASSERT(!ContainsDependentJoin(*this->plan)); D_ASSERT(VerifyPlannedExpressions(*this->plan)); D_ASSERT(VerifyCanonicalComparisonJoins(*this->plan)); diff --git a/src/duckdb/src/planner/table_filter_state.cpp b/src/duckdb/src/planner/table_filter_state.cpp index 932ea641b..33bfb58bb 100644 --- a/src/duckdb/src/planner/table_filter_state.cpp +++ b/src/duckdb/src/planner/table_filter_state.cpp @@ -348,7 +348,7 @@ class BloomFilterExecutor final : public ExpressionFilterExecutor { } } - optional_ptr filter; + shared_ptr filter; bool filters_null_values; Vector hashes; SelectionVector bloom_sel; From b78125af63248f058cf2337a9790542aa6d1ee19 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Thu, 15 Oct 2026 22:08:38 +0000 Subject: [PATCH 05/13] Update vendored DuckDB sources to 57d34370c80 --- .../aggregate/algebraic/avg.cpp | 5 +- .../aggregate/distributive/bitstring_agg.cpp | 11 +- .../aggregate/distributive/kurtosis.cpp | 95 +++-- .../aggregate/distributive/skew.cpp | 78 ++-- .../aggregate/distributive/sum.cpp | 36 +- .../holistic/approximate_quantile.cpp | 61 +++- .../aggregate/algebraic/covar.hpp | 7 +- .../aggregate/algebraic/stddev.hpp | 7 +- .../aggregate/regression/regr_count.hpp | 8 +- .../scalar/array/array_value.cpp | 2 + .../core_functions/scalar/date/date_part.cpp | 14 +- .../core_functions/scalar/date/date_trunc.cpp | 12 +- .../scalar/generic/current_setting.cpp | 2 + .../scalar/generic/type_functions.cpp | 6 +- .../core_functions/scalar/list/list_sort.cpp | 21 +- .../core_functions/scalar/map/switch.cpp | 2 + .../core_functions/scalar/math/numeric.cpp | 7 +- .../scalar/string/parse_path.cpp | 186 +++++----- .../core_functions/scalar/string/replace.cpp | 13 +- .../icu/collation/collation_collator.cpp | 6 + .../icu/collation/collation_normalizer.cpp | 2 +- .../collation/include/collation_collator.hpp | 8 + src/duckdb/extension/icu/icu-datefunc.cpp | 11 + src/duckdb/extension/icu/icu-datepart.cpp | 10 +- src/duckdb/extension/icu/icu-datesub.cpp | 14 + src/duckdb/extension/icu/icu-datetrunc.cpp | 15 + src/duckdb/extension/icu/icu-makedate.cpp | 6 +- src/duckdb/extension/icu/icu-strptime.cpp | 41 ++- src/duckdb/extension/icu/icu-timebucket.cpp | 33 +- src/duckdb/extension/icu/icu-timezone.cpp | 4 + .../extension/icu/include/icu-datefunc.hpp | 4 + .../json/json_functions/json_create.cpp | 19 +- .../json/json_functions/json_transform.cpp | 39 +- src/duckdb/extension/json/json_reader.cpp | 10 +- .../extension/parquet/column_reader.cpp | 58 ++- .../delta_length_byte_array_decoder.cpp | 45 +-- .../parquet/include/parquet_dbp_decoder.hpp | 5 +- .../parquet/include/parquet_reader.hpp | 3 + .../extension/parquet/parquet_extension.cpp | 3 + .../extension/parquet/parquet_metadata.cpp | 30 +- .../extension/parquet/parquet_reader.cpp | 113 ++++-- .../extension/parquet/parquet_shredding.cpp | 5 + .../parquet/read_single_parquet_file.cpp | 21 ++ .../reader/row_number_column_reader.cpp | 9 + ...nerated_extension_loader_package_build.cpp | 8 +- .../catalog_entry/duck_table_entry.cpp | 36 +- .../catalog_entry/sequence_catalog_entry.cpp | 17 + .../common/allocator/allocator_jemalloc.cpp | 17 + .../common/allocator/allocator_standard.cpp | 4 + src/duckdb/src/common/bind_helpers.cpp | 7 +- src/duckdb/src/common/enum_util.cpp | 33 +- src/duckdb/src/common/exception.cpp | 1 - src/duckdb/src/common/file_buffer.cpp | 6 + src/duckdb/src/common/gzip_file_system.cpp | 8 +- src/duckdb/src/common/hive_partitioning.cpp | 4 +- src/duckdb/src/common/local_file_system.cpp | 7 +- .../multi_file/multi_file_column_mapper.cpp | 54 ++- .../src/common/multi_file/multi_file_list.cpp | 3 +- src/duckdb/src/common/opener_file_system.cpp | 3 +- .../src/common/operator/cast_operators.cpp | 37 +- src/duckdb/src/common/sort/sort.cpp | 6 +- src/duckdb/src/common/sort/sorted_run.cpp | 18 +- .../src/common/sort/sorted_run_merger.cpp | 7 +- src/duckdb/src/common/types/bignum.cpp | 32 +- src/duckdb/src/common/types/geometry.cpp | 3 + src/duckdb/src/common/types/hugeint.cpp | 259 +------------ src/duckdb/src/common/types/interval.cpp | 4 +- src/duckdb/src/common/types/time.cpp | 29 +- .../variant/parquet_variant_iterator.cpp | 7 + src/duckdb/src/common/types/vector_buffer.cpp | 17 +- .../src/common/vector/constant_vector.cpp | 58 ++- src/duckdb/src/common/vector/list_vector.cpp | 2 +- .../expression_executor/execute_function.cpp | 43 ++- .../src/execution/index/fixed_size_buffer.cpp | 6 +- .../operator/aggregate/physical_window.cpp | 19 +- .../scanner/string_value_scanner.cpp | 36 +- .../table_function/read_single_csv_file.cpp | 3 + .../csv_scanner/util/csv_reader_options.cpp | 21 ++ .../csv_scanner/util/csv_validator.cpp | 2 +- .../operator/join/physical_asof_join.cpp | 16 +- .../operator/join/physical_hash_join.cpp | 21 +- .../operator/order/physical_top_n.cpp | 19 +- .../persistent/physical_batch_insert.cpp | 4 +- .../persistent/physical_copy_to_file.cpp | 19 +- .../operator/persistent/physical_insert.cpp | 5 +- .../projection/physical_projection.cpp | 10 + .../execution/operator/set/physical_union.cpp | 13 +- .../physical_plan/plan_asof_join.cpp | 6 + .../physical_plan/plan_comparison_join.cpp | 4 +- .../physical_plan/plan_set_operation.cpp | 20 +- .../function/aggregate/distributive/count.cpp | 1 + .../aggregate/sorted_aggregate_function.cpp | 47 ++- src/duckdb/src/function/cast/array_casts.cpp | 8 +- src/duckdb/src/function/cast/bignum_casts.cpp | 14 +- .../src/function/cast/cast_statistics.cpp | 4 + src/duckdb/src/function/cast/string_cast.cpp | 30 +- src/duckdb/src/function/cast/union_casts.cpp | 5 + .../function/cast/variant/from_variant.cpp | 19 +- src/duckdb/src/function/function_binder.cpp | 15 + .../src/function/pragma/pragma_functions.cpp | 34 +- .../function/scalar/generic/getvariable.cpp | 2 + .../src/function/scalar/map/map_contains.cpp | 41 +++ .../function/scalar/operator/arithmetic.cpp | 29 +- .../src/function/scalar/sequence/nextval.cpp | 11 + .../src/function/scalar/string/contains.cpp | 69 +++- .../src/function/scalar/string/length.cpp | 23 +- .../src/function/scalar/string/regexp.cpp | 9 + .../scalar/system/aggregate_export.cpp | 20 +- .../src/function/table/arrow_conversion.cpp | 235 ++++++------ src/duckdb/src/function/table/checkpoint.cpp | 4 +- src/duckdb/src/function/table/range.cpp | 3 +- .../table/system/create_external_resource.cpp | 5 + .../table/system/enable_profiling.cpp | 17 + .../table/system/external_resources.cpp | 3 + .../function/table/system/logging_utils.cpp | 10 + .../function/table/version/pragma_version.cpp | 6 +- .../function/window/window_value_function.cpp | 30 +- .../catalog_entry/sequence_catalog_entry.hpp | 2 + .../src/include/duckdb/common/allocator.hpp | 2 + .../src/include/duckdb/common/enum_util.hpp | 8 + .../common/exception/http_exception.hpp | 5 + .../duckdb/common/hive_partitioning.hpp | 4 +- .../src/include/duckdb/common/hugeint.hpp | 343 +++++++++++++++++- .../src/include/duckdb/common/mutex.hpp | 70 ++++ .../operator/decimal_cast_operators.hpp | 15 +- .../common/operator/integer_cast_operator.hpp | 23 +- .../duckdb/common/sorting/buffered_merge.hpp | 192 ++++++++++ .../duckdb/common/sorting/sorted_run.hpp | 2 + .../duckdb/common/thread_annotation/mutex.hpp | 16 + .../common/types/arrow_string_view_type.hpp | 4 +- .../src/include/duckdb/common/types/time.hpp | 5 +- .../execution/expression_executor_state.hpp | 2 + .../persistent/physical_batch_insert.hpp | 4 +- .../operator/persistent/physical_insert.hpp | 4 +- .../projection/physical_projection.hpp | 1 + .../duckdb/execution/physical_operator.hpp | 5 + .../function/cast/variant/json_to_variant.hpp | 9 +- .../cast/variant/primitive_to_variant.hpp | 24 +- .../function/cast/variant/to_variant_fwd.hpp | 5 +- .../duckdb/function/compression_function.hpp | 6 +- .../src/include/duckdb/function/function.hpp | 6 +- .../duckdb/function/scalar/generic_common.hpp | 3 + .../include/duckdb/main/client_context.hpp | 2 + src/duckdb/src/include/duckdb/main/config.hpp | 52 ++- .../include/duckdb/main/extension_entries.hpp | 8 +- .../include/duckdb/main/extension_manager.hpp | 10 +- .../duckdb/main/http/http_retry_budget.hpp | 162 ++++++++- .../include/duckdb/main/http/http_util.hpp | 23 +- .../join_order/join_order_optimizer.hpp | 6 + .../relation_statistics.hpp | 15 + .../optimizer/remove_unused_columns.hpp | 2 + .../duckdb/parallel/scan_read_ahead.hpp | 24 ++ .../parser/peg/ast/expression_chain.hpp | 5 +- .../duckdb/parser/peg/compiled_grammar.hpp | 13 +- .../duckdb/parser/peg/inlined_grammar.hpp | 18 +- .../peg/matcher/number_literal_matcher.hpp | 8 + .../peg/transformer/peg_transformer.hpp | 39 +- .../duckdb/planner/expression_binder.hpp | 2 + .../planner/filter/table_filter_functions.hpp | 5 +- .../duckdb/planner/operator/logical_join.hpp | 4 + .../duckdb/planner/sql_export_helpers.hpp | 7 + .../duckdb/storage/block_allocator.hpp | 12 +- .../compression/dict_fsst/decompression.hpp | 2 +- .../storage/compression/empty_validity.hpp | 2 +- .../duckdb/storage/string_uncompressed.hpp | 2 +- .../duckdb/storage/table/column_data.hpp | 2 +- .../duckdb/storage/table/column_segment.hpp | 3 +- src/duckdb/src/main/capi/v1/arrow-c.cpp | 11 +- src/duckdb/src/main/client_context.cpp | 11 +- .../src/main/client_context_execution.cpp | 1 + src/duckdb/src/main/config.cpp | 23 +- .../src/main/extension/extension_helper.cpp | 3 + src/duckdb/src/main/extension_manager.cpp | 14 +- .../src/main/http/http_retry_budget.cpp | 333 +++++++++++++++-- src/duckdb/src/main/http/http_util.cpp | 177 +++++++-- src/duckdb/src/main/parser_context.cpp | 44 ++- src/duckdb/src/main/secret/secret_manager.cpp | 4 +- .../src/main/settings/custom_settings.cpp | 13 +- .../optimizer/common_subplan_optimizer.cpp | 86 +++-- src/duckdb/src/optimizer/filter_combiner.cpp | 56 +-- src/duckdb/src/optimizer/filter_pushdown.cpp | 46 +-- .../src/optimizer/grouping_sets_optimizer.cpp | 7 + .../join_order/join_order_optimizer.cpp | 16 +- .../optimizer/join_order/relation_manager.cpp | 12 +- .../multi_stage_aggregate_rewriter.cpp | 93 ++++- src/duckdb/src/optimizer/optimizer.cpp | 6 +- .../pushdown/pushdown_cross_product.cpp | 54 +++ .../pushdown/pushdown_inner_join.cpp | 16 +- .../pushdown/pushdown_semi_anti_join.cpp | 4 +- .../relation_statistics.cpp | 18 + .../relation_statistics_helper.cpp | 27 +- .../relation_statistics_operator.cpp | 21 +- .../src/optimizer/remove_duplicate_groups.cpp | 6 +- .../src/optimizer/remove_unused_columns.cpp | 30 +- .../rule/comparison_simplification.cpp | 6 + .../rule/date_trunc_simplification.cpp | 28 ++ .../statistics/operator/propagate_join.cpp | 59 +-- .../src/optimizer/topn_window_elimination.cpp | 22 +- src/duckdb/src/parallel/pipeline_executor.cpp | 4 +- src/duckdb/src/parallel/scan_read_ahead.cpp | 108 +++++- src/duckdb/src/parser/peg/matcher_factory.cpp | 1 + .../parser/peg/tokenizer/base_tokenizer.cpp | 18 +- .../transformer/peg_transformer_factory.cpp | 2 - .../parser/peg/transformer/transform_copy.cpp | 4 +- .../peg/transformer/transform_create_type.cpp | 7 +- .../peg/transformer/transform_describe.cpp | 3 + .../parser/peg/transformer/transform_drop.cpp | 7 +- .../peg/transformer/transform_expression.cpp | 125 ++++--- .../transform_generated_trampoline.cpp | 191 ++++++++-- .../transform_generic_copy_option.cpp | 3 +- .../peg/transformer/transform_select.cpp | 24 +- src/duckdb/src/planner/bind_context.cpp | 17 +- src/duckdb/src/planner/binder.cpp | 5 + .../expression/bind_aggregate_expression.cpp | 15 + .../expression/bind_columnref_expression.cpp | 3 + .../expression/bind_function_expression.cpp | 40 +- .../expression/bind_macro_expression.cpp | 3 + .../expression/bind_window_expression.cpp | 4 +- .../binder/query_node/bind_cte_node.cpp | 3 + .../query_node/bind_recursive_cte_node.cpp | 3 + .../binder/query_node/bind_select_node.cpp | 7 +- .../query_node/bind_table_macro_node.cpp | 3 + .../planner/binder/statement/bind_attach.cpp | 4 +- .../planner/binder/statement/bind_connect.cpp | 2 +- .../planner/binder/statement/bind_copy.cpp | 2 +- .../binder/statement/bind_copy_database.cpp | 11 +- .../binder/statement/bind_create_table.cpp | 5 + .../planner/binder/statement/bind_export.cpp | 7 +- .../statement/bind_external_resource.cpp | 2 +- .../binder/statement/bind_merge_into.cpp | 48 ++- .../binder/tableref/bind_basetableref.cpp | 6 +- .../planner/binder/tableref/bind_pivot.cpp | 43 ++- .../planner/binder/tableref/bind_showref.cpp | 3 + .../binder/tableref/bind_table_function.cpp | 9 + .../expression/bound_aggregate_expression.cpp | 6 + .../expression/bound_cast_expression.cpp | 68 +++- .../expression_binder/check_binder.cpp | 5 +- .../expression_binder/index_binder.cpp | 23 +- .../src/planner/filter/dynamic_filter.cpp | 4 +- .../filter/table_filter_dynamic_function.cpp | 5 +- .../src/planner/operator/logical_join.cpp | 16 + .../bound_expression_sql_exporter.cpp | 14 +- .../sql_export/sql_export_composition.cpp | 3 + .../sql_export/sql_export_functions.cpp | 10 +- .../sql_export/sql_export_relations.cpp | 11 +- .../planner/sql_export/sql_export_window.cpp | 28 +- .../subquery/delim_join_cte_rewriter.cpp | 5 +- .../subquery/flatten_dependent_join.cpp | 14 +- src/duckdb/src/storage/block_allocator.cpp | 29 +- src/duckdb/src/storage/buffer/buffer_pool.cpp | 4 +- src/duckdb/src/storage/checkpoint_manager.cpp | 7 +- .../src/storage/compression/dict_fsst.cpp | 14 +- .../compression/dict_fsst/decompression.cpp | 37 +- src/duckdb/src/storage/compression/fsst.cpp | 6 +- .../storage/compression/numeric_constant.cpp | 17 +- src/duckdb/src/storage/compression/rle.cpp | 22 +- .../compression/string_uncompressed.cpp | 5 +- .../compression/validity_uncompressed.cpp | 19 +- src/duckdb/src/storage/data_table.cpp | 2 + .../caching_file_system.cpp | 6 + .../src/storage/statistics/numeric_stats.cpp | 27 +- .../src/storage/statistics/variant_stats.cpp | 4 +- src/duckdb/src/storage/table/column_data.cpp | 77 +++- .../src/storage/table/column_segment.cpp | 9 +- .../storage/table/standard_column_data.cpp | 9 +- src/duckdb/src/storage/wal_replay.cpp | 5 + .../src/transaction/meta_transaction.cpp | 7 +- src/duckdb/third_party/httplib/httplib.hpp | 16 +- src/duckdb/third_party/ska_sort/ska_sort.hpp | 64 +++- src/duckdb/third_party/vergesort/vergesort.h | 30 +- 270 files changed, 4813 insertions(+), 1727 deletions(-) create mode 100644 src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp diff --git a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp index bbe22c410..2b7768041 100644 --- a/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/algebraic/avg.cpp @@ -239,8 +239,9 @@ struct IntervalAverageOperation : public BaseSumOperation(shifted_days / hugeint_t(count)); target.micros += micros_remainder; } } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp index 3f1b8affc..343915caa 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/bitstring_agg.cpp @@ -123,15 +123,12 @@ struct BitStringAggOperation { if (min > max) { throw InvalidInputException("Invalid explicit bitstring range: Minimum (%d) > maximum (%d)", min, max); } - INPUT_TYPE result; - if (!TrySubtractOperator::Operation(max, min, result)) { + // the difference of two (at most 64-bit) values does not always fit in their own type, but it fits in a hugeint + const auto range = Hugeint::Convert(max) - Hugeint::Convert(min); + if (range >= Hugeint::Convert(NumericLimits::Maximum())) { return NumericLimits::Maximum(); } - auto val = NumericCast(result); - if (val == NumericLimits::Maximum()) { - return val; - } - return val + 1; + return Hugeint::Cast(range) + 1; } template diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp index c7b4853a5..a5e0a5a39 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/kurtosis.cpp @@ -1,3 +1,4 @@ +#include "duckdb/common/operator/add.hpp" #include "core_functions/aggregate/distributive_functions.hpp" #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" @@ -8,15 +9,17 @@ namespace duckdb { namespace { +//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's +//! method used by var_samp: M2..M4 are exactly zero for constant input instead of rounding noise of power sums struct KurtosisState { - static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub", "sum_four"}; + static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3", "m4"}; using STATE_TYPE = StructStateType; idx_t n; - double sum; - double sum_sqr; - double sum_cub; - double sum_four; + double mean; // M1 + double m2; // M2 = sum((x - mean)^2) + double m3; // M3 = sum((x - mean)^3) + double m4; // M4 = sum((x - mean)^4) }; struct KurtosisFlagBiasCorrection {}; @@ -28,18 +31,29 @@ struct KurtosisOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - for (idx_t i = 0; i < count; i++) { - Operation(state, input, unary_input); - } + // a block of identical values has mean "input" and zero central moments + STATE constant; + constant.n = count; + constant.mean = static_cast(input); + constant.m2 = 0; + constant.m3 = 0; + constant.m4 = 0; + Combine(constant, state, unary_input.input); } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { + const double n1 = static_cast(state.n); state.n++; - state.sum += input; - state.sum_sqr += pow(input, 2); - state.sum_cub += pow(input, 3); - state.sum_four += pow(input, 4); + const double n = static_cast(state.n); + const double delta = static_cast(input) - state.mean; + const double delta_n = delta / n; + const double delta_n2 = delta_n * delta_n; + const double term1 = delta * delta_n * n1; + state.mean += delta_n; + state.m4 += term1 * delta_n2 * (n * n - 3 * n + 3) + 6 * delta_n2 * state.m2 - 4 * delta_n * state.m3; + state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; + state.m2 += term1; } template @@ -47,45 +61,52 @@ struct KurtosisOperation { if (source.n == 0) { return; } - target.n += source.n; - target.sum += source.sum; - target.sum_sqr += source.sum_sqr; - target.sum_cub += source.sum_cub; - target.sum_four += source.sum_four; + if (target.n == 0) { + target = source; + return; + } + idx_t count; + if (!TryAddOperator::Operation(target.n, source.n, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } + const double na = static_cast(target.n); + const double nb = static_cast(source.n); + const double n = static_cast(count); + const double delta = source.mean - target.mean; + const double delta_n = delta / n; + const double delta_n2 = delta_n * delta_n; + target.m4 += source.m4 + delta * delta_n * delta_n2 * na * nb * (na * na - na * nb + nb * nb) + + 6 * delta_n2 * (na * na * source.m2 + nb * nb * target.m2) + + 4 * delta_n * (na * source.m3 - nb * target.m3); + target.m3 += + source.m3 + delta * delta_n2 * na * nb * (na - nb) + 3 * delta_n * (na * source.m2 - nb * target.m2); + target.m2 += source.m2 + delta * delta_n * na * nb; + target.mean += delta_n * nb; + target.n = count; } template static void Finalize(STATE &state, TARGET_TYPE &target, AggregateFinalizeData &finalize_data) { - auto n = (double)state.n; - if (n <= 1) { + if (state.n <= 1) { finalize_data.ReturnNull(); return; } - if (std::is_same::value && n <= 3) { + if (std::is_same::value && state.n <= 3) { finalize_data.ReturnNull(); return; } - double temp = 1 / n; - //! This is necessary due to linux 32 bits - long double temp_aux = 1 / n; - if (state.sum_sqr - state.sum * state.sum * temp == 0 || - state.sum_sqr - state.sum * state.sum * temp_aux == 0) { - finalize_data.ReturnNull(); - return; - } - double m4 = - temp * (state.sum_four - 4 * state.sum_cub * state.sum * temp + - 6 * state.sum_sqr * state.sum * state.sum * temp * temp - 3 * pow(state.sum, 4) * pow(temp, 3)); - - double m2 = temp * (state.sum_sqr - state.sum * state.sum * temp); - if (m2 <= 0) { // m2 shouldn't be below 0 but floating points are weird + // kurtosis is undefined for zero variance (M2 is exactly 0 for constant input) + if (state.m2 <= 0) { finalize_data.ReturnNull(); return; } + const double n = static_cast(state.n); + // m4 / m2^2 with the population moments m4 = M4 / n and m2 = M2 / n + const double m4_over_m2_sqr = n * (state.m4 / state.m2) / state.m2; if (std::is_same::value) { - target = m4 / (m2 * m2) - 3; + target = m4_over_m2_sqr - 3; } else { - target = (n - 1) * ((n + 1) * m4 / (m2 * m2) - 3 * (n - 1)) / ((n - 2) * (n - 3)); + target = (n - 1) * ((n + 1) * m4_over_m2_sqr - 3 * (n - 1)) / ((n - 2) * (n - 3)); } if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("Kurtosis is out of range!"); diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp index 8443c7e55..f354c98bf 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/skew.cpp @@ -3,37 +3,47 @@ #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/common/algorithm.hpp" -#include namespace duckdb { namespace { +//! Online central moments (Terriberry / Pébay, DOI: 10.2172/1028931), the higher-moment extension of Welford's +//! method used by var_samp: M2 and M3 are exactly zero for constant input instead of rounding noise of power sums struct SkewState { - static constexpr const char *STATE_NAMES[] = {"n", "sum", "sum_sqr", "sum_cub"}; + static constexpr const char *STATE_NAMES[] = {"n", "mean", "m2", "m3"}; using STATE_TYPE = StructStateType; idx_t n; - double sum; - double sum_sqr; - double sum_cub; + double mean; // M1 + double m2; // M2 = sum((x - mean)^2) + double m3; // M3 = sum((x - mean)^3) }; struct SkewnessOperation { template static void ConstantOperation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input, idx_t count) { - for (idx_t i = 0; i < count; i++) { - Operation(state, input, unary_input); - } + // a block of identical values has mean "input" and zero central moments + STATE constant; + constant.n = count; + constant.mean = static_cast(input); + constant.m2 = 0; + constant.m3 = 0; + Combine(constant, state, unary_input.input); } template - static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &unary_input) { + static void Operation(STATE &state, const INPUT_TYPE &input, AggregateUnaryInput &) { + const double n1 = static_cast(state.n); state.n++; - state.sum += input; - state.sum_sqr += pow(input, 2); - state.sum_cub += pow(input, 3); + const double n = static_cast(state.n); + const double delta = static_cast(input) - state.mean; + const double delta_n = delta / n; + const double term1 = delta * delta_n * n1; + state.mean += delta_n; + state.m3 += term1 * delta_n * (n - 2) - 3 * delta_n * state.m2; + state.m2 += term1; } template @@ -41,11 +51,22 @@ struct SkewnessOperation { if (source.n == 0) { return; } - - target.n += source.n; - target.sum += source.sum; - target.sum_sqr += source.sum_sqr; - target.sum_cub += source.sum_cub; + if (target.n == 0) { + target = source; + return; + } + const auto count = target.n + source.n; + D_ASSERT(count >= target.n); // This is a check that we are not overflowing + const double na = static_cast(target.n); + const double nb = static_cast(source.n); + const double n = static_cast(count); + const double delta = source.mean - target.mean; + const double delta_n = delta / n; + target.m3 += source.m3 + delta * delta_n * delta_n * na * nb * (na - nb) + + 3 * delta_n * (na * source.m2 - nb * target.m2); + target.m2 += source.m2 + delta * delta_n * na * nb; + target.mean += delta_n * nb; + target.n = count; } template @@ -54,26 +75,15 @@ struct SkewnessOperation { finalize_data.ReturnNull(); return; } - double n = state.n; - double temp = 1 / n; - double raw_m2 = state.sum_sqr - state.sum * state.sum * temp; - // Only treat finite second-moment noise as zero; overflow should still surface as out-of-range. - if (Value::DoubleIsFinite(raw_m2) && Value::DoubleIsFinite(state.sum_sqr) && - // Scale the tolerance by the accumulated squared magnitude instead of using a fixed epsilon. - std::abs(raw_m2) <= std::numeric_limits::epsilon() * std::max(1.0, std::abs(state.sum_sqr))) { - finalize_data.ReturnNull(); - return; - } - double variance = temp * raw_m2; - if (variance <= 0) { + // skewness is undefined for zero variance (M2 is exactly 0 for constant input) + if (state.m2 <= 0) { finalize_data.ReturnNull(); return; } - auto p = std::pow(variance, 3); - double div = std::sqrt(p); - double temp1 = std::sqrt(n * (n - 1)) / (n - 2); - target = temp1 * temp * - (state.sum_cub - 3 * state.sum_sqr * state.sum * temp + 2 * pow(state.sum, 3) * temp * temp) / div; + const double n = static_cast(state.n); + // g1 = (M3 / n) / (M2 / n)^1.5 = sqrt(n) * M3 / M2^1.5, with the bias correction sqrt(n (n - 1)) / (n - 2) + const double g1 = std::sqrt(n) * (state.m3 / state.m2) / std::sqrt(state.m2); + target = std::sqrt(n * (n - 1)) / (n - 2) * g1; if (!Value::DoubleIsFinite(target)) { throw OutOfRangeException("SKEW is out of range!"); } diff --git a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp index 55a67fb58..bcb7594c2 100644 --- a/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp +++ b/src/duckdb/extension/core_functions/aggregate/distributive/sum.cpp @@ -247,8 +247,17 @@ struct DecimalSumOperation : public OP { } }; -unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { - throw BinderException("sum_no_overflow is for internal use only!"); +//! Binds the regular sum that sum_no_overflow is a specialized implementation of +pair> BindLogicalSum(ClientContext &context, + vector> &arguments) { + vector argument_types; + for (auto &argument : arguments) { + argument_types.push_back(argument->GetReturnType()); + } + auto &entry = Catalog::GetEntry( + context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); + FunctionBinder binder(context); + return binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); } void SumNoOverflowSerialize(Serializer &serializer, const optional_ptr bind_data, @@ -261,15 +270,10 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo auto &return_type = deserializer.Get(); auto &children = deserializer.Get(); vector> arguments; - vector argument_types; for (auto &child : children) { arguments.push_back(child.get().Copy()); - argument_types.push_back(child.get().GetReturnType()); } - auto &entry = Catalog::GetEntry( - context, QualifiedName(Identifier::SystemCatalog(), Identifier::DefaultSchema(), Identifier("sum"))); - FunctionBinder binder(context); - auto logical = binder.ResolveFunction(entry.functions.GetFunctionByArguments(context, argument_types), arguments); + auto logical = BindLogicalSum(context, arguments); if (logical.second || (!return_type.IsAggregateState() && logical.first.GetReturnType() != return_type)) { throw SerializationException("Cannot reconstruct the logical sum signature"); } @@ -279,6 +283,8 @@ unique_ptr SumNoOverflowDeserialize(Deserializer &deserializer, Bo return nullptr; } +unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input); + AggregateFunction GetSumAggregateNoOverflow(PhysicalType type) { switch (type) { case PhysicalType::INT32: { @@ -318,6 +324,20 @@ AggregateFunction GetSumAggregateNoOverflowDecimal() { return aggr; } +//! Binding sum_no_overflow by name (e.g. when re-binding an exported aggregate state) delegates to the regular sum, +//! with the no-overflow implementation swapped in as statistics propagation does +unique_ptr SumNoOverflowBind(BindAggregateFunctionInput &input) { + auto &function = input.GetBoundFunction(); + auto &arguments = input.GetArguments(); + const auto parameter_type = function.GetArguments()[0]; + auto logical = BindLogicalSum(input.GetClientContext(), arguments); + if (parameter_type.id() != LogicalTypeId::DECIMAL) { + logical.first.ReplaceImplementation(GetSumAggregateNoOverflow(parameter_type.InternalType())); + } + function = std::move(logical.first); + return std::move(logical.second); +} + unique_ptr SumPropagateStats(ClientContext &context, BoundAggregateExpression &expr, AggregateStatisticsInput &input) { if (!input.node_stats || !input.node_stats->has_max_cardinality) { diff --git a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp index 008c81d94..b545e937a 100644 --- a/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp +++ b/src/duckdb/extension/core_functions/aggregate/holistic/approximate_quantile.cpp @@ -1,3 +1,4 @@ +#include "duckdb/common/operator/add.hpp" #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/common/vector/list_vector.hpp" @@ -38,6 +39,22 @@ struct ApproxQuantileCoding { } return false; } + + template + static void Decode(const SAVE_TYPE &source, TARGET_TYPE &target, const LogicalType &type) { + Decode(source, target); + if (type.id() != LogicalTypeId::DECIMAL) { + return; + } + // The unscaled decimal range can be narrower than its physical integer range. + const auto limit = Hugeint::POWERS_OF_TEN[DecimalType::GetWidth(type)] - 1; + const auto value = Cast::Operation(target); + if (value > limit) { + target = Cast::Operation(limit); + } else if (value < -limit) { + target = Cast::Operation(-limit); + } + } }; template <> @@ -64,6 +81,26 @@ bool ApproxQuantileCoding::Decode(const double &source, dtime_tz_t &target) { return decoded; } +template <> +double ApproxQuantileCoding::Encode(const timestamp_t &input) { + return Encode(input.value); +} + +template <> +bool ApproxQuantileCoding::Decode(const double &source, timestamp_t &target) { + int64_t value; + const auto decoded = Decode(source, value); + // the infinities are the largest and smallest timestamps - do not return anything beyond them + if (value >= timestamp_t::infinity().value) { + target = timestamp_t::infinity(); + } else if (value <= timestamp_t::ninfinity().value) { + target = timestamp_t::ninfinity(); + } else { + target = timestamp_t(value); + } + return decoded; +} + struct ApproximateQuantileBindData : public FunctionData { ApproximateQuantileBindData() { } @@ -137,8 +174,10 @@ struct ApproxQuantileOperation { auto &allocator = aggr_input_data.allocator; target.h = allocator.Make(allocator, 100); } + if (!TryAddOperator::Operation(target.pos, source.pos, target.pos)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } target.h->merge(source.h); - target.pos += source.pos; } static bool IgnoreNull() { @@ -159,7 +198,7 @@ struct ApproxQuantileScalarOperation : public ApproxQuantileOperation { auto &bind_data = finalize_data.input.bind_data->template Cast(); D_ASSERT(bind_data.quantiles.size() == 1); const auto source = state.h->quantile(bind_data.quantiles[0]); - ApproxQuantileCoding::Decode(source, target); + ApproxQuantileCoding::Decode(source, target, finalize_data.result.GetType()); } }; @@ -309,6 +348,10 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ return AggregateFunction::UnaryAggregate(type, type); } + if (type.id() == LogicalTypeId::TIMESTAMP || type.id() == LogicalTypeId::TIMESTAMP_TZ) { + return AggregateFunction::UnaryAggregate(type, type); + } switch (type.InternalType()) { case PhysicalType::INT8: return AggregateFunction::UnaryAggregate( @@ -339,15 +382,12 @@ AggregateFunction GetApproximateQuantileAggregateFunction(const LogicalType &typ AggregateFunction GetApproximateQuantileDecimalAggregateFunction(const LogicalType &type) { switch (type.InternalType()) { case PhysicalType::INT8: - return GetApproximateQuantileAggregateFunction(LogicalType::TINYINT); case PhysicalType::INT16: - return GetApproximateQuantileAggregateFunction(LogicalType::SMALLINT); case PhysicalType::INT32: - return GetApproximateQuantileAggregateFunction(LogicalType::INTEGER); case PhysicalType::INT64: - return GetApproximateQuantileAggregateFunction(LogicalType::BIGINT); case PhysicalType::INT128: - return GetApproximateQuantileAggregateFunction(LogicalType::HUGEINT); + // keep the DECIMAL type as the input and return type so that the width and scale survive the bind + return GetApproximateQuantileAggregateFunction(type); default: throw InternalException("Unimplemented quantile decimal aggregate"); } @@ -461,7 +501,7 @@ struct ApproxQuantileListOperation : public ApproxQuantileOperation { const auto &quantile = bind_data.quantiles[q]; const auto &source = state.h->quantile(quantile); auto &target = rdata[ridx + q]; - ApproxQuantileCoding::Decode(source, target); + ApproxQuantileCoding::Decode(source, target, result.GetType()); } ListVector::SetListSize(finalize_data.result, entry.offset + entry.length); @@ -498,10 +538,11 @@ AggregateFunction GetApproxQuantileListAggregateFunction(const LogicalType &type case LogicalTypeId::DATE: return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::BIGINT: - case LogicalTypeId::TIMESTAMP: - case LogicalTypeId::TIMESTAMP_TZ: case LogicalTypeId::TIME: return GetTypedApproxQuantileListAggregateFunction(type); + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + return GetTypedApproxQuantileListAggregateFunction(type); case LogicalTypeId::TIME_TZ: // Not binary comparable return GetTypedApproxQuantileListAggregateFunction(type); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp index 719736066..34dcb6f6d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/covar.hpp @@ -9,6 +9,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" namespace duckdb { @@ -48,8 +49,10 @@ struct CovarOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - const auto count = target.count + source.count; - D_ASSERT(count >= target.count); // This is a check that we are not overflowing + uint64_t count; + if (!TryAddOperator::Operation(target.count, source.count, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } const auto target_count = static_cast(target.count); const auto source_count = static_cast(source.count); const auto total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp index 0150248c1..0068b906d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/algebraic/stddev.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include #include @@ -57,8 +58,10 @@ struct STDDevBaseOperation { if (target.count == 0) { target = source; } else if (source.count > 0) { - const auto count = target.count + source.count; - D_ASSERT(count >= target.count); // This is a check that we are not overflowing + uint64_t count; + if (!TryAddOperator::Operation(target.count, source.count, count)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } const double target_count = static_cast(target.count); const double source_count = static_cast(source.count); const double total_count = static_cast(count); diff --git a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp index ac9836499..dbe33743d 100644 --- a/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp +++ b/src/duckdb/extension/core_functions/include/core_functions/aggregate/regression/regr_count.hpp @@ -9,6 +9,7 @@ #pragma once +#include "duckdb/common/operator/add.hpp" #include "duckdb/function/aggregate_function.hpp" #include "core_functions/aggregate/algebraic/covar.hpp" #include "core_functions/aggregate/algebraic/stddev.hpp" @@ -18,11 +19,16 @@ namespace duckdb { struct RegrCountFunction { template static void Combine(const STATE &source, STATE &target, AggregateInputData &) { - target += source; + if (!TryAddOperator::Operation(target, source, target)) { + throw OutOfRangeException("Overflow in aggregate state combine"); + } } template static void Finalize(STATE &state, T &target, AggregateFinalizeData &finalize_data) { + if (state > NumericLimits::Maximum()) { + throw OutOfRangeException("regr_count result %llu does not fit in the result type", state); + } target = static_cast(state); } static bool IgnoreNull() { diff --git a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp index 91105869d..08e1a0abf 100644 --- a/src/duckdb/extension/core_functions/scalar/array/array_value.cpp +++ b/src/duckdb/extension/core_functions/scalar/array/array_value.cpp @@ -95,6 +95,8 @@ ScalarFunction ArrayValueFun::GetFunction() { fun.SetResolveTypesCallback(ArrayValueResolveTypes); fun.GetSignature().AddArgs("args", LogicalType::ANY); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // casting the arguments to the common child type can fail, e.g. for arrays of different sizes + fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp index 3b8f8ae2e..5663e2957 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_part.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_part.cpp @@ -440,7 +440,19 @@ struct DatePart { template static unique_ptr PropagateStatistics(ClientContext &context, FunctionStatisticsInput &input) { - return PropagateDatePartStatistics(input.child_stats); + auto result = PropagateDatePartStatistics(input.child_stats); + if (!result) { + return nullptr; + } + const auto min_year = NumericStats::GetMin(*result); + const auto max_year = NumericStats::GetMax(*result); + if (min_year > 0) { + return PropagateDatePartStatistics(input.child_stats); + } + // the week is subtracted for years before the common era, so only bound the result by the ISO year + NumericStats::SetMin(*result, Value::BIGINT(min_year * 100 - 53)); + NumericStats::SetMax(*result, Value::BIGINT(max_year * 100 + 53)); + return result; } }; diff --git a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp index e6bd79669..9998eb3d5 100644 --- a/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp +++ b/src/duckdb/extension/core_functions/scalar/date/date_trunc.cpp @@ -494,8 +494,16 @@ unique_ptr DateTruncStatistics(vector &child_sta } // Infinite values are unmodified - auto min_part = DateTrunc::UnaryFunction(min); - auto max_part = DateTrunc::UnaryFunction(max); + TR min_part; + TR max_part; + try { + min_part = DateTrunc::UnaryFunction(min); + max_part = DateTrunc::UnaryFunction(max); + } catch (const ConversionException &) { + // the bounds are not in the range of the result type (e.g. a DATE beyond the TIMESTAMP range) - the rows + // holding them may not even reach the function, so do not propagate statistics + return nullptr; + } auto min_value = Value::CreateValue(min_part); auto max_value = Value::CreateValue(max_part); diff --git a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp index 48b0adbea..c1da6d140 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/current_setting.cpp @@ -59,6 +59,8 @@ ScalarFunction CurrentSettingFun::GetFunction() { auto fun = ScalarFunction({}, LogicalType::ANY, CurrentSettingFunction, CurrentSettingBind); fun.GetSignature().AddParameter("setting_name", LogicalType::VARCHAR); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // the value of the setting can change between queries + fun.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return fun; } diff --git a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp index 685dd73b1..03fd01d40 100644 --- a/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp +++ b/src/duckdb/extension/core_functions/scalar/generic/type_functions.cpp @@ -111,7 +111,11 @@ static unique_ptr BindMakeTypeFunctionExpression(FunctionBindExpress } auto type_name = args.front().second.GetValue(); - auto qualified_name = QualifiedName::Parse(type_name); + QualifiedName qualified_name; + string error; + if (!QualifiedName::TryParse(type_name, qualified_name, error)) { + throw ParserException(error); + } auto unbound_type = LogicalType::UNBOUND(make_uniq(std::move(qualified_name), std::move(type_args))); diff --git a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp index 5e498ea96..a2281bdca 100644 --- a/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp +++ b/src/duckdb/extension/core_functions/scalar/list/list_sort.cpp @@ -459,6 +459,16 @@ static unique_ptr ListSortUnbind(FunctionUnbindInput &input) { return make_uniq(QualifiedName("system", "main", "cast_to_type"), std::move(result)); } +static void ListSortResolveTypes(ResolveScalarFunctionTypesInput &input) { + auto list_type = input.GetArgumentType(0); + if (list_type.id() == LogicalTypeId::ARRAY) { + list_type = LogicalType::LIST(ArrayType::GetChildType(list_type)); + } + if (list_type.id() == LogicalTypeId::LIST) { + input.GetBoundFunction().SetReturnType(list_type); + } +} + static void SetListSortSerialization(ScalarFunction &function) { function.SetUnbindCallback(ListSortUnbind); function.SetSerializeCallback(ListSortBindData::Serialize); @@ -483,8 +493,11 @@ ScalarFunctionSet ListSortFun::GetFunctions() { .AddParameter("sort_order", LogicalType::VARCHAR) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort); + sort.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_order); + sort_order.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_orders); + sort_orders.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_sort; list_sort.AddFunction(sort); @@ -495,17 +508,17 @@ ScalarFunctionSet ListSortFun::GetFunctions() { ScalarFunctionSet ListGradeUpFun::GetFunctions() { // one parameter: list - ScalarFunction sort({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort.GetSignature().AddParameter("list", LogicalType::LIST(LogicalType::ANY)); // two parameters: list, order - ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_order({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort_order.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR); // three parameters: list, order, null order - ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::ANY), ListSortFunction, ListGradeUpBind); + ScalarFunction sort_orders({}, LogicalType::LIST(LogicalType::BIGINT), ListSortFunction, ListGradeUpBind); sort_orders.GetSignature() .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("sort_order", LogicalType::VARCHAR) @@ -533,7 +546,9 @@ ScalarFunctionSet ListReverseSortFun::GetFunctions() { .AddParameter("list", LogicalType::LIST(LogicalType::ANY)) .AddParameter("null_order", LogicalType::VARCHAR); SetListSortSerialization(sort_reverse); + sort_reverse.SetResolveTypesCallback(ListSortResolveTypes); SetListSortSerialization(sort_reverse_null_order); + sort_reverse_null_order.SetResolveTypesCallback(ListSortResolveTypes); ScalarFunctionSet list_reverse_sort; list_reverse_sort.AddFunction(sort_reverse); diff --git a/src/duckdb/extension/core_functions/scalar/map/switch.cpp b/src/duckdb/extension/core_functions/scalar/map/switch.cpp index c060299bd..1eec73243 100644 --- a/src/duckdb/extension/core_functions/scalar/map/switch.cpp +++ b/src/duckdb/extension/core_functions/scalar/map/switch.cpp @@ -122,6 +122,8 @@ ScalarFunctionSet SwitchFun::GetFunctions() { switch_expression.GetSignature().AddParameter(param.first, param.second); } switch_expression.SetBindExpressionCallback(SwitchBindExpression); + // switch is rewritten into a CASE expression, so NULL arguments do not make the result NULL + switch_expression.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); func_set.AddFunction(std::move(switch_expression)); } diff --git a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp index f71b80a0e..a69ceeede 100644 --- a/src/duckdb/extension/core_functions/scalar/math/numeric.cpp +++ b/src/duckdb/extension/core_functions/scalar/math/numeric.cpp @@ -1005,9 +1005,14 @@ struct RoundOperatorPrecision { double rounded_value; if (precision < 0) { double modifier = std::pow(10, -TA(precision)); + if (std::isinf(modifier)) { + // every finite value rounds to zero + return Value::IsFinite(input) ? 0 : input; + } rounded_value = ROUND_POLICY::Nearest(input / modifier) * modifier; if (std::isinf(rounded_value) || std::isnan(rounded_value)) { - return 0; + // the input is too large to be rounded to this precision + return input; } } else { double modifier = std::pow(10, TA(precision)); diff --git a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp index a4c40b066..4abc7d07c 100644 --- a/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/parse_path.cpp @@ -1,5 +1,6 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/vector_iterator.hpp" +#include "duckdb/common/vector/vector_writer.hpp" #include "core_functions/scalar/string_functions.hpp" #include "duckdb/function/scalar/string_common.hpp" #include "duckdb/common/local_file_system.hpp" @@ -105,126 +106,111 @@ static idx_t SplitPath(string_t input, const string &sep, CB &&emit) { return list_idx; } -static void ReadOptionalArgs(DataChunk &args, Vector &sep, Vector &trim, const bool &front_trim) { +//! The optional separator and trim_extension arguments - NULL values use the default options +struct PathOptionArguments { + optional_ptr separator; + optional_ptr trim_extension; +}; + +static PathOptionArguments GetPathOptionArguments(DataChunk &args, const bool front_trim) { + PathOptionArguments result; switch (args.ColumnCount()) { - case 1: { + case 1: // use default values break; - } - case 2: { - UnifiedVectorFormat sec_arg; - args.data[1].ToUnifiedFormat(sec_arg); - if (sec_arg.validity.RowIsValid(0)) { // if not NULL - switch (args.data[1].GetType().id()) { - case LogicalTypeId::VARCHAR: { - sep.Reinterpret(args.data[1]); - break; - } - case LogicalTypeId::BOOLEAN: { // parse_path and parse_driname won't get in here - trim.Reinterpret(args.data[1]); - break; - } - default: - throw InvalidInputException("Invalid argument type"); - } + case 2: + switch (args.data[1].GetType().id()) { + case LogicalTypeId::VARCHAR: + result.separator = args.data[1]; + break; + case LogicalTypeId::BOOLEAN: // parse_path and parse_dirname won't get in here + result.trim_extension = args.data[1]; + break; + default: + throw InvalidInputException("Invalid argument type"); } break; - } - case 3: { - if (!front_trim) { - // set trim_extension - UnifiedVectorFormat sec_arg; - args.data[1].ToUnifiedFormat(sec_arg); - if (sec_arg.validity.RowIsValid(0)) { - trim.Reinterpret(args.data[1]); - } - UnifiedVectorFormat third_arg; - args.data[2].ToUnifiedFormat(third_arg); - if (third_arg.validity.RowIsValid(0)) { - sep.Reinterpret(args.data[2]); - } - } else { + case 3: + if (front_trim) { throw InvalidInputException("Invalid number of arguments"); } + result.trim_extension = args.data[1]; + result.separator = args.data[2]; break; - } default: throw InvalidInputException("Invalid number of arguments"); } + return result; } -template -static void TrimPathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - // set default values - const Vector &path = args.data[0]; - Vector separator(string_t("default"), count_t(args.size())); - Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); - ReadOptionalArgs(args, separator, trim_extension, FRONT_TRIM); +template +static void ExecutePathFunction(DataChunk &args, Vector &result, const bool front_trim, OP &&op) { + const auto count = args.size(); + auto options = GetPathOptionArguments(args, front_trim); + Vector default_separator(string_t("default"), count_t(count)); + Vector default_trim_extension(Value::BOOLEAN(false), count_t(count)); + auto paths = args.data[0].Values(); + auto separators = (options.separator ? *options.separator : default_separator).Values(); + auto trim_extensions = (options.trim_extension ? *options.trim_extension : default_trim_extension).Values(); - TernaryExecutor::Execute( - path, separator, trim_extension, result, [&](string_t &inputs, string_t input_sep, bool trim_extension) { - auto data = inputs.GetData(); - auto input_size = inputs.GetSize(); - auto sep = GetSeparator(input_sep.GetString()); + auto writer = FlatVector::Writer(result, count); + for (idx_t i = 0; i < count; i++) { + auto path = paths[i]; + if (!path.IsValid()) { + writer.WriteNull(); + continue; + } + auto separator = separators[i]; + auto sep = GetSeparator(separator.IsValid() ? separator.GetValue() : string_t("default")); + auto trim_extension = trim_extensions[i]; + writer.WriteValue(op(path.GetValue(), sep, trim_extension.IsValid() && trim_extension.GetValue())); + } +} - // find the beginning idx and the size of the result string - idx_t begin = 0; - idx_t new_size = input_size; - if (FRONT_TRIM) { // left trim - auto pos = Find(data, input_size, sep); - if (pos == 0) { // path starts with separator - pos = 1; - } - new_size = (IsIdxValid(pos, input_size)) ? pos : 0; - } else { // right trim - auto idx_last_sep = FindLast(data, input_size, sep); - if (IsIdxValid(idx_last_sep, input_size)) { - begin = idx_last_sep + 1; - } - if (trim_extension) { - auto idx_extension_sep = FindLast(data, input_size, "."); - if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { - new_size = idx_extension_sep; - } - } - } - // copy the trimmed string - D_ASSERT(begin <= new_size); - auto target = StringVector::EmptyString(result, new_size - begin); - auto output = target.GetDataWriteable(); - memcpy(output, data + begin, new_size - begin); +template +static void TrimPathFunction(DataChunk &args, ExpressionState &state, Vector &result) { + ExecutePathFunction(args, result, FRONT_TRIM, [&](const string_t &input, const string &sep, bool trim_extension) { + auto data = input.GetData(); + auto input_size = input.GetSize(); - target.Finalize(); - return target; - }); + // find the beginning idx and the size of the result string + idx_t begin = 0; + idx_t new_size = input_size; + if (FRONT_TRIM) { // left trim + auto pos = Find(data, input_size, sep); + if (pos == 0) { // path starts with separator + pos = 1; + } + new_size = (IsIdxValid(pos, input_size)) ? pos : 0; + } else { // right trim + auto idx_last_sep = FindLast(data, input_size, sep); + if (IsIdxValid(idx_last_sep, input_size)) { + begin = idx_last_sep + 1; + } + if (trim_extension) { + auto idx_extension_sep = FindLast(data, input_size, "."); + if (begin <= idx_extension_sep && IsIdxValid(idx_extension_sep, input_size)) { + new_size = idx_extension_sep; + } + } + } + D_ASSERT(begin <= new_size); + return string_t(data + begin, UnsafeNumericCast(new_size - begin)); + }); } static void ParseDirpathFunction(DataChunk &args, ExpressionState &state, Vector &result) { - // set default values - const Vector &path = args.data[0]; - Vector separator(string_t("default"), count_t(args.size())); - Vector trim_extension(Value::BOOLEAN(false), count_t(args.size())); - ReadOptionalArgs(args, separator, trim_extension, true); + ExecutePathFunction(args, result, true, [&](const string_t &input, const string &sep, bool) { + auto path = input.GetData(); + auto path_size = input.GetSize(); - auto &heap = StringVector::GetStringHeap(result); - BinaryExecutor::Execute( - path, separator, result, [&](string_t input_path, string_t input_sep) { - auto path = input_path.GetData(); - auto path_size = input_path.GetSize(); - auto sep = GetSeparator(input_sep.GetString()); - - auto last_sep = FindLast(path, path_size, sep); - if (last_sep == 0 && path_size == 1) { - last_sep = 1; - } - idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; - - auto target = heap.EmptyString(new_size); - auto output = target.GetDataWriteable(); - memcpy(output, path, new_size); - target.Finalize(); - return target; - }); + auto last_sep = FindLast(path, path_size, sep); + if (last_sep == 0 && path_size == 1) { + last_sep = 1; + } + idx_t new_size = (IsIdxValid(last_sep, path_size)) ? last_sep : 0; + return string_t(path, UnsafeNumericCast(new_size)); + }); } static void ParsePathFunction(DataChunk &args, ExpressionState &state, Vector &result) { diff --git a/src/duckdb/extension/core_functions/scalar/string/replace.cpp b/src/duckdb/extension/core_functions/scalar/string/replace.cpp index e2d70be15..91f72baf2 100644 --- a/src/duckdb/extension/core_functions/scalar/string/replace.cpp +++ b/src/duckdb/extension/core_functions/scalar/string/replace.cpp @@ -3,6 +3,7 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/common/vector_operations/ternary_executor.hpp" +#include "duckdb/function/scalar/string_common.hpp" #include #include @@ -13,13 +14,11 @@ namespace duckdb { static idx_t NextNeedle(const char *input_haystack, idx_t size_haystack, const char *input_needle, const idx_t size_needle) { // Needle needs something to proceed - if (size_needle > 0) { - // Haystack should be bigger or equal size to the needle - for (idx_t string_position = 0; (size_haystack - string_position) >= size_needle; ++string_position) { - // Compare Needle to the Haystack - if ((memcmp(input_haystack + string_position, input_needle, size_needle) == 0)) { - return string_position; - } + if (size_needle > 0 && size_haystack > 0) { + auto position = FindStrInStr(const_uchar_ptr_cast(input_haystack), size_haystack, + const_uchar_ptr_cast(input_needle), size_needle); + if (position != DConstants::INVALID_INDEX) { + return position; } } // Did not find the needle diff --git a/src/duckdb/extension/icu/collation/collation_collator.cpp b/src/duckdb/extension/icu/collation/collation_collator.cpp index 307eb7a3e..d52e5e530 100644 --- a/src/duckdb/extension/icu/collation/collation_collator.cpp +++ b/src/duckdb/extension/icu/collation/collation_collator.cpp @@ -85,6 +85,9 @@ static collation_element_t MakeElement(uint32_t primary, uint32_t secondary, uin //! Returns the position of a Han character in radical/stroke order, or false if it is not a Han character static bool GetHanIndex(const CollationRoot &root, uint32_t codepoint, uint32_t &result) { // only the ranges of the block the code point is in have to be searched + if (codepoint >= 0x110000) { + return false; + } uint32_t lower = root.han_block_lower[codepoint >> HAN_BLOCK_SHIFT]; uint32_t upper = root.han_block_upper[codepoint >> HAN_BLOCK_SHIFT]; while (lower < upper) { @@ -124,6 +127,9 @@ static collation_element_t ImplicitElement(const CollationRoot &root, uint32_t c } static uint32_t LookupValue(const CollationTable &table, uint32_t codepoint) { + if (codepoint >= 0x110000) { + return 0; + } auto block = table.trie_stage1[codepoint >> COLLATION_TRIE_SHIFT]; return table.trie_stage2[block + (codepoint & COLLATION_TRIE_MASK)]; } diff --git a/src/duckdb/extension/icu/collation/collation_normalizer.cpp b/src/duckdb/extension/icu/collation/collation_normalizer.cpp index 8a9412909..e354a96cd 100644 --- a/src/duckdb/extension/icu/collation/collation_normalizer.cpp +++ b/src/duckdb/extension/icu/collation/collation_normalizer.cpp @@ -66,7 +66,7 @@ uint32_t Normalizer::Decode(const char *data, idx_t size, vector &resu } codepoint = (codepoint << 6) | (trail & 0x3F); } - if (!valid) { + if (!valid || codepoint > 0x10FFFF) { result.push_back(REPLACEMENT_CHARACTER); pos++; continue; diff --git a/src/duckdb/extension/icu/collation/include/collation_collator.hpp b/src/duckdb/extension/icu/collation/include/collation_collator.hpp index c3639dc58..53ed16bc2 100644 --- a/src/duckdb/extension/icu/collation/include/collation_collator.hpp +++ b/src/duckdb/extension/icu/collation/include/collation_collator.hpp @@ -48,11 +48,19 @@ struct TailoredBlocks { void Build(const uint32_t *codepoints, uint32_t count); bool Contains(uint32_t codepoint) const { + if (codepoint >= 0x110000) { + return false; + } auto block = codepoint >> BITMAP_SHIFT; return (bitmap[block / 64] & (1ULL << (block % 64))) != 0; } //! The part of the tailoring that can hold the code point void GetRange(uint32_t codepoint, uint32_t &lower, uint32_t &upper) const { + if (codepoint >= 0x110000) { + lower = 0; + upper = 0; + return; + } lower = bounds[codepoint >> BOUND_SHIFT]; upper = bounds[(codepoint >> BOUND_SHIFT) + 1]; } diff --git a/src/duckdb/extension/icu/icu-datefunc.cpp b/src/duckdb/extension/icu/icu-datefunc.cpp index d839b22db..ab10a86c7 100644 --- a/src/duckdb/extension/icu/icu-datefunc.cpp +++ b/src/duckdb/extension/icu/icu-datefunc.cpp @@ -78,6 +78,17 @@ Calendar *ICUDateFunc::CalendarCacheState::GetCalendar(const string_t &tz_id) { return entry.calendar.get(); } +Calendar *ICUDateFunc::CalendarCacheState::TryGetCalendar(const string_t &tz_id, string *error_message) { + auto &entry = GetEntry(tz_id); + if (!entry.calendar) { + if (error_message) { + *error_message = entry.error; + } + return nullptr; + } + return entry.calendar.get(); +} + void ICUDateFunc::CalendarCacheState::SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message) { auto &entry = GetEntry(tz_id); if (!entry.tz) { diff --git a/src/duckdb/extension/icu/icu-datepart.cpp b/src/duckdb/extension/icu/icu-datepart.cpp index 3b1caae00..8ac85ecae 100644 --- a/src/duckdb/extension/icu/icu-datepart.cpp +++ b/src/duckdb/extension/icu/icu-datepart.cpp @@ -677,7 +677,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { // register the individual operators // year/decade use CAL_YEAR (year-of-era, positive in both BC and AD), which is non-monotonic - // across the BC/AD flip; leave them unannotated. era/century/millennium/isoyear are signed. + // across the BC/AD flip; leave them unannotated. century/millennium are negated in era 0, which is the only era + // of some calendars, and yearweek decreases within negative years, so these are unannotated as well. // BIGINTs ICUDatePart::AddUnaryPartCodeFunctions("era", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); @@ -685,8 +686,8 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("month", loader); ICUDatePart::AddUnaryPartCodeFunctions("day", loader); ICUDatePart::AddUnaryPartCodeFunctions("decade", loader); - ICUDatePart::AddUnaryPartCodeFunctions("century", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); - ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader, LogicalType::BIGINT, ArgProperties().NonDecreasing()); + ICUDatePart::AddUnaryPartCodeFunctions("century", loader); + ICUDatePart::AddUnaryPartCodeFunctions("millennium", loader); ICUDatePart::AddUnaryPartCodeFunctions("microsecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("millisecond", loader); ICUDatePart::AddUnaryPartCodeFunctions("second", loader); @@ -707,8 +708,7 @@ void RegisterICUDatePartFunctions(ExtensionLoader &loader) { ICUDatePart::AddUnaryPartCodeFunctions("julian", loader, LogicalType::DOUBLE); // register combinations - ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader, LogicalType::BIGINT, - ArgProperties().NonDecreasing()); // ISO year and week + ICUDatePart::AddUnaryPartCodeFunctions("yearweek", loader); // ISO year and week // register various aliases ICUDatePart::AddUnaryPartCodeFunctions("dayofmonth", loader); diff --git a/src/duckdb/extension/icu/icu-datesub.cpp b/src/duckdb/extension/icu/icu-datesub.cpp index d80be2ce8..1c8d48541 100644 --- a/src/duckdb/extension/icu/icu-datesub.cpp +++ b/src/duckdb/extension/icu/icu-datesub.cpp @@ -209,6 +209,20 @@ ICUDateFunc::part_sub_t ICUDateFunc::SubtractFactory(DatePartSpecifier type) { // MS-SQL differences can be computed using ICU by truncating both arguments // to the desired part precision and then applying ICU subtraction/difference struct ICUCalendarDiff : public ICUDateFunc { + static unique_ptr Bind(BindScalarFunctionInput &input) { + auto part_value = input.TryGetConstant(0); + if (part_value && !part_value->IsNull()) { + DatePartSpecifier part; + if (TryGetDatePartSpecifier(part_value->GetValue(), part) && + (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM)) { + // date_diff is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary + input.GetBoundFunction().SetArgProperties({}); + } + } + return ICUDateFunc::Bind(input); + } + static int64_t DifferenceEra(Calendar *calendar, timestamp_tz_t start_date, timestamp_tz_t end_date) { SetTime(calendar, start_date); const auto start_era = ExtractField(calendar, CAL_ERA); diff --git a/src/duckdb/extension/icu/icu-datetrunc.cpp b/src/duckdb/extension/icu/icu-datetrunc.cpp index 13562cc85..2e980012f 100644 --- a/src/duckdb/extension/icu/icu-datetrunc.cpp +++ b/src/duckdb/extension/icu/icu-datetrunc.cpp @@ -1,6 +1,7 @@ #include "include/icu-datetrunc.hpp" #include "include/icu-datefunc.hpp" +#include "duckdb/common/enums/date_part_specifier.hpp" #include "duckdb/common/vector_operations/binary_executor.hpp" #include "duckdb/planner/expression/bound_function_expression.hpp" #include "duckdb/main/extension/extension_loader.hpp" @@ -9,6 +10,20 @@ namespace duckdb { struct ICUDateTrunc : public ICUDateFunc { + static unique_ptr Bind(BindScalarFunctionInput &input) { + auto part_value = input.TryGetConstant(0); + if (part_value && !part_value->IsNull()) { + DatePartSpecifier part; + if (TryGetDatePartSpecifier(part_value->GetValue(), part) && + (part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM)) { + // date_trunc is not monotone for these parts because ICU years skip year 0 at the BC/AD boundary + input.GetBoundFunction().SetArgProperties({}); + } + } + return ICUDateFunc::Bind(input); + } + static void PreserveOffsets(Calendar *calendar) { // We have to extract _everything_ before setting anything // Otherwise ICU will clear the fStamp fields diff --git a/src/duckdb/extension/icu/icu-makedate.cpp b/src/duckdb/extension/icu/icu-makedate.cpp index 058355bc2..b41f54893 100644 --- a/src/duckdb/extension/icu/icu-makedate.cpp +++ b/src/duckdb/extension/icu/icu-makedate.cpp @@ -62,8 +62,10 @@ bool ICUMakeDate::CastNsToDate(Vector &source, Vector &result, idx_t count, Cast } // the date only needs the microsecond instant: floor the nanoseconds so no instant moves past midnight const auto nanos = input.value; - const auto micros = nanos >= 0 ? nanos / Interval::NANOS_PER_MICRO - : (nanos - (Interval::NANOS_PER_MICRO - 1)) / Interval::NANOS_PER_MICRO; + auto micros = nanos / Interval::NANOS_PER_MICRO; + if (nanos % Interval::NANOS_PER_MICRO < 0) { + micros--; + } return Operation(calendar.get(), timestamp_tz_t(micros)); }); return true; diff --git a/src/duckdb/extension/icu/icu-strptime.cpp b/src/duckdb/extension/icu/icu-strptime.cpp index 60c2b8b91..e53514780 100644 --- a/src/duckdb/extension/icu/icu-strptime.cpp +++ b/src/duckdb/extension/icu/icu-strptime.cpp @@ -143,12 +143,9 @@ struct ICUStrptime : public ICUDateFunc { result = timestamp_tz_t(parsed.ToTimestamp()); return; } else { - // Set TZ first, if any. - if (!parsed.tz.empty()) { - cache.SetTimeZone(calendar, parsed.tz); - } - - result = GetTime(calendar, ToMicros(calendar, parsed, format)); + // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone + auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); + result = GetTime(row_calendar, ToMicros(row_calendar, parsed, format)); return; } } @@ -166,11 +163,9 @@ struct ICUStrptime : public ICUDateFunc { result.value = parsed.ToTimestamp().value; return; } else { - // Set TZ first, if any. - if (!parsed.tz.empty()) { - cache.SetTimeZone(calendar, parsed.tz); - } - result = GetTimeNS(calendar, ToNanos(calendar, parsed, format)); + // Use the calendar of the parsed time zone, if any - the shared calendar keeps the session zone + auto row_calendar = parsed.tz.empty() ? calendar : cache.GetCalendar(parsed.tz); + result = GetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format)); return; } } @@ -207,8 +202,9 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { - if (TryGetTime(calendar, ToMicros(calendar, parsed, format), result)) { + } else { + auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); + if (row_calendar && TryGetTime(row_calendar, ToMicros(row_calendar, parsed, format), result)) { return true; } } @@ -226,8 +222,9 @@ struct ICUStrptime : public ICUDateFunc { if (parsed.is_special) { result.value = parsed.ToTimestamp().value; return true; - } else if (parsed.tz.empty() || cache.TrySetTimeZone(calendar, parsed.tz)) { - if (TryGetTimeNS(calendar, ToNanos(calendar, parsed, format), result)) { + } else { + auto row_calendar = parsed.tz.empty() ? calendar : cache.TryGetCalendar(parsed.tz); + if (row_calendar && TryGetTimeNS(row_calendar, ToNanos(row_calendar, parsed, format), result)) { return true; } } @@ -403,18 +400,24 @@ struct ICUStrptime : public ICUDateFunc { // Convert parts to a TZ (default or parsed) if no offset was provided auto calendar = cal.get(); - // Change TZ if one was provided. + // Use the calendar of the time zone if one was provided - the shared calendar keeps the session zone if (tz.GetSize()) { string error_msg; - cache.SetTimeZone(calendar, tz, &error_msg); - if (!error_msg.empty()) { + calendar = cache.TryGetCalendar(tz, &error_msg); + if (!calendar) { HandleCastError::AssignError(error_msg, parameters); return nullopt; } } // Now get the parts in the given time zone - return FromNaive(calendar, us); + timestamp_tz_t result; + string error_msg; + if (!TryFromNaive(calendar, us, result, error_msg)) { + HandleCastError::AssignError(error_msg, parameters); + return nullopt; + } + return result; } return timestamp_tz_t(us); diff --git a/src/duckdb/extension/icu/icu-timebucket.cpp b/src/duckdb/extension/icu/icu-timebucket.cpp index 0a9586ab1..495ead083 100644 --- a/src/duckdb/extension/icu/icu-timebucket.cpp +++ b/src/duckdb/extension/icu/icu-timebucket.cpp @@ -73,6 +73,25 @@ struct ICUTimeBucket : public ICUDateFunc { return Add(calendar, origin, interval_t {0, 0, result_micros}); } + //! Add resolves a local time that occurs twice (e.g. when the clocks are turned back) to its latter occurrence. + //! If that is after ts, ts lies between the two occurrences, and its bucket starts at the former occurrence. + static timestamp_tz_t FormerOccurrence(Calendar *calendar, const timestamp_tz_t bucket, const timestamp_tz_t ts) { + auto get_offset = [&](const timestamp_tz_t instant) { + SetTime(calendar, instant); + return int64_t(ExtractField(calendar, CAL_ZONE_OFFSET)) + ExtractField(calendar, CAL_DST_OFFSET); + }; + const auto ts_offset = get_offset(ts); + const auto bucket_offset = get_offset(bucket); + if (ts_offset <= bucket_offset) { + return bucket; + } + const timestamp_tz_t former(bucket.value - (ts_offset - bucket_offset) * Interval::MICROS_PER_MSEC); + if (ts < former || get_offset(former) != ts_offset) { + return bucket; + } + return former; + } + static inline timestamp_tz_t WidthConvertibleToDaysCommon(int32_t bucket_width_days, const timestamp_tz_t ts, const timestamp_tz_t origin, TZCalendar &calendar) { if (!bucket_width_days) { @@ -87,9 +106,10 @@ struct ICUTimeBucket : public ICUDateFunc { } auto bucket = Add(calendar, origin, interval_t {0, static_cast(result_days), 0}); if (ts < bucket) { - D_ASSERT(ts < origin); + bucket = FormerOccurrence(calendar.GetCalendar(), bucket, ts); + } + while (ts < bucket) { bucket = Add(calendar, bucket, interval_t {0, -bucket_width_days, 0}); - D_ASSERT(ts > bucket); } return bucket; @@ -123,7 +143,14 @@ struct ICUTimeBucket : public ICUDateFunc { SubtractOperatorOverflowCheck::Operation(result_months, bucket_width_months); } - return Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); + auto bucket = Add(calendar_p, truncated_origin, interval_t {static_cast(result_months), 0, 0}); + if (ts < bucket) { + bucket = FormerOccurrence(calendar, bucket, ts); + } + while (ts < bucket) { + bucket = Add(calendar_p, bucket, interval_t {-bucket_width_months, 0, 0}); + } + return bucket; } template diff --git a/src/duckdb/extension/icu/icu-timezone.cpp b/src/duckdb/extension/icu/icu-timezone.cpp index 2caa2ebcb..23dd0195e 100644 --- a/src/duckdb/extension/icu/icu-timezone.cpp +++ b/src/duckdb/extension/icu/icu-timezone.cpp @@ -708,6 +708,10 @@ timestamp_tz_t ICUDateFunc::FromNaive(Calendar *calendar, timestamp_t naive) { return ICUFromNaiveTimestamp::Operation(calendar, naive); } +bool ICUDateFunc::TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error) { + return ICUFromNaiveTimestamp::TryOperation(calendar, naive, result, error); +} + void RegisterICUTimeZoneFunctions(ExtensionLoader &loader) { // Table functions TableFunction tz_names("pg_timezone_names", {}, ICUTimeZoneFunction, ICUTimeZoneBind, ICUTimeZoneInit); diff --git a/src/duckdb/extension/icu/include/icu-datefunc.hpp b/src/duckdb/extension/icu/include/icu-datefunc.hpp index 04ad57f5b..c47777f8e 100644 --- a/src/duckdb/extension/icu/include/icu-datefunc.hpp +++ b/src/duckdb/extension/icu/include/icu-datefunc.hpp @@ -62,6 +62,8 @@ struct ICUDateFunc { //! Returns a calendar for the time zone. Throws for unknown zones. Calendar *GetCalendar(const string_t &tz_id); + //! Returns a calendar for the time zone, or nullptr for unknown zones (filling error_message if provided). + Calendar *TryGetCalendar(const string_t &tz_id, string *error_message = nullptr); //! Sets the time zone for the calendar. //! For unknown zones, fills error_message if provided and throws otherwise. void SetTimeZone(Calendar *calendar, const string_t &tz_id, string *error_message = nullptr); @@ -122,6 +124,8 @@ struct ICUDateFunc { //! constructs an ICU timestamp, and then converts that back to a DuckDB instant //! Adding offset doesn't really work around DST because the bin values are ambiguous static timestamp_tz_t FromNaive(Calendar *calendar, timestamp_t naive); + //! Non-throwing form of FromNaive - sets the error and returns false if the result is out of range + static bool TryFromNaive(Calendar *calendar, timestamp_t naive, timestamp_tz_t &result, string &error); //! Truncates the calendar time to the given part precision typedef void (*part_trunc_t)(Calendar *calendar, uint64_t µs); diff --git a/src/duckdb/extension/json/json_functions/json_create.cpp b/src/duckdb/extension/json/json_functions/json_create.cpp index 4eb1fa82c..6117f0b89 100644 --- a/src/duckdb/extension/json/json_functions/json_create.cpp +++ b/src/duckdb/extension/json/json_functions/json_create.cpp @@ -80,9 +80,9 @@ enum class JSONGeometryFormat : uint8_t { WKT, GEOJSON }; struct JSONCopyFormatOptions { JSONGeometryFormat geometry_format = JSONGeometryFormat::WKT; - optional_ptr date_format; - optional_ptr timestamp_format; - optional_ptr context; + mutable optional_ptr date_format; + mutable optional_ptr timestamp_format; + mutable optional_ptr context; optional_ptr timestamptz_format_expression; optional_ptr timestamptz_ns_format_expression; }; @@ -862,7 +862,7 @@ static void CreateValuesFormatted(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V static void CreateValuesDate(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.date_format), [&](Vector &string_vector) { - options.date_format.get_mutable()->ConvertDateVector(value_v, string_vector); + options.date_format->ConvertDateVector(value_v, string_vector); }); } @@ -875,14 +875,14 @@ static void CreateValuesTimestamp(yyjson_mut_doc *doc, yyjson_mut_val *vals[], V return; } CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format.get_mutable()->ConvertTimestampVector(value_v, string_vector); + options.timestamp_format->ConvertTimestampVector(value_v, string_vector); }); } static void CreateValuesTimestampNS(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count, const JSONCopyFormatOptions &options) { CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) { - options.timestamp_format.get_mutable()->ConvertTimestampNSVector(value_v, string_vector); + options.timestamp_format->ConvertTimestampNSVector(value_v, string_vector); }); } @@ -892,7 +892,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], // no COPY format was given - use the full cast set so that the session time zone is applied Vector string_vector(LogicalType::VARCHAR, count); if (options.context) { - VectorOperations::Cast(*options.context.get_mutable(), value_v, string_vector, count, true); + VectorOperations::Cast(*options.context, value_v, string_vector, count, true); } else { VectorOperations::DefaultCast(value_v, string_vector, count); } @@ -910,7 +910,7 @@ static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], input.InitializeEmpty({value_v.GetType()}); input.data[0].Reference(value_v); input.CheckCardinality(count); - ExpressionExecutor executor(*options.context.get_mutable(), *format_expression); + ExpressionExecutor executor(*options.context, *format_expression); executor.ExecuteExpression(input, string_vector); }); } @@ -1516,6 +1516,8 @@ ScalarFunction JSONFunctions::GetJSONCopyToJSONFunction() { fun.GetSignature().GetParameter(1).SetName("date_format"); fun.GetSignature().GetParameter(2).SetName("timestamp_format"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + // formatting dates and timestamps can fail + fun.SetFallible(); fun.SetSerializeCallback(JSONCopyToJSONSerialize); fun.SetDeserializeCallback(JSONCopyToJSONDeserialize); return fun; @@ -1534,6 +1536,7 @@ ScalarFunction JSONFunctions::GetJSONCopyToGeoJSONFunction() { fun.GetSignature().GetParameter(4).SetName("id_column"); fun.GetSignature().GetParameter(5).SetName("write_bbox"); fun.SetNullHandling(FunctionNullHandling::SPECIAL_HANDLING); + fun.SetFallible(); return fun; } diff --git a/src/duckdb/extension/json/json_functions/json_transform.cpp b/src/duckdb/extension/json/json_functions/json_transform.cpp index 4c08be33d..ffa43977e 100644 --- a/src/duckdb/extension/json/json_functions/json_transform.cpp +++ b/src/duckdb/extension/json/json_functions/json_transform.cpp @@ -11,6 +11,7 @@ #include "duckdb/common/error_data.hpp" #include "duckdb/common/serializer/deserializer.hpp" #include "duckdb/common/types/geometry.hpp" +#include "duckdb/common/types/value_map.hpp" #include "json_geojson.hpp" #include "duckdb/common/serializer/serializer.hpp" #include "duckdb/common/types.hpp" @@ -44,7 +45,8 @@ static LogicalType StructureStringToTypeArray(yyjson_val *arr, ClientContext &co } static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context) { - unordered_set names; + // STRUCT field names are case-insensitive + identifier_set_t names; child_list_t child_types; size_t idx, max; yyjson_val *key, *val; @@ -54,10 +56,9 @@ static LogicalType StructureToTypeObject(yyjson_val *obj, ClientContext &context if (key_str.empty()) { throw BinderException("Empty key in object in JSON structure"); } - if (names.find(key_str) != names.end()) { + if (!names.insert(Identifier(key_str)).second) { JSONCommon::ThrowValFormatError("Duplicate keys in object in JSON structure: %s", val); } - names.insert(key_str); child_types.emplace_back(key_str, StructureStringToType(val, context)); } D_ASSERT(yyjson_obj_size(obj) == names.size()); @@ -922,6 +923,38 @@ static bool TransformObjectToMap(yyjson_val *objects[], yyjson_alc *alc, Vector success = false; } + // objects can have duplicate keys, and keys that fail to transform in non-strict mode are NULL + auto &map_keys = MapVector::GetKeys(result); + auto key_validity = map_keys.Validity(); + value_set_t unique_keys; + for (idx_t i = 0; i < count; i++) { + if (!list_validity.RowIsValid(i)) { + continue; + } + const auto &list_entry = list_entries[i]; + const char *invalid_reason = nullptr; + unique_keys.clear(); + for (idx_t key_idx = list_entry.offset; key_idx < list_entry.offset + list_entry.length; key_idx++) { + if (!key_validity.IsValid(key_idx)) { + invalid_reason = "Map keys can not be NULL"; + break; + } + if (!unique_keys.insert(map_keys.GetValue(key_idx)).second) { + invalid_reason = "Map keys must be unique"; + break; + } + } + if (!invalid_reason) { + continue; + } + list_validity.SetInvalid(i); + if (success && options.strict_cast) { + options.error_message = invalid_reason; + options.object_index = i; + success = false; + } + } + if (!options.delay_error && !success) { throw InvalidInputException(options.error_message); } diff --git a/src/duckdb/extension/json/json_reader.cpp b/src/duckdb/extension/json/json_reader.cpp index 8e32310db..1ecd229b1 100644 --- a/src/duckdb/extension/json/json_reader.cpp +++ b/src/duckdb/extension/json/json_reader.cpp @@ -771,7 +771,15 @@ bool JSONReader::ParseJSON(JSONReaderScanState &scan_state, char *const json_sta // If we return strings, we cannot parse INSITU. If we ignore errors, we cannot parse INSITU either: a malformed // document could extend into (and modify) the next one const bool ignorable_errors = options.ignore_errors && GetFormat() == JSONFormat::NEWLINE_DELIMITED; - if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors) { + // A number that does not fit is read as a raw string, which INSITU parsing null-terminates in place - at the root + // that overwrites the delimiter that follows the document + idx_t first_char = 0; + while (first_char < json_size && StringUtil::CharacterIsSpace(json_start[first_char])) { + first_char++; + } + const bool number_root = first_char < json_size && + (StringUtil::CharacterIsDigit(json_start[first_char]) || json_start[first_char] == '-'); + if (options.type == JSONScanType::READ_JSON_OBJECTS || ignorable_errors || number_root) { doc = JSONCommon::ReadDocumentUnsafe(json_start, json_size, JSONCommon::READ_STOP_FLAG, scan_state.allocator.GetYYAlc(), &err); } else { diff --git a/src/duckdb/extension/parquet/column_reader.cpp b/src/duckdb/extension/parquet/column_reader.cpp index 453be1314..54519dfc2 100644 --- a/src/duckdb/extension/parquet/column_reader.cpp +++ b/src/duckdb/extension/parquet/column_reader.cpp @@ -250,6 +250,9 @@ void ColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_row chunk = &columns[ColumnIndex()]; protocol = &protocol_p; D_ASSERT(chunk); + if (chunk->__isset.crypto_metadata && !reader.metadata->crypto_metadata) { + throw InvalidInputException("File is encrypted but no file crypto metadata is set"); + } D_ASSERT(chunk->__isset.meta_data); if (chunk->__isset.file_path) { @@ -516,9 +519,6 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { uint32_t compressed_page_size = page_hdr.compressed_page_size; if (chunk->__isset.crypto_metadata) { - if (!reader.metadata->crypto_metadata) { - throw InvalidInputException("File is encrypted but no file crypto metadata is set"); - } auto const file_aad = reader.GetUniqueFileIdentifier(reader.metadata->crypto_metadata->encryption_algorithm); if (!file_aad.empty()) { // If there is a file aad (identifier), this means that the Encrypted file is written by Arrow @@ -550,6 +550,50 @@ void ColumnReader::PreparePage(PageHeader &page_hdr) { page_hdr.uncompressed_page_size); } +static bool TryDecompressLZ4Hadoop(const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { + // Hadoop framing stores a big-endian uncompressed block size, followed by one or more + // chunks, each prefixed by its big-endian compressed size. + while (src_size > 0) { + if (src_size < sizeof(uint32_t)) { + return false; + } + auto block_size = BSwapIfLE(Load(src)); + src += sizeof(uint32_t); + src_size -= sizeof(uint32_t); + if (block_size > dst_size) { + return false; + } + if (block_size == 0) { + // Hadoop uses a zero-sized block to terminate an empty stream. + return src_size == 0 && dst_size == 0; + } + idx_t remaining = block_size; + while (remaining > 0) { + if (src_size < sizeof(uint32_t)) { + return false; + } + auto compressed_size = BSwapIfLE(Load(src)); + src += sizeof(uint32_t); + src_size -= sizeof(uint32_t); + if (compressed_size == 0 || compressed_size > src_size) { + return false; + } + auto decompressed_size = + duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), + NumericCast(compressed_size), NumericCast(remaining)); + if (decompressed_size <= 0) { + return false; + } + src += compressed_size; + src_size -= compressed_size; + dst += decompressed_size; + dst_size -= decompressed_size; + remaining -= decompressed_size; + } + } + return dst_size == 0; +} + void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_ptr_t src, idx_t src_size, data_ptr_t dst, idx_t dst_size) { switch (codec) { @@ -560,6 +604,12 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p s.Decompress(const_char_ptr_cast(src), src_size, char_ptr_cast(dst), dst_size); break; } + case CompressionCodec::LZ4: + if (TryDecompressLZ4Hadoop(src, src_size, dst, dst_size)) { + break; + } + // Early parquet-cpp writers used raw LZ4 under the legacy codec identifier. + DUCKDB_EXPLICIT_FALLTHROUGH; case CompressionCodec::LZ4_RAW: { auto res = duckdb_lz4::LZ4_decompress_safe(const_char_ptr_cast(src), char_ptr_cast(dst), @@ -619,7 +669,7 @@ void ColumnReader::DecompressInternal(CompressionCodec::type codec, const_data_p duckdb::stringstream codec_name; codec_name << codec; throw InvalidInputException("Failed to read file \"%s\": Unsupported compression codec \"%s\". Supported " - "options are uncompressed, brotli, gzip, lz4_raw, snappy or zstd", + "options are uncompressed, brotli, gzip, lz4, lz4_raw, snappy or zstd", Reader().GetFileName(), codec_name.str()); } // LCOV_EXCL_STOP } diff --git a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp index 8c54b9243..74da3df82 100644 --- a/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp +++ b/src/duckdb/extension/parquet/decoder/delta_length_byte_array_decoder.cpp @@ -45,35 +45,6 @@ bool IsContinuationByte(uint8_t b) { return (b & 0xC0) == 0x80; } -//! Whether a string starts or ends in the middle of a multi-byte character. These are the only strings that can be -//! invalid on their own when the batch they are part of is valid UTF-8 -bool CutsUtf8Character(const char *data, uint32_t len) { - if (len == 0) { - return false; - } - const auto bytes = const_data_ptr_cast(data); - if (IsContinuationByte(bytes[0])) { - return true; - } - // find the first byte of the last character - a character is at most 4 bytes - idx_t last_start = len - 1; - while (last_start > 0 && len - last_start < 4 && IsContinuationByte(bytes[last_start])) { - last_start--; - } - const auto first_byte = bytes[last_start]; - idx_t char_length; - if (first_byte < 0x80) { - char_length = 1; - } else if (first_byte >= 0xF0) { - char_length = 4; - } else if (first_byte >= 0xE0) { - char_length = 3; - } else { - char_length = 2; - } - return len - last_start != char_length; -} - void DeltaLengthByteArrayDecoder::Read(shared_ptr &block_ref, uint8_t *defines, idx_t read_count, Vector &result, idx_t result_offset) { if (defines) { @@ -109,6 +80,7 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo StringColumnReader::Utf8ValidationOption::STRICT_UTF8; const auto start_ptr = block.ptr; + auto current_ptr = start_ptr; auto result_data = FlatVector::Writer(result, read_count, result_offset); for (idx_t row_idx = 0; row_idx < read_count; row_idx++) { @@ -125,20 +97,23 @@ void DeltaLengthByteArrayDecoder::ReadInternal(shared_ptr &blo length_idx, byte_array_count); } } - const auto &str_len = length_data[length_idx++]; + const auto str_len = length_data[length_idx++]; if (needs_full_individual_validation) { - auto verified = string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); + auto verified = string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); result_data.WriteValue(verified); } else { - if (CutsUtf8Character(char_ptr_cast(block.ptr), str_len)) { - string_column_reader.VerifyString(char_ptr_cast(block.ptr), str_len); + // Batch validation below catches incomplete trailing characters. Within a valid batch, a character + // can only cross a string boundary if the next nonempty string starts with a continuation byte. + if (str_len > 0 && IsContinuationByte(current_ptr[0])) { + string_column_reader.VerifyString(char_ptr_cast(current_ptr), str_len); } - result_data.WriteStringRef(string_t(char_ptr_cast(block.ptr), str_len)); + result_data.WriteStringRef(string_t(char_ptr_cast(current_ptr), str_len)); } - block.unsafe_inc(str_len); + current_ptr += str_len; } + block.unsafe_inc(current_ptr - start_ptr); if (!needs_full_individual_validation) { string_column_reader.VerifyString(char_ptr_cast(start_ptr), NumericCast(block.ptr - start_ptr)); } diff --git a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp index 775160215..6f71072e3 100644 --- a/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp +++ b/src/duckdb/extension/parquet/include/parquet_dbp_decoder.hpp @@ -27,7 +27,7 @@ class DbpDecoder { miniblock_index(number_of_miniblocks_per_block - 1), list_of_bitwidths_of_miniblocks(nullptr), miniblock_offset(number_of_values_in_a_miniblock), unpacked_data_offset(BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE) { - if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && + if (!(block_size_in_values % number_of_miniblocks_per_block == 0 && number_of_values_in_a_miniblock > 0 && number_of_values_in_a_miniblock % BitpackingPrimitives::BITPACKING_ALGORITHM_GROUP_SIZE == 0)) { throw InvalidInputException("Parquet file has invalid block sizes for DELTA_BINARY_PACKED"); } @@ -63,6 +63,9 @@ class DbpDecoder { if (miniblock_offset == number_of_values_in_a_miniblock) { return; } + if (miniblock_offset > number_of_values_in_a_miniblock) { + throw InvalidInputException("Parquet file has invalid miniblocks for DELTA_BINARY_PACKED"); + } auto data = make_unsafe_uniq_array(number_of_values_in_a_miniblock); GetBatchInternal(data_ptr_cast(data.get()), number_of_values_in_a_miniblock - miniblock_offset); } diff --git a/src/duckdb/extension/parquet/include/parquet_reader.hpp b/src/duckdb/extension/parquet/include/parquet_reader.hpp index 479ce7fb6..b7af194e4 100644 --- a/src/duckdb/extension/parquet/include/parquet_reader.hpp +++ b/src/duckdb/extension/parquet/include/parquet_reader.hpp @@ -475,6 +475,9 @@ class ParquetReader : public BaseFileReader { idx_t GetGroupOffset(ParquetReaderScanState &state); //! Group span is the distance between the min page offset and the max page offset plus the max page compressed size uint64_t GetGroupSpan(ParquetReaderScanState &state); + FilterPropagateResult CheckRowGroupFilterStatistics(ClientContext &context, ParquetReaderScanState &state, + ColumnReader &column_reader, const TableFilter &filter); + bool DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state); void PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t out_col_idx); //! Whole-group prefetch strategy. ParquetPrefetchStrategy WholeGroupPrefetch(ParquetReaderScanState &state, ThriftFileTransport &trans, diff --git a/src/duckdb/extension/parquet/parquet_extension.cpp b/src/duckdb/extension/parquet/parquet_extension.cpp index f14943469..fc01e0def 100644 --- a/src/duckdb/extension/parquet/parquet_extension.cpp +++ b/src/duckdb/extension/parquet/parquet_extension.cpp @@ -283,6 +283,9 @@ static unique_ptr ParquetWriteBind(ClientContext &context, CopyFun for (idx_t i = 0; i < values.size(); i++) { auto &value = values[i]; auto key = StructType::GetChildName(kv_struct_type, i); + if (value.IsNull()) { + throw BinderException("kv_metadata value for key %s cannot be NULL", key); + } // If the value is a blob, write the raw blob bytes // otherwise, cast to string if (value.type().id() == LogicalTypeId::BLOB) { diff --git a/src/duckdb/extension/parquet/parquet_metadata.cpp b/src/duckdb/extension/parquet/parquet_metadata.cpp index cb1da7910..3d8b00473 100644 --- a/src/duckdb/extension/parquet/parquet_metadata.cpp +++ b/src/duckdb/extension/parquet/parquet_metadata.cpp @@ -566,8 +566,12 @@ void ParquetRowGroupMetadataProcessor::ReadRow(vector> &output class ParquetSchemaProcessor : public ParquetMetadataFileProcessor { public: + void InitializeInternal(ClientContext &context, ParquetReader &reader) override; idx_t TotalRowCount(ParquetReader &reader) override; void ReadRow(vector> &output, idx_t row_idx, ParquetReader &reader) override; + +private: + vector element_types; }; template <> @@ -668,6 +672,25 @@ static Value ParquetLogicalTypeToString(const duckdb_parquet::LogicalType &type, return Value(); } +static void CollectElementTypes(const ParquetColumnSchema &schema, vector &element_types) { + // a group collapsed into its only child has the type of that child + for (auto &index : {schema.parent_schema_index, schema.schema_index}) { + if (index.IsValid() && index.GetIndex() < element_types.size() && element_types[index.GetIndex()].IsNull()) { + element_types[index.GetIndex()] = Value(schema.type.ToString()); + } + } + for (auto &child : schema.children) { + CollectElementTypes(child, element_types); + } +} + +void ParquetSchemaProcessor::InitializeInternal(ClientContext &context, ParquetReader &reader) { + element_types.assign(reader.GetFileMetadata()->schema.size(), Value()); + for (auto &column : reader.root_schema->children) { + CollectElementTypes(column, element_types); + } +} + idx_t ParquetSchemaProcessor::TotalRowCount(ParquetReader &reader) { return reader.GetFileMetadata()->schema.size(); } @@ -699,12 +722,7 @@ void ParquetSchemaProcessor::ReadRow(vector> &output, idx_t ro // logical_type output[10].get().Append(ParquetLogicalTypeToString(column.logicalType, column.__isset.logicalType)); // duckdb_type - ParquetColumnSchema column_schema; - Value duckdb_type; - if (column.__isset.type) { - duckdb_type = reader.DeriveLogicalType(column, column_schema).ToString(); - } - output[11].get().Append(duckdb_type); + output[11].get().Append(element_types[row_idx]); // column_id output[12].get().Append(Value::BIGINT(UnsafeNumericCast(row_idx))); } diff --git a/src/duckdb/extension/parquet/parquet_reader.cpp b/src/duckdb/extension/parquet/parquet_reader.cpp index 808bce83c..ef68bc96d 100644 --- a/src/duckdb/extension/parquet/parquet_reader.cpp +++ b/src/duckdb/extension/parquet/parquet_reader.cpp @@ -593,6 +593,10 @@ LogicalType ParquetReader::DeriveLogicalType(const SchemaElement &s_ele, const P throw IOException("TIME_MICROS converted type can only be set for value of Type::INT64"); } case ConvertedType::INTERVAL: + if (s_ele.type != Type::FIXED_LEN_BYTE_ARRAY || s_ele.type_length != 12) { + throw DataCorruptionException( + "INTERVAL converted type can only be set for value of Type::FIXED_LEN_BYTE_ARRAY of length 12"); + } return LogicalType::INTERVAL; case ConvertedType::JSON: return LogicalType::JSON(); @@ -1041,11 +1045,20 @@ static bool IsVariantType(const SchemaElement &root, const vector MAX_PARQUET_SCHEMA_DEPTH) { + throw InvalidInputException( + "Malformed Parquet schema in file \"%s\": the schema is nested more than %llu levels " + "deep", + file.path, MAX_PARQUET_SCHEMA_DEPTH); + } if (next_schema_idx >= file_meta_data->schema.size()) { throw InvalidInputException("Malformed Parquet schema in file \"%s\": invalid schema index %d", file.path, next_schema_idx); @@ -1145,6 +1158,24 @@ ParquetColumnSchema ParquetReader::ParseSchemaRecursive(idx_t depth, idx_t max_d if (!is_variant && parquet_options.variant_legacy_encoding && IsVariantType(s_ele, child_schemas)) { is_variant = true; } + if (is_variant) { + // the VARIANT reader reads the 'metadata' and 'value' columns as binary + for (auto &child_schema : child_schemas) { + if (child_schema.name != "metadata" && child_schema.name != "value") { + continue; + } + // a logical type annotation (e.g. GEOMETRY) would make the column reader produce another type + const auto is_binary = + child_schema.type.id() == LogicalTypeId::BLOB || child_schema.type.id() == LogicalTypeId::VARCHAR; + if (!child_schema.children.empty() || child_schema.parquet_type != duckdb_parquet::Type::BYTE_ARRAY || + !is_binary || child_schema.schema_type != ParquetColumnSchemaType::COLUMN) { + throw DataCorruptionException( + "Failed to read Parquet file \"%s\": the '%s' column of VARIANT column \"%s\" " + "must be a binary BYTE_ARRAY column", + file.path, child_schema.name, s_ele.name); + } + } + } if (!is_map_kv && this_idx > 0) { // check if the parent node of this is a map @@ -1770,6 +1801,55 @@ ColumnReader &ParquetReaderScanState::GetColumnReader(idx_t i) { return *column_readers[i]; } +FilterPropagateResult ParquetReader::CheckRowGroupFilterStatistics(ClientContext &context, + ParquetReaderScanState &state, + ColumnReader &column_reader, + const TableFilter &filter) { + auto &group = GetGroup(state); + auto schema_column_index = column_reader.ColumnIndex(); + auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; + bool is_generated_column = schema_column_index >= group.columns.size(); + bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; + auto stats = column_reader.Stats(state.group_index, group.columns); + if (stats) { + bool has_min_max = false; + if (!is_generated_column) { + has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && + group.columns[schema_column_index].meta_data.statistics.__isset.max_value; + } + if (!is_expression && !is_generated_column && has_min_max && + (column_reader.Type().id() == LogicalTypeId::FLOAT || column_reader.Type().id() == LogicalTypeId::DOUBLE) && + ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, + parquet_options.can_have_nan)) { + // floating point columns can have NaN values in addition to the min/max bounds defined in the file + // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning + // based on nan + prune_result = CheckParquetFloatFilter(context, column_reader, + group.columns[schema_column_index].meta_data.statistics, filter); + } else { + auto &expr_filter = + ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::CheckRowGroupFilterStatistics"); + prune_result = expr_filter.CheckStatistics(context, *stats); + } + } + return prune_result; +} + +bool ParquetReader::DynamicFiltersExcludeRowGroup(ClientContext &context, ParquetReaderScanState &state) { + for (auto &scan_filter : state.scan_filters) { + if (!ExpressionFilter::GetOptionalDynamicFilterData(scan_filter.filter)) { + continue; + } + auto &column_reader = state.GetColumnReader(MultiFileLocalIndex(scan_filter.filter_idx)); + auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, scan_filter.filter); + if (prune_result == FilterPropagateResult::FILTER_ALWAYS_FALSE || + prune_result == FilterPropagateResult::FILTER_FALSE_OR_NULL) { + return true; + } + } + return false; +} + void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderScanState &state, idx_t i) { auto &group = GetGroup(state); auto col_idx = MultiFileLocalIndex(i); @@ -1794,33 +1874,7 @@ void ParquetReader::PrepareRowGroupBuffer(ClientContext &context, ParquetReaderS if (filter_entry) { auto &filter = *filter_entry; - auto schema_column_index = column_reader.ColumnIndex(); - auto prune_result = FilterPropagateResult::NO_PRUNING_POSSIBLE; - bool is_generated_column = schema_column_index >= group.columns.size(); - bool is_expression = column_reader.Schema().schema_type == ParquetColumnSchemaType::EXPRESSION; - auto stats = column_reader.Stats(state.group_index, group.columns); - if (stats) { - bool has_min_max = false; - if (!is_generated_column) { - has_min_max = group.columns[schema_column_index].meta_data.statistics.__isset.min_value && - group.columns[schema_column_index].meta_data.statistics.__isset.max_value; - } - if (!is_expression && !is_generated_column && has_min_max && - (column_reader.Type().id() == LogicalTypeId::FLOAT || - column_reader.Type().id() == LogicalTypeId::DOUBLE) && - ParquetStatisticsUtils::CanHaveNaN(group.columns[schema_column_index].meta_data.statistics, - parquet_options.can_have_nan)) { - // floating point columns can have NaN values in addition to the min/max bounds defined in the file - // in order to do optimal pruning - we prune based on the [min, max] of the file followed by pruning - // based on nan - prune_result = CheckParquetFloatFilter( - context, column_reader, group.columns[schema_column_index].meta_data.statistics, filter); - } else { - auto &expr_filter = - ExpressionFilter::GetExpressionFilter(filter, "ParquetReader::PrepareRowGroupBuffer"); - prune_result = expr_filter.CheckStatistics(context, *stats); - } - } + auto prune_result = CheckRowGroupFilterStatistics(context, state, column_reader, filter); // check the bloom filter if present auto bloom_source = TryGetBloomFilterSource(column_reader); if (bloom_source) { @@ -2445,6 +2499,11 @@ AsyncResult ParquetReader::Process(ClientContext &context, ParquetReaderScanStat const bool log_prefetch = Logger::Get(context).ShouldLog(ParquetPrefetchLogType::NAME, ParquetPrefetchLogType::LEVEL); const idx_t group_num_rows = GetGroup(state).num_rows; + // Queued row groups may predate the current dynamic filter. Do not revisit a partially decoded chunk. + if (state.offset_in_group == 0 && !state.resuming_payload && DynamicFiltersExcludeRowGroup(context, state)) { + rows_read += group_num_rows; + state.offset_in_group = group_num_rows; + } auto scan_count = MinValue(STANDARD_VECTOR_SIZE, group_num_rows - state.offset_in_group); if (!state.resuming_payload) { result.SetChildCardinality(scan_count); diff --git a/src/duckdb/extension/parquet/parquet_shredding.cpp b/src/duckdb/extension/parquet/parquet_shredding.cpp index aacbacab7..903a03020 100644 --- a/src/duckdb/extension/parquet/parquet_shredding.cpp +++ b/src/duckdb/extension/parquet/parquet_shredding.cpp @@ -72,6 +72,11 @@ static ShreddingType ConvertShreddingTypeRecursive(const LogicalType &type) { return ShreddingType(LogicalType(LogicalTypeId::ANY)); } if (!type.IsNested()) { + // the values are written without further conversion or validation, so types with an alias (e.g., JSON) and + // types without a Parquet typed_value representation cannot be shredded on + if (type.HasAlias() || type.id() == LogicalTypeId::BIT || type.id() == LogicalTypeId::BIGNUM) { + throw BinderException("VARIANT cannot be shredded on type %s in Parquet", type.ToString()); + } return ShreddingType(type); } diff --git a/src/duckdb/extension/parquet/read_single_parquet_file.cpp b/src/duckdb/extension/parquet/read_single_parquet_file.cpp index 2739185de..d88450a5c 100644 --- a/src/duckdb/extension/parquet/read_single_parquet_file.cpp +++ b/src/duckdb/extension/parquet/read_single_parquet_file.cpp @@ -37,6 +37,20 @@ struct ReadSingleParquetFileData : public TableFunctionData { } } + unique_ptr Copy() const override { + // the copy does not take over the reader the bind opened - its scans open the file themselves + auto result = make_uniq(); + result->file = file; + result->options = options; + result->parquet_names = parquet_names; + result->parquet_types = parquet_types; + result->columns = columns; + result->metadata = metadata; + result->cardinality = cardinality; + result->projection_expressions = projection_expressions; + return std::move(result); + } + //! The reader the bind opened for this file, handed to the first scan of this bind data so that the file does //! not have to be opened again. Only set when the bind data is used to read the file shared_ptr TakeBindReader() const { @@ -397,6 +411,12 @@ static virtual_column_map_t ReadSingleParquetFileVirtualColumns(ClientContext &c return result; } +//! The row number within the file identifies a row of a single file +static vector ReadSingleParquetFileRowIdColumns(ClientContext &context, + optional_ptr bind_data) { + return {MultiFileReader::COLUMN_IDENTIFIER_FILE_ROW_NUMBER}; +} + //! The statistics of a column of this file, read from its metadata static unique_ptr ReadSingleParquetFileStatistics(ClientContext &context, const FunctionData *bind_data_p, column_t column_index) { @@ -530,6 +550,7 @@ TableFunction ParquetScanFunction::GetSingleFileFunction() { read_parquet.get_metrics = ReadSingleParquetFileGetMetrics; read_parquet.projection_pushdown = true; read_parquet.late_materialization = true; + read_parquet.get_row_id_columns = ReadSingleParquetFileRowIdColumns; read_parquet.filter_pushdown = true; read_parquet.filter_prune = true; ParquetScanFunction::AddNamedParameters(read_parquet); diff --git a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp index 3679ee03b..e7579b4ff 100644 --- a/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp +++ b/src/duckdb/extension/parquet/reader/row_number_column_reader.cpp @@ -31,6 +31,15 @@ RowNumberColumnReader::RowNumberColumnReader(const ParquetReader &reader, const void RowNumberColumnReader::InitializeRead(idx_t row_group_idx_p, idx_t row_group_num_rows, const vector &columns, TProtocol &protocol_p) { + // the row numbers are produced from the row count without reading a column chunk - every column chunk has a value + // (or NULL) per row, so it cannot have fewer values than the row group has rows + for (auto &column : columns) { + if (column.__isset.meta_data && column.meta_data.num_values < NumericCast(row_group_num_rows)) { + throw DataCorruptionException( + "Failed to read file \"%s\": metadata is corrupt. Column has %lld values but row group has %llu rows", + reader.GetFileName(), column.meta_data.num_values, row_group_num_rows); + } + } row_group_offset = 0; auto &row_groups = reader.GetFileMetadata()->row_groups; for (idx_t i = 0; i < row_group_idx_p; i++) { diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index c1e9f6bda..39677e583 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48043"; + descriptor->api_version = "v2.0.0-alpha48178"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48043"; + descriptor->api_version = "v2.0.0-alpha48178"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48043"; + descriptor->api_version = "v2.0.0-alpha48178"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48043"; + descriptor->api_version = "v2.0.0-alpha48178"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp index fbccd7aea..a3233f0e5 100644 --- a/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/duck_table_entry.cpp @@ -281,6 +281,13 @@ unique_ptr DuckTableEntry::GetSample() { return storage->GetSample(); } +//! The index of the column name in a column reference of a table expression - the names before it qualify the table, +//! the names after it are struct fields +static idx_t ColumnNameIndex(const ColumnRefExpression &colref, const Identifier &table_name) { + auto &names = colref.ColumnNames(); + return names.size() > 1 && names[0] == table_name ? 1 : 0; +} + unique_ptr DuckTableEntry::AlterEntry(CatalogTransaction transaction, AlterInfo &info) { if (transaction.HasContext()) { return AlterEntry(transaction.GetContext(), info); @@ -329,6 +336,19 @@ unique_ptr DuckTableEntry::AlterEntry(ClientContext &context, Alte auto &rename_info = table_info.Cast(); auto copied_table = Copy(context); copied_table->name = rename_info.new_table_name; + // CHECK constraints can qualify columns with the table name + for (auto &constraint : copied_table->Cast().constraints) { + if (constraint->type != ConstraintType::CHECK) { + continue; + } + auto &check = constraint->Cast(); + ParsedExpressionIterator::VisitExpressionMutable( + *check.expression, [&](ColumnRefExpression &colref) { + if (ColumnNameIndex(colref, name) == 1) { + colref.ColumnNamesMutable()[0] = rename_info.new_table_name; + } + }); + } return copied_table; } case AlterTableType::ADD_COLUMN: { @@ -403,10 +423,11 @@ void DuckTableEntry::UndoAlter(ClientContext &context, AlterInfo &info) { } } -static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info) { +static void RenameExpression(ParsedExpression &root_expr, RenameColumnInfo &info, const Identifier &table_name) { ParsedExpressionIterator::VisitExpressionMutable(root_expr, [&](ColumnRefExpression &colref) { - if (colref.ColumnNames().back() == info.old_name) { - colref.ColumnNamesMutable().back() = info.new_name; + auto column_idx = ColumnNameIndex(colref, table_name); + if (colref.ColumnNames()[column_idx] == info.old_name) { + colref.ColumnNamesMutable()[column_idx] = info.new_name; } }); } @@ -449,7 +470,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re copy.SetName(info.new_name); } if (col.Generated() && column_dependency_manager.IsDependencyOf(col.Logical(), rename_idx)) { - RenameExpression(copy.GeneratedExpressionMutable(), info); + RenameExpression(copy.GeneratedExpressionMutable(), info, name); } create_info->columns.AddColumn(std::move(copy)); } @@ -462,7 +483,7 @@ unique_ptr DuckTableEntry::RenameColumn(ClientContext &context, Re case ConstraintType::CHECK: { // CHECK constraint: need to rename column references that refer to the renamed column auto &check = copy->Cast(); - RenameExpression(*check.expression, info); + RenameExpression(*check.expression, info, name); break; } case ConstraintType::UNIQUE: { @@ -860,12 +881,9 @@ unique_ptr DuckTableEntry::RemoveColumn(ClientContext &context, Re if (!removed_columns.empty() && !info.cascade) { throw CatalogException("Cannot drop column: column is a dependency of 1 or more generated column(s)"); } - bool dropped_column_is_generated = false; + bool dropped_column_is_generated = columns.GetColumn(LogicalIndex(removed_index)).Generated(); for (auto &col : columns.Logical()) { if (col.Logical() == removed_index || removed_columns.count(col.Logical())) { - if (col.Generated()) { - dropped_column_is_generated = true; - } continue; } create_info->columns.AddColumn(col.Copy()); diff --git a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp index fec16d5d2..95c870b57 100644 --- a/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp +++ b/src/duckdb/src/catalog/catalog_entry/sequence_catalog_entry.cpp @@ -6,6 +6,7 @@ #include "duckdb/parser/parsed_data/create_sequence_info.hpp" #include "duckdb/catalog/dependency_manager.hpp" #include "duckdb/common/operator/add.hpp" +#include "duckdb/common/operator/subtract.hpp" #include "duckdb/transaction/duck_transaction.hpp" #include @@ -113,6 +114,22 @@ void SequenceCatalogEntry::ReplayValue(uint64_t v_usage_count, int64_t v_counter } } +void SequenceCatalogEntry::DeriveLastValue() { + lock_guard seqlock(lock); + if (data.last_value || data.usage_count == 0) { + return; + } + int64_t last_value; + if (!TrySubtractOperator::Operation(data.counter, data.increment, last_value)) { + return; + } + // a counter that wrapped around in a cycle does not tell us the last value + if (last_value < data.min_value || last_value > data.max_value) { + return; + } + data.last_value = last_value; +} + unique_ptr SequenceCatalogEntry::GetInfo() const { auto seq_data = GetData(); diff --git a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp index 74e25516b..c146cfb41 100644 --- a/src/duckdb/src/common/allocator/allocator_jemalloc.cpp +++ b/src/duckdb/src/common/allocator/allocator_jemalloc.cpp @@ -75,6 +75,23 @@ optional_idx Allocator::DecayDelay() { return NumericCast(DUCKDB_JEMALLOC_DECAY); } +optional_idx Allocator::GetMappedMemory() const { + if (allocate_function != DefaultAllocate || free_function != DefaultFree || + reallocate_function != DefaultReallocate) { + return optional_idx(); + } + uint64_t epoch = 1; + if (duckdb_je_mallctl("epoch", nullptr, nullptr, &epoch, sizeof(epoch)) != 0) { + return optional_idx(); + } + size_t mapped = 0; + size_t mapped_size = sizeof(mapped); + if (duckdb_je_mallctl("stats.mapped", &mapped, &mapped_size, nullptr, 0) != 0) { + return optional_idx(); + } + return optional_idx(mapped); +} + bool Allocator::SupportsFlush() { return true; } diff --git a/src/duckdb/src/common/allocator/allocator_standard.cpp b/src/duckdb/src/common/allocator/allocator_standard.cpp index da6a959c8..d74816ed1 100644 --- a/src/duckdb/src/common/allocator/allocator_standard.cpp +++ b/src/duckdb/src/common/allocator/allocator_standard.cpp @@ -37,6 +37,10 @@ optional_idx Allocator::DecayDelay() { return optional_idx(); } +optional_idx Allocator::GetMappedMemory() const { + return optional_idx(); +} + bool Allocator::SupportsFlush() { #if defined(__GLIBC__) return true; diff --git a/src/duckdb/src/common/bind_helpers.cpp b/src/duckdb/src/common/bind_helpers.cpp index c20b8bbe2..74bfde8da 100644 --- a/src/duckdb/src/common/bind_helpers.cpp +++ b/src/duckdb/src/common/bind_helpers.cpp @@ -9,6 +9,7 @@ #include "duckdb/planner/binder.hpp" #include "duckdb/planner/bound_result_modifier.hpp" #include "duckdb/planner/expression_iterator.hpp" +#include "duckdb/planner/expression_binder/relation_binder.hpp" #include "duckdb/planner/expression/bound_reference_expression.hpp" #include "duckdb/main/config.hpp" @@ -139,10 +140,12 @@ vector BindOrderByNodes(Binder &binder, TableIndex table_index vector &orders) { auto child_binder = Binder::CreateBinder(binder.context, &binder); child_binder->bind_context.AddGenericBinding(table_index, alias, names, types); - ExpressionBinder expr_binder(*child_binder, binder.context); + RelationBinder expr_binder(*child_binder, binder.context, "ORDER_BY"); vector bound_orders; for (auto &order : orders) { - bound_orders.emplace_back(order.type, order.null_order, expr_binder.Bind(order.expression)); + auto expression = expr_binder.Bind(order.expression); + ExpressionBinder::PushCollation(binder.context, expression, expression->GetReturnType()); + bound_orders.emplace_back(order.type, order.null_order, std::move(expression)); } return bound_orders; } diff --git a/src/duckdb/src/common/enum_util.cpp b/src/duckdb/src/common/enum_util.cpp index 652402efc..3c57cc018 100644 --- a/src/duckdb/src/common/enum_util.cpp +++ b/src/duckdb/src/common/enum_util.cpp @@ -163,6 +163,7 @@ #include "duckdb/main/extension.hpp" #include "duckdb/main/extension_helper.hpp" #include "duckdb/main/extension_install_info.hpp" +#include "duckdb/main/http/http_retry_budget.hpp" #include "duckdb/main/http/http_util.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" #include "duckdb/main/secret/secret.hpp" @@ -2279,19 +2280,20 @@ const StringUtil::EnumStringLiteral *GetExpressionTailTypeValues() { { static_cast(ExpressionTailType::IS_TEST), "IS_TEST" }, { static_cast(ExpressionTailType::DISTINCT), "DISTINCT" }, { static_cast(ExpressionTailType::COMPARISON), "COMPARISON" }, - { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" } + { static_cast(ExpressionTailType::OTHER_OPERATOR), "OTHER_OPERATOR" }, + { static_cast(ExpressionTailType::CAST), "CAST" } }; return values; } template<> const char* EnumUtil::ToChars(ExpressionTailType value) { - return StringUtil::EnumToString(GetExpressionTailTypeValues(), 4, "ExpressionTailType", static_cast(value)); + return StringUtil::EnumToString(GetExpressionTailTypeValues(), 5, "ExpressionTailType", static_cast(value)); } template<> ExpressionTailType EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 4, "ExpressionTailType", value)); + return static_cast(StringUtil::StringToEnum(GetExpressionTailTypeValues(), 5, "ExpressionTailType", value)); } const StringUtil::EnumStringLiteral *GetExpressionTypeValues() { @@ -2781,19 +2783,20 @@ const StringUtil::EnumStringLiteral *GetFunctionCollationHandlingValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(FunctionCollationHandling::PROPAGATE_COLLATIONS), "PROPAGATE_COLLATIONS" }, { static_cast(FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS), "PUSH_COMBINABLE_COLLATIONS" }, - { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" } + { static_cast(FunctionCollationHandling::IGNORE_COLLATIONS), "IGNORE_COLLATIONS" }, + { static_cast(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS), "PUSH_DISTINCT_COLLATIONS" } }; return values; } template<> const char* EnumUtil::ToChars(FunctionCollationHandling value) { - return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", static_cast(value)); + return StringUtil::EnumToString(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", static_cast(value)); } template<> FunctionCollationHandling EnumUtil::FromString(const char *value) { - return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 3, "FunctionCollationHandling", value)); + return static_cast(StringUtil::StringToEnum(GetFunctionCollationHandlingValues(), 4, "FunctionCollationHandling", value)); } const StringUtil::EnumStringLiteral *GetFunctionErrorsValues() { @@ -3037,6 +3040,24 @@ HTTPClientCachePolicy EnumUtil::FromString(const char *va return static_cast(StringUtil::StringToEnum(GetHTTPClientCachePolicyValues(), 2, "HTTPClientCachePolicy", value)); } +const StringUtil::EnumStringLiteral *GetHTTPRetryOutcomeOwnerValues() { + static constexpr StringUtil::EnumStringLiteral values[] { + { static_cast(HTTPRetryOutcomeOwner::STATUS), "STATUS" }, + { static_cast(HTTPRetryOutcomeOwner::COORDINATOR), "COORDINATOR" } + }; + return values; +} + +template<> +const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value) { + return StringUtil::EnumToString(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", static_cast(value)); +} + +template<> +HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value) { + return static_cast(StringUtil::StringToEnum(GetHTTPRetryOutcomeOwnerValues(), 2, "HTTPRetryOutcomeOwner", value)); +} + const StringUtil::EnumStringLiteral *GetHTTPStatusCodeValues() { static constexpr StringUtil::EnumStringLiteral values[] { { static_cast(HTTPStatusCode::INVALID), "INVALID" }, diff --git a/src/duckdb/src/common/exception.cpp b/src/duckdb/src/common/exception.cpp index 185bfe4f3..3ca99bdfb 100644 --- a/src/duckdb/src/common/exception.cpp +++ b/src/duckdb/src/common/exception.cpp @@ -74,7 +74,6 @@ bool Exception::InvalidatesTransaction(ExceptionType exception_type) { bool Exception::InvalidatesDatabase(ExceptionType exception_type) { switch (exception_type) { - case ExceptionType::DATA_CORRUPTION: case ExceptionType::FATAL: return true; default: diff --git a/src/duckdb/src/common/file_buffer.cpp b/src/duckdb/src/common/file_buffer.cpp index f86e3fb40..642adcb5a 100644 --- a/src/duckdb/src/common/file_buffer.cpp +++ b/src/duckdb/src/common/file_buffer.cpp @@ -113,6 +113,12 @@ void FileBuffer::Resize(BlockManager &block_manager) { void FileBuffer::Read(QueryContext context, FileHandle &handle, uint64_t location) { D_ASSERT(type != FileBufferType::TINY_BUFFER); + if (handle.file_system.IsLocalFileSystem()) { + // Pre-fault local read buffers before the kernel fills them. + for (idx_t offset = 0; offset < internal_size; offset += 4096) { + internal_buffer[offset] = 0; + } + } handle.Read(context, internal_buffer, internal_size, location); } diff --git a/src/duckdb/src/common/gzip_file_system.cpp b/src/duckdb/src/common/gzip_file_system.cpp index cfd6eae5b..a154be2a1 100644 --- a/src/duckdb/src/common/gzip_file_system.cpp +++ b/src/duckdb/src/common/gzip_file_system.cpp @@ -238,8 +238,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { } state_bytes_remaining = NumericCast((uint8_t)gzip_xlen[0] | (uint8_t)gzip_xlen[1] << 8); if (gzip_header_size + state_bytes_remaining >= GZIP_HEADER_MAXSIZE) { - throw InternalException("Extra field resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw DataCorruptionException("Extra field resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } read_state = GZipReadState::EXTRA_DATA; break; @@ -265,8 +265,8 @@ bool MiniZStreamWrapper::ReadNextMemberHeader(StreamData &sd) { auto c = *sd.in_buff_start++; gzip_header_size++; if (gzip_header_size >= GZIP_HEADER_MAXSIZE) { - throw InternalException("Filename resulting in GZIP header larger than defined maximum (%d)", - GZIP_HEADER_MAXSIZE); + throw DataCorruptionException("Filename resulting in GZIP header larger than defined maximum (%d)", + GZIP_HEADER_MAXSIZE); } if (c == '\0') { read_state = GZipReadState::DEFLATE; diff --git a/src/duckdb/src/common/hive_partitioning.cpp b/src/duckdb/src/common/hive_partitioning.cpp index b52f1ddf6..c103ab2e9 100644 --- a/src/duckdb/src/common/hive_partitioning.cpp +++ b/src/duckdb/src/common/hive_partitioning.cpp @@ -29,7 +29,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f auto &column_map = filter_info.column_map; if (filter_info.filename_enabled) { - auto lookup_column_id = column_map.find("filename"); + auto lookup_column_id = column_map.find(filter_info.filename_column); if (lookup_column_id != column_map.end()) { result.insert(make_pair(lookup_column_id->second, PartitioningColumnValue(filename))); } @@ -38,7 +38,7 @@ GetKnownColumnValues(const string &filename, const HivePartitioningFilterInfo &f if (filter_info.hive_enabled) { auto partitions = HivePartitioning::Parse(filename); for (auto &partition : partitions) { - auto lookup_column_id = column_map.find(partition.first); + auto lookup_column_id = column_map.find(Identifier(partition.first)); if (lookup_column_id != column_map.end()) { result.insert( make_pair(lookup_column_id->second, PartitioningColumnValue(partition.first, partition.second))); diff --git a/src/duckdb/src/common/local_file_system.cpp b/src/duckdb/src/common/local_file_system.cpp index 87ba23c52..664659e76 100644 --- a/src/duckdb/src/common/local_file_system.cpp +++ b/src/duckdb/src/common/local_file_system.cpp @@ -1285,7 +1285,12 @@ unique_ptr LocalFileSystem::OpenFile(const string &path_p, FileOpenF HANDLE hFile = CreateFileW(unicode_path.c_str(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL); if (hFile == INVALID_HANDLE_VALUE) { - if (flags.ReturnNullIfNotExists() && GetLastError() == ERROR_FILE_NOT_FOUND) { + auto last_error = GetLastError(); + if (flags.ReturnNullIfNotExists() && last_error == ERROR_FILE_NOT_FOUND) { + return nullptr; + } + // Match POSIX EEXIST handling for exclusive creation. + if (flags.ReturnNullIfExists() && (last_error == ERROR_FILE_EXISTS || last_error == ERROR_ALREADY_EXISTS)) { return nullptr; } auto error = LocalFileSystem::GetLastErrorAsString(); diff --git a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp index 589923fcd..af5b3c272 100644 --- a/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_column_mapper.cpp @@ -934,16 +934,51 @@ static unique_ptr CreateStructExtractExpression(unique_ptrsecond->index.GetIndex() != i || + !(source_children[i].first == target_children[i].first) || + !MappedCastIsInvertible(*entry->second, source_children[i].second, target_children[i].second)) { + return false; + } + } + return true; +} + +static bool TryCastConstant(Value &constant, const MultiFileIndexMapping &mapping, const LogicalType &target_type) { + // this filter replaces the global one, so both casts must preserve which rows match + if (!BoundCastExpression::CastIsInvertible(constant.type(), target_type) || + !MappedCastIsInvertible(mapping, target_type, constant.type())) { return false; } auto cast = constant.DefaultTryCastAs(target_type); if (!cast) { return false; } + auto roundtrip = cast->DefaultTryCastAs(constant.type()); + if (!roundtrip || *roundtrip != constant) { + return false; + } constant = std::move(*cast); return true; } @@ -992,13 +1027,14 @@ static RewrittenMappedExpression RewriteMappedValueExpression(const Expression & } static unique_ptr RewriteDynamicFilterExpression(const shared_ptr &filter_data, + const MultiFileIndexMapping &mapping, const LogicalType &target_type) { if (!filter_data || !filter_data->initialized) { return nullptr; } - lock_guard lock(filter_data->lock); + annotated_shared_lock lock(filter_data->lock); auto new_constant = filter_data->constant; - if (!TryCastConstant(new_constant, target_type)) { + if (!TryCastConstant(new_constant, mapping, target_type)) { return nullptr; } return BoundComparisonExpression::Create(filter_data->comparison_type, CreateReferenceExpression(target_type), @@ -1031,7 +1067,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto &data = func.BindInfo()->Cast(); - return RewriteDynamicFilterExpression(data.filter_data, target_type); + return RewriteDynamicFilterExpression(data.filter_data, mapping, target_type); } if (!BoundComparisonExpression::IsComparison(expr)) { return nullptr; @@ -1047,7 +1083,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = right.Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { return nullptr; } return BoundComparisonExpression::Create(comparison.GetExpressionType(), std::move(lhs.expr), @@ -1096,7 +1132,7 @@ static unique_ptr TryCastFilterExpression(const Expression &expr, co return nullptr; } auto constant = op.GetChildren()[i]->Cast().GetValue(); - if (!TryCastConstant(constant, *lhs.type)) { + if (!TryCastConstant(constant, *lhs.mapping, *lhs.type)) { return nullptr; } result->GetChildrenMutable().push_back(make_uniq(std::move(constant))); diff --git a/src/duckdb/src/common/multi_file/multi_file_list.cpp b/src/duckdb/src/common/multi_file/multi_file_list.cpp index cbabe9939..38ecf87cb 100644 --- a/src/duckdb/src/common/multi_file/multi_file_list.cpp +++ b/src/duckdb/src/common/multi_file/multi_file_list.cpp @@ -30,10 +30,11 @@ bool PushdownInternal(ClientContext &context, const MultiFileOptions &options, M if (IsVirtualColumn(info.column_ids[i])) { continue; } - filter_info.column_map.insert({info.column_names[info.column_ids[i]].GetIdentifierName(), i}); + filter_info.column_map.insert({info.column_names[info.column_ids[i]], i}); } filter_info.hive_enabled = options.hive_partitioning; filter_info.filename_enabled = options.filename; + filter_info.filename_column = Identifier(options.filename_column); auto start_files = expanded_files.size(); HivePartitioning::ApplyFiltersToFileList(context, expanded_files, filters, filter_info, info); diff --git a/src/duckdb/src/common/opener_file_system.cpp b/src/duckdb/src/common/opener_file_system.cpp index 5c3508a52..617947828 100644 --- a/src/duckdb/src/common/opener_file_system.cpp +++ b/src/duckdb/src/common/opener_file_system.cpp @@ -45,8 +45,7 @@ void OpenerFileSystem::VerifyCanAccessFileInternal(const string &path, FileType return; } auto &config = db->config; - auto canonical_path = config.file_system->CanonicalizePath(path, opener); - if (!config.CanAccessFile(canonical_path, type)) { + if (!config.CanAccessFile(path, type, opener)) { throw PermissionException("Cannot access %s \"%s\" - file system operations are disabled by configuration", type == FileType::FILE_TYPE_DIR ? "directory" : "file", path); } diff --git a/src/duckdb/src/common/operator/cast_operators.cpp b/src/duckdb/src/common/operator/cast_operators.cpp index c4282dc0c..95d7828e9 100644 --- a/src/duckdb/src/common/operator/cast_operators.cpp +++ b/src/duckdb/src/common/operator/cast_operators.cpp @@ -2187,9 +2187,9 @@ struct HugeIntCastData { idx_t digits; ResultType decimal; - uint16_t decimal_total_digits; + idx_t decimal_total_digits; ResultType decimal_intermediate; - uint16_t decimal_intermediate_digits; + idx_t decimal_intermediate_digits; bool Flush() { if (digits == 0 && intermediate == 0) { @@ -2319,7 +2319,7 @@ struct HugeIntegerCastOperation { remainder = negate_result; } state.decimal = remainder; - state.decimal_total_digits = static_cast(-e); + state.decimal_total_digits = static_cast(-e); state.decimal_intermediate = 0; state.decimal_intermediate_digits = 0; return Finalize(state); @@ -2338,16 +2338,19 @@ struct HugeIntegerCastOperation { return Finalize(state); } - e = exponent - state.decimal_total_digits; - if (e < 0) { - if (e < -38) { - return false; + const auto shift = static_cast(exponent) - static_cast(state.decimal_total_digits); + if (shift < 0) { + if (shift < -38) { + // all of decimal is fractional + remainder = state.decimal; + state.decimal = 0; + } else { + state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-shift], remainder); } - state.decimal = T::Operation::DivMod(state.decimal, T::Operation::POWERS_OF_TEN[-e], remainder); - state.decimal_total_digits -= (exponent); + state.decimal_total_digits -= static_cast(exponent); } else { - if (e > 38 || - !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[e], state.decimal)) { + if (shift > 38 || + !TryMultiplyOperator::Operation(state.decimal, T::Operation::POWERS_OF_TEN[shift], state.decimal)) { return false; } } @@ -2368,6 +2371,11 @@ struct HugeIntegerCastOperation { if (!state.Flush()) { return false; } + // digits past this position cannot affect the result for any int16_t exponent. Ignore them! + static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); + if (DUCKDB_UNLIKELY(state.decimal_total_digits + state.decimal_intermediate_digits == MAX_DECIMAL_DIGITS)) { + return true; + } if (DUCKDB_UNLIKELY(state.decimal_intermediate > (NumericLimits::Maximum() - digit) / 10)) { if (!state.FlushDecimal()) { return false; @@ -2395,7 +2403,7 @@ struct HugeIntegerCastOperation { state.decimal /= T::Operation::POWERS_OF_TEN[MAX_DIGITS]; state.decimal_total_digits -= MAX_DIGITS; } - D_ASSERT((state.decimal_total_digits - 1) >= 0 && (state.decimal_total_digits - 1) <= MAX_DIGITS); + D_ASSERT(state.decimal_total_digits >= 1 && state.decimal_total_digits - 1 <= MAX_DIGITS); state.decimal /= T::Operation::POWERS_OF_TEN[state.decimal_total_digits - 1]; if (state.decimal >= 5) { @@ -3315,6 +3323,7 @@ static void FillDecimalDigits(SRC input, duckdb_fast_float::decimal &decimal, bo } static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decimal, bool &negative) { + negative = false; if (input == 0) { return; } @@ -3322,8 +3331,6 @@ static void FillDecimalDigits(hugeint_t input, duckdb_fast_float::decimal &decim if (input < 0) { negative = true; Hugeint::NegateInPlace(input); - } else { - negative = false; } // a hugeint can have one more digit than the maximum decimal width @@ -3346,7 +3353,7 @@ bool TryCastDecimalToFloatingPoint(SRC input, DST &result, uint8_t width, uint8_ } duckdb_fast_float::decimal decimal; - bool negative; + bool negative = false; FillDecimalDigits(input, decimal, negative); decimal.decimal_point = UnsafeNumericCast(decimal.num_digits) - UnsafeNumericCast(scale); while (decimal.num_digits > 0 && decimal.digits[decimal.num_digits - 1] == 0) { diff --git a/src/duckdb/src/common/sort/sort.cpp b/src/duckdb/src/common/sort/sort.cpp index 234e0d94f..5bb566837 100644 --- a/src/duckdb/src/common/sort/sort.cpp +++ b/src/duckdb/src/common/sort/sort.cpp @@ -164,7 +164,7 @@ class SortGlobalSinkState : public GlobalSinkState { } // Double until it fits - auto required = num_threads * lstate.sorted_run->SizeInBytes(); + auto required = num_threads * lstate.sorted_run->SizeInBytesForSorting(); if (is_index_sort) { required *= 4; // Index creation is pretty intense, so we are very conservative here } @@ -225,8 +225,8 @@ unique_ptr Sort::GetGlobalSinkState(ClientContext &context) con //! Returns true if the Sink call is done (either because run size is small or because run was finalized) static bool TryFinishSink(SortGlobalSinkState &gstate, SortLocalSinkState &lstate, unique_lock &guard) { - // Check if we exceed the limit - const auto sorted_run_size = lstate.sorted_run->SizeInBytes(); + // Check if we exceed the limit (sorting the run needs a merge buffer on top of the run itself) + const auto sorted_run_size = lstate.sorted_run->SizeInBytesForSorting(); if (sorted_run_size < lstate.maximum_run_size) { return true; // Sink is done } diff --git a/src/duckdb/src/common/sort/sorted_run.cpp b/src/duckdb/src/common/sort/sorted_run.cpp index d72cc9328..f7eab31cf 100644 --- a/src/duckdb/src/common/sort/sorted_run.cpp +++ b/src/duckdb/src/common/sort/sorted_run.cpp @@ -8,6 +8,7 @@ #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/common/sorting/sort.hpp" #include "duckdb/common/sorting/sort_key.hpp" +#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "vergesort.h" @@ -247,10 +248,16 @@ static void TemplatedSort(ClientContext &context, TupleDataCollection &key_data, auto ska_extract_key = SkaExtractKey(requires_next_sort, ska_sort_width, sort_skippable_bytes, context.interrupt_state); - const auto fallback = [ska_extract_key](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { - duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key); + // vergesort merges the sorted runs it finds with a temporary buffer of up to half the keys - allocate that + // buffer through the buffer allocator instead of letting std::inplace_merge take it from operator new. The same + // buffer serves the merges of the nested vergesort that ska_sort runs over the buckets its radix passes cannot + // order, so that no merge anywhere in this sort allocates on its own (BufferedMerge never does) + SortMergeBuffer merge_buffer(BufferAllocator::Get(context), key_data.Count(), sizeof(SORT_KEY)); + const BufferedMergePolicy merge(merge_buffer); + const auto fallback = [&ska_extract_key, &merge](const BLOCK_ITERATOR &fb_begin, const BLOCK_ITERATOR &fb_end) { + duckdb_ska_sort::ska_sort(fb_begin, fb_end, ska_extract_key, merge); }; - duckdb_vergesort::vergesort(begin, end, std::less(), fallback); + duckdb_vergesort::vergesort(begin, end, std::less(), fallback, merge); context.InterruptCheck(); } @@ -447,4 +454,9 @@ idx_t SortedRun::SizeInBytes() const { return size; } +idx_t SortedRun::SizeInBytesForSorting() const { + // the fixed-size sort keys are the rows of the key layout, so the row width is the key size the merge buffer uses + return SizeInBytes() + SortMergeBuffer::MaximumSize(key_data->Count(), key_data->GetLayout().GetRowWidth()); +} + } // namespace duckdb diff --git a/src/duckdb/src/common/sort/sorted_run_merger.cpp b/src/duckdb/src/common/sort/sorted_run_merger.cpp index 2baecd162..1ddd7c6f0 100644 --- a/src/duckdb/src/common/sort/sorted_run_merger.cpp +++ b/src/duckdb/src/common/sort/sorted_run_merger.cpp @@ -6,6 +6,7 @@ #include "duckdb/common/thread_annotation.hpp" #include "duckdb/common/sorting/sorted_run.hpp" #include "duckdb/common/sorting/sort_key.hpp" +#include "duckdb/common/sorting/buffered_merge.hpp" #include "duckdb/common/types/row/block_iterator.hpp" #include "duckdb/common/types/row/tuple_data_collection.hpp" #include "duckdb/parallel/parallel_destroy_task.hpp" @@ -655,8 +656,12 @@ void SortedRunMergerLocalState::TemplatedMergePartition(SortedRunMergerGlobalSta static const auto fallback = [](SORT_KEY *begin, SORT_KEY *end) { duckdb_pdqsort::pdqsort_branchless(begin, end); }; + // the run merges need a scratch buffer of up to half the keys - take it from the buffer allocator so that it is + // counted against the memory limit instead of coming from operator new inside std::inplace_merge + SortMergeBuffer merge_buffer(BufferAllocator::Get(gstate.context), merged_partition_count, sizeof(SORT_KEY)); + const BufferedMergePolicy merge(merge_buffer); duckdb_vergesort::vergesort(merged_partition_keys, merged_partition_keys + merged_partition_count, - std::less(), fallback); + std::less(), fallback, merge); } void SortedRunMergerLocalState::ScanPartition(SortedRunMergerGlobalState &gstate, DataChunk &chunk) { diff --git a/src/duckdb/src/common/types/bignum.cpp b/src/duckdb/src/common/types/bignum.cpp index 9f4da3623..862e959ae 100644 --- a/src/duckdb/src/common/types/bignum.cpp +++ b/src/duckdb/src/common/types/bignum.cpp @@ -101,29 +101,8 @@ char Bignum::DigitToChar(int digit) { } static bool ShouldRoundDecimalDigits(const char *data, idx_t decimal_start, idx_t decimal_end) { - if (decimal_start >= decimal_end) { - return false; - } - uint64_t decimal = 0; - uint16_t decimal_digits = 0; - for (idx_t pos = decimal_start; pos < decimal_end; pos++) { - auto digit = UnsafeNumericCast(data[pos] - '0'); - if (decimal > (NumericLimits::Maximum() - digit) / 10) { - for (; pos < decimal_end; pos++) { - if (data[pos] != '0') { - return true; - } - } - break; - } - decimal_digits++; - decimal = decimal * 10 + digit; - } - while (decimal > 10) { - decimal /= 10; - decimal_digits--; - } - return decimal_digits == 1 && decimal >= 5; + // the fraction is at least 0.5 exactly when its first digit is at least 5 + return decimal_start < decimal_end && data[decimal_start] >= '5'; } static void IncrementDecimalString(string &digits) { @@ -558,11 +537,8 @@ bool Bignum::BignumToDouble(const bignum_t &blob, double &result, bool &strict) if (is_negative) { result *= -1; } - if (!std::isfinite(result)) { - // We throw an error - throw ConversionException("Could not convert bignum '%s' to Double", BignumToVarchar(blob)); - } - return true; + // the value does not fit in a double + return std::isfinite(result); } } // namespace duckdb diff --git a/src/duckdb/src/common/types/geometry.cpp b/src/duckdb/src/common/types/geometry.cpp index 6c366b727..2e7539f77 100644 --- a/src/duckdb/src/common/types/geometry.cpp +++ b/src/duckdb/src/common/types/geometry.cpp @@ -1018,6 +1018,9 @@ WKBAnalysis AnalyzeWKB(BlobReader &reader) { const auto meta = reader.Read(le); const auto type_id = (meta & 0x0000FFFF) % 1000; const auto flag_id = (meta & 0x0000FFFF) / 1000; + if (flag_id > 3) { + throw InvalidInputException("Unsupported geometry flag %d in WKB", flag_id); + } // Extended WKB detection const auto has_extz = (meta & 0x80000000) != 0; diff --git a/src/duckdb/src/common/types/hugeint.cpp b/src/duckdb/src/common/types/hugeint.cpp index 79a353a6e..b98611ddb 100644 --- a/src/duckdb/src/common/types/hugeint.cpp +++ b/src/duckdb/src/common/types/hugeint.cpp @@ -89,42 +89,6 @@ hugeint_t Hugeint::Abs(hugeint_t n) { // Divide //===--------------------------------------------------------------------===// -static uint8_t PositiveHugeintHighestBit(hugeint_t bits) { - uint8_t out = 0; - if (bits.upper) { - out = 64; - uint64_t up = static_cast(bits.upper); - while (up) { - up >>= 1; - out++; - } - } else { - uint64_t low = bits.lower; - while (low) { - low >>= 1; - out++; - } - } - return out; -} - -static bool PositiveHugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { - if (bit_position < 64) { - return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); - } else { - return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); - } -} - -static hugeint_t PositiveHugeintLeftShift(hugeint_t lhs, uint32_t amount) { - D_ASSERT(amount > 0 && amount < 64); - hugeint_t result; - result.lower = lhs.lower << amount; - result.upper = - UnsafeNumericCast((UnsafeNumericCast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); - return result; -} - hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remainder) { D_ASSERT(lhs.upper >= 0); // DivMod code adapted from: @@ -136,14 +100,14 @@ hugeint_t Hugeint::DivModPositive(hugeint_t lhs, uint64_t rhs, uint64_t &remaind div_result.upper = 0; remainder = 0; - uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); + uint8_t highest_bit_set = HugeintHighestBit(lhs); // now iterate over the amount of bits that are set in the LHS for (uint8_t x = highest_bit_set; x > 0; x--) { // left-shift the current result and remainder by 1 - div_result = PositiveHugeintLeftShift(div_result, 1); + div_result = HugeintLeftShift(div_result, 1); remainder <<= 1; // we get the value of the bit at position X, where position 0 is the least-significant bit - if (PositiveHugeintIsBitSet(lhs, x - 1)) { + if (HugeintIsBitSet(lhs, x - 1)) { // increment the remainder remainder++; } @@ -303,172 +267,18 @@ bool Hugeint::TryMultiply(hugeint_t lhs, hugeint_t rhs, hugeint_t &result) { // Multiply without overflow check template <> hugeint_t Hugeint::Multiply(hugeint_t lhs, hugeint_t rhs) { - hugeint_t result; - bool lhs_negative = lhs.upper < 0; - bool rhs_negative = rhs.upper < 0; - if (lhs_negative) { - NegateInPlace(lhs); - } - if (rhs_negative) { - NegateInPlace(rhs); - } - -#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) - __uint128_t left = __uint128_t(lhs.lower) + (__uint128_t(lhs.upper) << 64); - __uint128_t right = __uint128_t(rhs.lower) + (__uint128_t(rhs.upper) << 64); - __uint128_t result_i128; - result_i128 = left * right; - uint64_t upper = uint64_t(result_i128 >> 64); - result.upper = int64_t(upper); - result.lower = uint64_t(result_i128 & 0xffffffffffffffff); -#else - // Multiply code adapted from: - // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - - // split values into 4 32-bit parts - uint64_t top[4] = {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, - lhs.lower & 0xffffffff}; - uint64_t bottom[4] = {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, - rhs.lower & 0xffffffff}; - uint64_t products[4][4]; - - // multiply each component of the values - for (auto x = 0; x < 4; x++) { - for (auto y = 0; y < 4; y++) { - products[x][y] = top[x] * bottom[y]; - } - } - - // first row - uint64_t fourth32 = (products[3][3] & 0xffffffff); - uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); - uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); - uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); - - // second row - third32 += (products[2][3] & 0xffffffff); - second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); - first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); - - // third row - second32 += (products[1][3] & 0xffffffff); - first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); - - // fourth row - first32 += (products[0][3] & 0xffffffff); - - // move carry to next digit - third32 += fourth32 >> 32; - second32 += third32 >> 32; - first32 += second32 >> 32; - - // remove carry from current digit - fourth32 &= 0xffffffff; - third32 &= 0xffffffff; - second32 &= 0xffffffff; - first32 &= 0xffffffff; - - // combine components - result.lower = (third32 << 32) | fourth32; - result.upper = (first32 << 32) | second32; -#endif - if (lhs_negative ^ rhs_negative) { - NegateInPlace(result); - } - return result; + return HugeintMultiplyUnchecked(lhs, rhs); } //===--------------------------------------------------------------------===// // Divide //===--------------------------------------------------------------------===// -int Sign(hugeint_t n) { - return ((n > 0) - (n < 0)); -} - -hugeint_t Abs(hugeint_t n) { - D_ASSERT(n != NumericLimits::Minimum()); - return (n * Sign(n)); -} - -static hugeint_t DivModMinimum(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { - D_ASSERT(lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()); - if (rhs == NumericLimits::Minimum()) { - if (lhs == NumericLimits::Minimum()) { - remainder = 0; - return 1; - } - remainder = lhs; - return 0; - } - - // Add 1 to minimum and run through DivMod again - hugeint_t result = Hugeint::DivMod(NumericLimits::Minimum() + 1, rhs, remainder); - - // If the 1 mattered we need to adjust the result, otherwise the remainder - if (Abs(remainder) + 1 == Abs(rhs)) { - result -= Sign(rhs); - remainder = 0; - } else { - remainder -= 1; - } - return result; -} - // No overflow checks hugeint_t Hugeint::DivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &remainder) { - if (rhs == 0) { - remainder = lhs; - return hugeint_t(0); - } - - // Check if one of the sides is hugeint_t minimum, as that can't be negated. - if (lhs == NumericLimits::Minimum() || rhs == NumericLimits::Minimum()) { - return DivModMinimum(lhs, rhs, remainder); - } - - bool lhs_negative = lhs.upper < 0; - bool rhs_negative = rhs.upper < 0; - if (lhs_negative) { - Hugeint::NegateInPlace(lhs); - } - if (rhs_negative) { - Hugeint::NegateInPlace(rhs); - } - // DivMod code adapted from: - // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - - // initialize the result and remainder to 0 - hugeint_t div_result; - div_result.lower = 0; - div_result.upper = 0; - remainder.lower = 0; - remainder.upper = 0; - - uint8_t highest_bit_set = PositiveHugeintHighestBit(lhs); - // now iterate over the amount of bits that are set in the LHS - for (uint8_t x = highest_bit_set; x > 0; x--) { - // left-shift the current result and remainder by 1 - div_result = PositiveHugeintLeftShift(div_result, 1); - remainder = PositiveHugeintLeftShift(remainder, 1); - - // we get the value of the bit at position X, where position 0 is the least-significant bit - if (PositiveHugeintIsBitSet(lhs, x - 1)) { - remainder += 1; - } - if (Hugeint::GreaterThanEquals(remainder, rhs)) { - // the remainder has passed the division multiplier: add one to the divide result - remainder -= rhs; - div_result += 1; - } - } - if (lhs_negative ^ rhs_negative) { - Hugeint::NegateInPlace(div_result); - } - if (lhs_negative) { - Hugeint::NegateInPlace(remainder); - } - return div_result; + auto result = HugeintDivMod(lhs, rhs); + remainder = result.remainder; + return result.quotient; } bool Hugeint::TryDivMod(hugeint_t lhs, hugeint_t rhs, hugeint_t &result, hugeint_t &remainder) { @@ -494,9 +304,7 @@ hugeint_t Hugeint::Divide(hugeint_t lhs, hugeint_t rhs) { template <> hugeint_t Hugeint::Modulo(hugeint_t lhs, hugeint_t rhs) { - hugeint_t remainder; - (void)Hugeint::DivMod(lhs, rhs, remainder); - return remainder; + return HugeintDivMod(lhs, rhs).remainder; } //===--------------------------------------------------------------------===// @@ -773,62 +581,11 @@ bool Hugeint::TryConvert(long double value, hugeint_t &result) { //===--------------------------------------------------------------------===// // hugeint_t operators //===--------------------------------------------------------------------===// -hugeint_t::hugeint_t(int64_t value) { - auto result = Hugeint::Convert(value); - this->lower = result.lower; - this->upper = result.upper; -} - -bool hugeint_t::operator==(const hugeint_t &rhs) const { - return Hugeint::Equals(*this, rhs); -} - -bool hugeint_t::operator!=(const hugeint_t &rhs) const { - return Hugeint::NotEquals(*this, rhs); -} - -bool hugeint_t::operator<(const hugeint_t &rhs) const { - return Hugeint::LessThan(*this, rhs); -} - -bool hugeint_t::operator<=(const hugeint_t &rhs) const { - return Hugeint::LessThanEquals(*this, rhs); -} - -bool hugeint_t::operator>(const hugeint_t &rhs) const { - return Hugeint::GreaterThan(*this, rhs); -} - -bool hugeint_t::operator>=(const hugeint_t &rhs) const { - return Hugeint::GreaterThanEquals(*this, rhs); -} - -hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { - return hugeint_t(upper + rhs.upper + ((lower + rhs.lower) < lower), lower + rhs.lower); -} - -hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { - return hugeint_t(upper - rhs.upper - ((lower - rhs.lower) > lower), lower - rhs.lower); -} - -hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { - hugeint_t result = *this; - result *= rhs; - return result; -} - -hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { - return Hugeint::Divide(*this, rhs); -} hugeint_t hugeint_t::operator%(const hugeint_t &rhs) const { return Hugeint::Modulo(*this, rhs); } -hugeint_t hugeint_t::operator-() const { - return Hugeint::Negate(*this); -} - hugeint_t hugeint_t::operator>>(const hugeint_t &rhs) const { hugeint_t result; uint64_t shift = rhs.lower; diff --git a/src/duckdb/src/common/types/interval.cpp b/src/duckdb/src/common/types/interval.cpp index be943a4de..f208bf137 100644 --- a/src/duckdb/src/common/types/interval.cpp +++ b/src/duckdb/src/common/types/interval.cpp @@ -167,10 +167,10 @@ interval_parse_time : { // parse the remainder of the time as a Time type dtime_t time; idx_t pos; - if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time)) { + if (!Time::TryConvertInterval(str + start_pos, len - start_pos, pos, time, false, nullptr, negative)) { return false; } - if (!IntervalTryAddition(result.micros, negative ? -time.value : time.value, 1, error_message)) { + if (!IntervalTryAddition(result.micros, time.value, 1, error_message)) { return false; } found_any = true; diff --git a/src/duckdb/src/common/types/time.cpp b/src/duckdb/src/common/types/time.cpp index 34ebec740..04396b7a1 100644 --- a/src/duckdb/src/common/types/time.cpp +++ b/src/duckdb/src/common/types/time.cpp @@ -5,7 +5,9 @@ #include "duckdb/common/types/date.hpp" #include "duckdb/common/types/interval.hpp" #include "duckdb/common/types/timestamp.hpp" +#include "duckdb/common/operator/add.hpp" #include "duckdb/common/operator/multiply.hpp" +#include "duckdb/common/operator/subtract.hpp" #include "duckdb/common/exception/conversion_exception.hpp" #include @@ -19,8 +21,9 @@ static_assert(sizeof(dtime_t) == sizeof(int64_t), "dtime_t was padded"); // ISO 8601 bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos) { - int32_t hour = -1, min = -1, sec = -1, micros = -1; + optional_ptr nanos, bool negative) { + int64_t hour = -1; + int32_t min = -1, sec = -1, micros = -1; pos = 0; if (len == 0) { @@ -42,9 +45,9 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r return false; } - // Allow up to 9 digit hours to support intervals + // Allow up to 10 digit interval hours hour = 0; - for (int32_t digits = 9; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { + for (int32_t digits = 10; pos < len && StringUtil::CharacterIsDigit(buf[pos]); ++pos) { if (digits-- > 0) { hour = hour * 10 + (buf[pos] - '0'); } else { @@ -133,13 +136,25 @@ bool Time::TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &r } } - result = Time::FromTime(hour, min, sec, micros); + // a negative interval accumulates below zero, so its minimum fits + int64_t time_micros; + auto sub_hour_micros = Time::FromTime(0, min, sec, micros).value; + if (negative) { + if (!TryMultiplyOperator::Operation(hour, -Interval::MICROS_PER_HOUR, time_micros) || + !TrySubtractOperator::Operation(time_micros, sub_hour_micros, time_micros)) { + return false; + } + } else if (!TryMultiplyOperator::Operation(hour, Interval::MICROS_PER_HOUR, time_micros) || + !TryAddOperator::Operation(time_micros, sub_hour_micros, time_micros)) { + return false; + } + result = dtime_t(time_micros); return true; } bool Time::TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos) { - return Time::TryConvertInternal(buf, len, pos, result, strict, nanos); + optional_ptr nanos, bool negative) { + return Time::TryConvertInternal(buf, len, pos, result, strict, nanos, negative); } bool Time::TryConvertTime(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, diff --git a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp index 7a372334d..d85fc87dd 100644 --- a/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp +++ b/src/duckdb/src/common/types/variant/parquet_variant_iterator.cpp @@ -623,6 +623,13 @@ ParquetArrayIterator ParquetVariantNode::GetArrayChildren() const { void ParquetObjectIterator::Finalize() { std::sort(ordered_entries.begin(), ordered_entries.end(), [](const ParquetObjectEntry &a, const ParquetObjectEntry &b) { return a.key < b.key; }); + // the keys of a Variant object have to be unique + for (idx_t i = 1; i < ordered_entries.size(); i++) { + if (ordered_entries[i].key == ordered_entries[i - 1].key) { + throw InvalidInputException("Invalid VARIANT: object contains the key \"%s\" more than once", + ordered_entries[i].key.GetString()); + } + } } ParquetObjectIterator::ParquetObjectIterator(const ParquetVariantIterator &state, const ShreddedGroupView &view, diff --git a/src/duckdb/src/common/types/vector_buffer.cpp b/src/duckdb/src/common/types/vector_buffer.cpp index 957712872..751027350 100644 --- a/src/duckdb/src/common/types/vector_buffer.cpp +++ b/src/duckdb/src/common/types/vector_buffer.cpp @@ -338,18 +338,17 @@ void VectorBuffer::Copy(const Vector &source_p, const SelectionVector &source_se finished = true; break; default: { - // for exotic types we flatten followed by copying - only the copied range [source_offset, source_count) - // is flattened, as e.g. list functions copy the children of every list at a different offset - D_ASSERT(source_count >= source_offset); - const auto range_count = source_count - source_offset; - SelectionVector range_sel(range_count); - for (idx_t i = 0; i < range_count; i++) { + // for exotic types we flatten followed by copying - only the copied rows are flattened, as e.g. list + // functions copy the children of every list at a different offset, and the selection vector might only + // cover the copied rows + D_ASSERT(source_count >= source_offset + copy_count); + SelectionVector range_sel(copy_count); + for (idx_t i = 0; i < copy_count; i++) { range_sel.set_index(i, sel.get_index(source_offset + i)); } Vector flattened_vector(Vector::Ref(source)); - flattened_vector.Flatten(range_sel, range_count); - Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), range_count, 0, target_offset, - copy_count); + flattened_vector.Flatten(range_sel, copy_count); + Copy(flattened_vector, *FlatVector::IncrementalSelectionVector(), copy_count, 0, target_offset, copy_count); return; } } diff --git a/src/duckdb/src/common/vector/constant_vector.cpp b/src/duckdb/src/common/vector/constant_vector.cpp index 1343fde9d..706d3830e 100644 --- a/src/duckdb/src/common/vector/constant_vector.cpp +++ b/src/duckdb/src/common/vector/constant_vector.cpp @@ -34,22 +34,60 @@ void ConstantVector::Reference(Vector &vector, const Value &value, count_t count vector.SetBuffer(CreateConstantBuffer(value, count)); } -void ConstantVector::SetNull(Vector &vector, count_t count) { - // try to re-use the buffer if possible +static bool IsReusableBufferType(VectorBufferType buffer_type) { + switch (buffer_type) { + case VectorBufferType::STANDARD_BUFFER: + case VectorBufferType::STRUCT_BUFFER: + case VectorBufferType::ARRAY_BUFFER: + case VectorBufferType::LIST_BUFFER: + case VectorBufferType::STRING_BUFFER: + return true; + default: + return false; + } +} + +//! Whether the buffer of the vector can be turned into a NULL constant in place: it must not be shared with other +//! vectors (e.g. a referenced payload), and the children that are nulled as well must be flat or constant +static bool CanSetNullInPlace(const Vector &vector) { auto &buffer_ref = vector.GetBufferRef(); - bool needs_new_buffer = !buffer_ref; - if (!needs_new_buffer) { - auto buffer_type = buffer_ref->GetBufferType(); - needs_new_buffer = - (buffer_type != VectorBufferType::STANDARD_BUFFER && buffer_type != VectorBufferType::STRUCT_BUFFER && - buffer_type != VectorBufferType::ARRAY_BUFFER && buffer_type != VectorBufferType::LIST_BUFFER && - buffer_type != VectorBufferType::STRING_BUFFER); + if (!buffer_ref || buffer_ref.use_count() > 1) { + return false; + } + switch (buffer_ref->GetBufferType()) { + case VectorBufferType::STANDARD_BUFFER: + case VectorBufferType::LIST_BUFFER: + case VectorBufferType::STRING_BUFFER: + return true; + case VectorBufferType::STRUCT_BUFFER: + for (auto &entry : StructVector::GetEntries(vector)) { + if (!CanSetNullInPlace(entry)) { + return false; + } + } + return true; + case VectorBufferType::ARRAY_BUFFER: { + auto child_type = ArrayVector::GetChild(vector).GetVectorType(); + return child_type == VectorType::FLAT_VECTOR || child_type == VectorType::CONSTANT_VECTOR; } - if (needs_new_buffer) { + default: + return false; + } +} + +void ConstantVector::SetNull(Vector &vector, count_t count) { + auto &buffer_ref = vector.GetBufferRef(); + if (!buffer_ref || !IsReusableBufferType(buffer_ref->GetBufferType())) { // we cannot re-use the buffer - refer a null-value which has code necessary to create a new buffer Reference(vector, Value(vector.GetType()), count); return; } + if (!CanSetNullInPlace(vector)) { + // the buffer is shared with other vectors (or has children that cannot be nulled) - use a fresh buffer + auto capacity = MaxValue(buffer_ref->Capacity(), 1); + vector.Initialize(VectorDataInitialization::UNINITIALIZED, capacity); + } + // re-use the buffer, so that e.g. the reserved capacity of a list child is kept vector.SetVectorType(VectorType::CONSTANT_VECTOR); FlatVector::SetSize(vector, count); SetNull(vector, true); diff --git a/src/duckdb/src/common/vector/list_vector.cpp b/src/duckdb/src/common/vector/list_vector.cpp index 127125e54..124911264 100644 --- a/src/duckdb/src/common/vector/list_vector.cpp +++ b/src/duckdb/src/common/vector/list_vector.cpp @@ -329,7 +329,7 @@ void ListVector::SetListSize(Vector &vec, idx_t size) { if (child_buffer) { auto vtype = child_buffer->GetVectorType(); if ((vtype == VectorType::FLAT_VECTOR || vtype == VectorType::CONSTANT_VECTOR) && - size > child_buffer->Capacity()) { + size > child_buffer->Capacity() && size > child_buffer->Size()) { list_buffer.Reserve(size); } } diff --git a/src/duckdb/src/execution/expression_executor/execute_function.cpp b/src/duckdb/src/execution/expression_executor/execute_function.cpp index 7481fe125..ef1fe0bc2 100644 --- a/src/duckdb/src/execution/expression_executor/execute_function.cpp +++ b/src/duckdb/src/execution/expression_executor/execute_function.cpp @@ -186,6 +186,29 @@ static idx_t SelectBooleanResult(Vector &result, const SelectionVector *sel, idx result, sel, count, [](bool value) { return value; }, true_sel, false_sel); } +static void SetConstantArgumentCardinality(DataChunk &arguments, vector> &buffers) { + buffers.clear(); + for (auto &arg : arguments.data) { + buffers.push_back(arg.GetBufferRef()); + } + arguments.SetChildCardinality(1ULL); +} + +static void RestoreConstantArgumentCardinality(DataChunk &arguments, vector> &buffers, + idx_t count) { + for (auto &arg : arguments.data) { + arg.SetVectorType(VectorType::CONSTANT_VECTOR); + } + arguments.SetChildCardinality(count); + // the function can have replaced an argument's buffer (e.g. by flattening it) + for (auto &buffer : buffers) { + if (buffer) { + buffer->SetVectorSize(count); + } + } + buffers.clear(); +} + static void ExecuteConstantSelectFunction(const BoundFunctionExpression &expr, DataChunk &args, ExpressionState &state, Vector &result) { D_ASSERT(args.size() == 1); @@ -222,15 +245,15 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } } + auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant temporarily set the child cardinality to 1 - arguments.SetChildCardinality(1ULL); + SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); } else { arguments.SetChildCardinality(count); } arguments.Verify(context); - auto &execute_function_state = state->Cast(); auto dictionary_executed = expr.Function().HasFunctionCallback() && !all_constant && execute_function_state.TryExecuteDictionaryExpression(expr, arguments, *state, result); if (!dictionary_executed) { @@ -243,11 +266,7 @@ void ExpressionExecutor::Execute(const BoundFunctionExpression &expr, Expression } } if (all_constant) { - // restore the input cardinality - for (auto &arg : arguments.data) { - arg.SetVectorType(VectorType::CONSTANT_VECTOR); - } - arguments.SetChildCardinality(count); + RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); // ensure the result type is constant result.FlattenAndSetConstant(); } @@ -281,9 +300,10 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression return SelectBooleanResult(result, sel, count, true_sel, false_sel); } } + auto &execute_function_state = state->Cast(); if (all_constant) { // if all arguments are constant we only need to run the function on one value - arguments.SetChildCardinality(1ULL); + SetConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers); } else { arguments.SetChildCardinality(count); } @@ -296,14 +316,9 @@ idx_t ExpressionExecutor::Select(const BoundFunctionExpression &expr, Expression } else { ExecuteConstantSelectFunction(expr, arguments, *state, result); } - // restore the input cardinality - for (auto &arg : arguments.data) { - arg.SetVectorType(VectorType::CONSTANT_VECTOR); - } - arguments.SetChildCardinality(count); + RestoreConstantArgumentCardinality(arguments, execute_function_state.constant_argument_buffers, count); return SelectBooleanResult(result, sel, count, true_sel, false_sel); } - auto &execute_function_state = state->Cast(); if (expr.Function().HasFunctionCallback()) { Vector result(LogicalType::BOOLEAN); auto dictionary_executed = diff --git a/src/duckdb/src/execution/index/fixed_size_buffer.cpp b/src/duckdb/src/execution/index/fixed_size_buffer.cpp index a1f4483ea..457413209 100644 --- a/src/duckdb/src/execution/index/fixed_size_buffer.cpp +++ b/src/duckdb/src/execution/index/fixed_size_buffer.cpp @@ -77,7 +77,11 @@ FixedSizeBuffer::~FixedSizeBuffer() { } if (OnDisk()) { // marking a block as modified decreases the reference count of multi-use blocks - block_manager.MarkBlockAsModified(block_pointer.block_id); + // this must not throw out of a destructor, e.g., if the block ids of a corrupt file overlap + try { + block_manager.MarkBlockAsModified(block_pointer.block_id); + } catch (...) { // NOLINT + } } } diff --git a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp index 3edca9ab2..687f3fb43 100644 --- a/src/duckdb/src/execution/operator/aggregate/physical_window.cpp +++ b/src/duckdb/src/execution/operator/aggregate/physical_window.cpp @@ -518,7 +518,9 @@ class WindowGlobalSourceState : public GlobalSourceState { return !stopped && finished < total_tasks; } //! Get the next task given the current state - bool TryNextTask(TaskPtr &task, Task &task_local); + bool TryNextTask(TaskPtr &task, Task &task_local) DUCKDB_EXCLUDES(lock); + //! Select a task with the lock held, after the previous task has been retired. + bool TryNextTaskLocked(TaskPtr &task, Task &task_local) DUCKDB_REQUIRES(lock); //! Context for executing computations ClientContext &client; @@ -1049,7 +1051,10 @@ bool WindowGlobalSourceState::TryNextTask(TaskPtr &task, Task &task_local) { WindowHashGroupPtr finished_group; annotated_lock_guard guard(lock); finished_group = FinishTask(task); + return TryNextTaskLocked(task, task_local); +} +bool WindowGlobalSourceState::TryNextTaskLocked(TaskPtr &task, Task &task_local) { if (!HasMoreTasks()) { task = nullptr; return false; @@ -1294,11 +1299,15 @@ SourceResultType PhysicalWindow::GetDataInternal(ExecutionContext &context, Data // no more tasks - exit gsource.UnblockTasks(); break; - } else { - // there are more tasks available, but we can't execute them yet - // block the source - return gsource.BlockSource(source.interrupt_state); } + // TryAssignTask released the lock: another thread may have prepared the next stage and unblocked the + // blocked tasks in between, so look again before blocking, or no one would ever unblock this task. + if (gsource.TryNextTaskLocked(lsource.task, lsource.task_local)) { + continue; + } + // there are more tasks available, but we can't execute them yet + // block the source + return gsource.BlockSource(source.interrupt_state); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp index dfec932ad..96773a018 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/scanner/string_value_scanner.cpp @@ -673,7 +673,7 @@ void StringValueResult::AddValue(StringValueResult &result, const idx_t buffer_p AddQuotedValue(result, buffer_pos - result.extra_delimiter_bytes); } else if (result.escaped) { AddPossiblyEscapedValue(result, buffer_pos, result.buffer_ptr + result.last_position.buffer_pos, - buffer_pos - result.last_position.buffer_pos, false); + result.HandleMultiDelimiter(buffer_pos), false); } else { result.AddValueToVector(result.buffer_ptr + result.last_position.buffer_pos, result.HandleMultiDelimiter(buffer_pos)); @@ -1536,12 +1536,22 @@ void StringValueScanner::ProcessOverBufferValue() { } if (!skip_value) { string_t value; + const auto &delimiter = result.state_machine.dialect_options.state_machine_options.delimiter.GetValue(); + const idx_t extra_delimiter_bytes = states.IsDelimiter() && !delimiter.empty() ? delimiter.size() - 1 : 0; + bool removed_extra_delimiter_bytes = false; if (result.quoted && !result.comment) { idx_t length = over_buffer_string.size() - 1 - result.quoted_position; while (length > 0 && result.ignore_empty_values && over_buffer_string.c_str()[result.quoted_position + length] == ' ') { length--; } + // remove the extra delimiter bytes before removing escapes, which can change the length of the value + if (extra_delimiter_bytes > length) { + throw InternalException( + "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); + } + length -= extra_delimiter_bytes; + removed_extra_delimiter_bytes = true; value = string_t(over_buffer_string.c_str() + result.quoted_position, UnsafeNumericCast(length)); if (result.escaped) { if (result.cur_col_id >= result.number_of_columns && @@ -1566,12 +1576,11 @@ void StringValueScanner::ProcessOverBufferValue() { } return; } - value = - RemoveEscape(str_ptr, over_buffer_string.size() - 2, - state_machine->dialect_options.state_machine_options.escape.GetValue(), - state_machine->dialect_options.state_machine_options.quote.GetValue(), - result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), - result.parse_chunk.data[result.chunk_col_id]); + value = RemoveEscape( + str_ptr, length, state_machine->dialect_options.state_machine_options.escape.GetValue(), + state_machine->dialect_options.state_machine_options.quote.GetValue(), + result.state_machine.dialect_options.state_machine_options.strict_mode.GetValue(), + result.parse_chunk.data[result.chunk_col_id]); } } } else { @@ -1611,10 +1620,7 @@ void StringValueScanner::ProcessOverBufferValue() { result.EmptyLine(result, iterator.pos.buffer_pos); } else if (!states.IsNotSet() && (!result.comment || !value.Empty())) { idx_t value_size = value.GetSize(); - if (states.IsDelimiter() && - !result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().empty()) { - idx_t extra_delimiter_bytes = - result.state_machine.dialect_options.state_machine_options.delimiter.GetValue().size() - 1; + if (!removed_extra_delimiter_bytes) { if (extra_delimiter_bytes > value_size) { throw InternalException( "Value size is lower than the number of extra delimiter bytes in the ProcessOverBufferValue()"); @@ -1758,12 +1764,16 @@ bool StringValueScanner::FirstValueEndsOnQuote(CSVIterator iterator) const { CSVStates current_state; current_state.Initialize(CSVState::STANDARD); const idx_t to_pos = iterator.GetEndPos(); + const auto quote = state_machine->dialect_options.state_machine_options.quote.GetValue(); + const auto start_pos = iterator.pos.buffer_pos; while (iterator.pos.buffer_pos < to_pos) { state_machine->Transition(current_state, buffer_handle_ptr[iterator.pos.buffer_pos++]); if (current_state.IsState(CSVState::DELIMITER) || current_state.IsState(CSVState::CARRIAGE_RETURN) || current_state.IsState(CSVState::RECORD_SEPARATOR)) { - return buffer_handle_ptr[iterator.pos.buffer_pos - 2] == - state_machine->dialect_options.state_machine_options.quote.GetValue(); + // a value that also starts with a quote is a regular quoted value + const auto value_end = iterator.pos.buffer_pos - 2; + return value_end > start_pos && buffer_handle_ptr[value_end] == quote && + buffer_handle_ptr[start_pos] != quote; } } return false; diff --git a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp index 9330822ee..907cb14a6 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/table_function/read_single_csv_file.cpp @@ -123,6 +123,9 @@ static unique_ptr ReadSingleCSVFileBind(ClientContext &context, Ta throw BinderException("read_single_csv_file requires a non-NULL file name"); } result->file = TableFunctionFileBindInput::GetFile(input); + if (result->file.path.empty()) { + throw BinderException("read_single_csv_file requires a non-empty file name"); + } // the options of the scan this file is part of steer the sniffer - the file list is this single file MultiFileOptions file_options; diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp index 02de6895c..160f15832 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_reader_options.cpp @@ -680,6 +680,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::STRUCT) { throw BinderException("read_csv columns requires a struct as input"); } + if (val.IsNull()) { + throw BinderException("read_csv columns cannot be NULL"); + } auto &struct_children = StructValue::GetChildren(val); D_ASSERT(StructType::GetChildCount(child_type) == struct_children.size()); @@ -694,6 +697,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv requires a type specification as string"); } + if (val.IsNull()) { + throw BinderException("read_csv columns cannot have a NULL type"); + } parsed_types_per_column[name] = i; parsed_types.emplace_back(TransformStringToLogicalType(StringValue::Get(val), context)); } @@ -719,6 +725,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child_type.id() != LogicalTypeId::LIST) { throw BinderException("read_csv auto_types requires a list as input"); } + if (val.IsNull()) { + throw BinderException("auto_type_candidates cannot be NULL"); + } auto &list_children = ListValue::GetChildren(val); if (list_children.empty()) { throw BinderException("auto_type_candidates requires at least one type"); @@ -727,6 +736,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (child.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("auto_type_candidates requires a type specification as string"); } + if (child.IsNull()) { + throw BinderException("auto_type_candidates cannot contain NULL types"); + } auto candidate_type = TransformStringToLogicalType(StringValue::Get(child), context); candidate_types[GetCandidateSpecificity(candidate_type)] = candidate_type; } @@ -772,6 +784,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (!columns_set && !sql_type_list.empty()) { throw BinderException("read_csv column_types/types/dtypes can only be supplied once"); } + if (val.IsNull()) { + throw BinderException("read_csv %s cannot be NULL", key); + } // Parse into temporary lists first vector sql_type_names; @@ -785,6 +800,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key if (val.type().id() != LogicalTypeId::VARCHAR) { throw BinderException("read_csv %s requires a type specification as string", key); } + if (val.IsNull()) { + throw BinderException("read_csv %s cannot have a NULL type", key); + } sql_type_names.push_back(StringValue::Get(val)); parsed_types_per_column[name] = i; } @@ -795,6 +813,9 @@ void CSVReaderOptions::ParseOption(ClientContext &context, const Identifier &key } auto &children = ListValue::GetChildren(val); for (auto &child : children) { + if (child.IsNull()) { + throw BinderException("read_csv %s cannot have a NULL type", key); + } sql_type_names.push_back(StringValue::Get(child)); } } diff --git a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp index 09c2ea4c2..f2f27d572 100644 --- a/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp +++ b/src/duckdb/src/execution/operator/csv_scanner/util/csv_validator.cpp @@ -30,7 +30,7 @@ void ThreadLines::Verify() const { continue; } if (last_end_pos + error_margin < line_info.second.start_pos || - line_info.second.start_pos < last_end_pos - error_margin) { + line_info.second.start_pos + error_margin < last_end_pos) { std::ostringstream error; error << "The Parallel CSV Reader currently does not support a full read on this file." << '\n'; error << "To correctly parse this file, please run with the single threaded error (i.e., parallel = " diff --git a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp index 81df617a6..286cc8b16 100644 --- a/src/duckdb/src/execution/operator/join/physical_asof_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_asof_join.cpp @@ -1089,14 +1089,16 @@ AsOfProbeBuffer::AsOfProbeBuffer(ClientContext &client, const PhysicalAsOfJoin & prefix_types.emplace_back(type); SortKeyPrefixComparisonColumn col; col.size = DConstants::INVALID_INDEX; - switch (type.id()) { - case LogicalTypeId::VARCHAR: - case LogicalTypeId::BLOB: - col.type = SortKeyPrefixComparisonType::VARCHAR; + switch (type.InternalType()) { + case PhysicalType::VARCHAR: + // only the sort keys of VARCHAR are terminated by the first null byte - the sort keys of other string + // types (e.g., BLOB or GEOMETRY) can contain null bytes, so we have to decode them to measure their width + col.type = type.id() == LogicalTypeId::VARCHAR ? SortKeyPrefixComparisonType::VARCHAR + : SortKeyPrefixComparisonType::NESTED; break; - case LogicalTypeId::STRUCT: - case LogicalTypeId::LIST: - case LogicalTypeId::ARRAY: + case PhysicalType::STRUCT: + case PhysicalType::LIST: + case PhysicalType::ARRAY: col.type = SortKeyPrefixComparisonType::NESTED; break; default: diff --git a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp index 21154bb4a..81ff1e45d 100644 --- a/src/duckdb/src/execution/operator/join/physical_hash_join.cpp +++ b/src/duckdb/src/execution/operator/join/physical_hash_join.cpp @@ -525,6 +525,12 @@ class HashJoinGlobalSinkState : public GlobalSinkState { //! The number of active local states atomic active_local_states; + //! The memory available to the hash table: the reservation minus what partitioning the probe side requires + idx_t GetMaxExternalHTSize() const { + const auto reservation = temporary_memory_state->GetReservation(); + return reservation > probe_side_requirement ? reservation - probe_side_requirement : 0; + } + //! Whether we are doing an external + some sizes bool external; idx_t total_size; @@ -1419,9 +1425,7 @@ class HashJoinRepartitionEvent : public BasePipelineEvent { FinalizeExternalRuntimeFilters(sink.context, op, sink); - D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - - sink.probe_side_requirement); + sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); sink.ScheduleFinalize(*pipeline, *this); } }; @@ -2017,8 +2021,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl (max_partition_ht_size + sink.probe_side_requirement) > sink.temporary_memory_state->GetReservation()) { // We have to repartition const auto radix_bits_before = ht.GetRadixBits(); - ht.SetRepartitionRadixBits(sink.temporary_memory_state->GetReservation(), sink.max_partition_size, - sink.max_partition_count); + ht.SetRepartitionRadixBits(sink.GetMaxExternalHTSize(), sink.max_partition_size, sink.max_partition_count); DUCKDB_LOG(context, PhysicalOperatorLogType, *this, "PhysicalHashJoin", "Repartition", {{"partitions_before", to_string(RadixPartitioning::NumberOfPartitions(radix_bits_before))}, {"partitions_after", to_string(RadixPartitioning::NumberOfPartitions(ht.GetRadixBits()))}}); @@ -2032,9 +2035,7 @@ SinkFinalizeType PhysicalHashJoin::Finalize(Pipeline &pipeline, Event &event, Cl sink.local_hash_tables.clear(); sink.owned_local_hash_tables.clear(); FinalizeExternalRuntimeFilters(context, *this, sink); - D_ASSERT(sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - sink.hash_table->PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - - sink.probe_side_requirement); + sink.hash_table->PrepareExternalFinalize(sink.GetMaxExternalHTSize()); sink.ScheduleFinalize(pipeline, event); } sink.finalized = true; @@ -2534,9 +2535,7 @@ void HashJoinGlobalSourceState::PrepareBuild(HashJoinGlobalSinkState &sink) { sink.probe_side_requirement); // Try to put the next partitions in the block collection of the HT - D_ASSERT(!sink.external || sink.temporary_memory_state->GetReservation() >= sink.probe_side_requirement); - if (!sink.external || - !ht.PrepareExternalFinalize(sink.temporary_memory_state->GetReservation() - sink.probe_side_requirement)) { + if (!sink.external || !ht.PrepareExternalFinalize(sink.GetMaxExternalHTSize())) { global_stage = HashJoinSourceStage::DONE; sink.temporary_memory_state->SetZero(); return; diff --git a/src/duckdb/src/execution/operator/order/physical_top_n.cpp b/src/duckdb/src/execution/operator/order/physical_top_n.cpp index 41cb651eb..5dd70b9db 100644 --- a/src/duckdb/src/execution/operator/order/physical_top_n.cpp +++ b/src/duckdb/src/execution/operator/order/physical_top_n.cpp @@ -57,9 +57,13 @@ struct TopNBoundaryValue { Vector boundary_vector; OrderModifiers boundary_modifiers; - string GetBoundaryValue() { - lock_guard l(lock); - return boundary_value; + bool TryGetBoundaryValue(string ¤t_boundary) { + unique_lock l(lock, std::try_to_lock); + if (!l.owns_lock() || boundary_value == current_boundary) { + return false; + } + current_boundary = boundary_value; + return true; } void UpdateValue(string_t boundary_val) { @@ -273,15 +277,14 @@ void TopNHeap::AddLargeHeap(DataChunk &input, const Vector &sort_keys_vec) { } bool TopNHeap::CheckBoundaryValues(DataChunk &sort_chunk, DataChunk &payload, TopNBoundaryValue &global_boundary) { - // get the global boundary value - auto current_boundary_val = global_boundary.GetBoundaryValue(); - if (current_boundary_val.empty()) { + // An older local boundary remains valid if refreshing it would block. + const auto boundary_updated = global_boundary.TryGetBoundaryValue(boundary_val); + if (boundary_val.empty()) { // no boundary value (yet) - don't do anything return true; } - if (current_boundary_val != boundary_val) { + if (boundary_updated) { // new boundary value - decode - boundary_val = std::move(current_boundary_val); boundary_values.Reset(); CreateSortKeyHelpers::DecodeSortKey(string_t(boundary_val), boundary_values, 0, modifiers); for (auto &col : boundary_values.data) { diff --git a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp index ef35aa085..af65686ff 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_batch_insert.cpp @@ -335,12 +335,12 @@ unique_ptr PhysicalBatchInsert::GetGlobalSinkState(ClientContex // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info); + auto created_table = catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info); table = &created_table->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table.get_mutable(); + table = insert_table; } // heuristic - we start off by allocating 4MB of cache space per column static constexpr const idx_t MINIMUM_MEMORY_PER_COLUMN = 4ULL * 1024ULL * 1024ULL; diff --git a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp index ebbb8a93c..7487684a4 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_copy_to_file.cpp @@ -634,7 +634,7 @@ class PartitionWriteLease { PartitionWriteInfo &Get() const { D_ASSERT(write_info); - return *write_info.get_mutable(); + return *write_info; } PartitionWriteInfo &operator*() const { @@ -649,7 +649,7 @@ class PartitionWriteLease { private: optional_ptr manager; - optional_ptr write_info; + mutable optional_ptr write_info; }; class PartitionWriteManager { @@ -766,8 +766,21 @@ struct PartitionedCopyFlushAction { struct PartitionedCopyBatchState { private: + //! Partition values can be NULL, so they are compared with IS NOT DISTINCT FROM semantics + static bool PartitionValuesEqual(const vector &left, const vector &right) { + if (left.size() != right.size()) { + return false; + } + for (idx_t i = 0; i < left.size(); i++) { + if (!Value::NotDistinctFrom(left[i], right[i])) { + return false; + } + } + return true; + } + void SetValues(vector values_p) { - D_ASSERT(!values || *values == values_p); + D_ASSERT(!values || PartitionValuesEqual(*values, values_p)); if (!values) { values = std::move(values_p); } diff --git a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp index c8c9c1635..c671c63db 100644 --- a/src/duckdb/src/execution/operator/persistent/physical_insert.cpp +++ b/src/duckdb/src/execution/operator/persistent/physical_insert.cpp @@ -117,12 +117,11 @@ unique_ptr PhysicalInsert::GetGlobalSinkState(ClientContext &co // CREATE TABLE AS D_ASSERT(!insert_table); auto &catalog = schema->catalog; - table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema.get_mutable(), *info) - ->Cast(); + table = &catalog.CreateTable(catalog.GetCatalogTransaction(context), *schema, *info)->Cast(); } else { D_ASSERT(insert_table); D_ASSERT(insert_table->IsDuckTable()); - table = insert_table.get_mutable(); + table = insert_table; } auto result = make_uniq(context, GetTypes(), *table); return std::move(result); diff --git a/src/duckdb/src/execution/operator/projection/physical_projection.cpp b/src/duckdb/src/execution/operator/projection/physical_projection.cpp index fcc5b7ba6..5dd02daa8 100644 --- a/src/duckdb/src/execution/operator/projection/physical_projection.cpp +++ b/src/duckdb/src/execution/operator/projection/physical_projection.cpp @@ -31,6 +31,16 @@ PhysicalProjection::PhysicalProjection(PhysicalPlan &physical_plan, vectorGetExpressionClass() != ExpressionClass::BOUND_REF) { + PhysicalOperator::InitializeOutputChunk(allocator, chunk); + return; + } + } + chunk.InitializeEmpty(types); +} + OperatorResultType PhysicalProjection::Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state_p) const { auto &state = state_p.Cast(); diff --git a/src/duckdb/src/execution/operator/set/physical_union.cpp b/src/duckdb/src/execution/operator/set/physical_union.cpp index 3be025eff..78551361b 100644 --- a/src/duckdb/src/execution/operator/set/physical_union.cpp +++ b/src/duckdb/src/execution/operator/set/physical_union.cpp @@ -83,10 +83,15 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin auto dependency_type = order_matters ? PipelineDependencyType::REQUIRED : PipelineDependencyType::OPTIONAL_DEPENDENCY; dependencies = meta_pipeline.AddDependenciesFrom(union_pipeline, union_pipeline, false, dependency_type); + if (!allow_out_of_order) { + // CreateUnionPipeline already orders this pipeline after current. Include current when + // propagating that ordering to the child sink pipelines as well. + dependencies.push_back(current.shared_from_this()); + } // we also add dependencies if the LHS child can saturate all available threads // in that case, we recursively make all RHS children depend on the LHS. // This prevents breadth-first plan evaluation - if (can_saturate_threads) { + if (!allow_out_of_order || can_saturate_threads) { last_child_ptr = meta_pipeline.GetLastChild(); } } @@ -96,8 +101,10 @@ void PhysicalUnion::BuildPipelines(Pipeline ¤t, MetaPipeline &meta_pipelin children[i].get().BuildPipelines(union_pipeline, meta_pipeline); if (last_child_ptr) { - // the pointer was set, set up the dependencies - meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr); + // Explicitly ordered unions also order their children's sink pipelines. + auto dependency_mode = + !allow_out_of_order ? RecursiveDependencyMode::FORCE : RecursiveDependencyMode::RESPECT_PARALLELISM; + meta_pipeline.AddRecursiveDependencies(dependencies, *last_child_ptr, dependency_mode); } } } diff --git a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp index c22e2cf4f..f2b2dbbcc 100644 --- a/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_asof_join.cpp @@ -144,6 +144,7 @@ optional_ptr PhysicalPlanGenerator::PlanAsOfLoopJoin(LogicalCo case ExpressionType::COMPARE_EQUAL: case ExpressionType::COMPARE_NOTEQUAL: case ExpressionType::COMPARE_DISTINCT_FROM: + case ExpressionType::COMPARE_NOT_DISTINCT_FROM: break; default: // Unsupported NLJ comparison @@ -360,6 +361,11 @@ PhysicalOperator &PhysicalPlanGenerator::PlanAsOfJoin(LogicalComparisonJoin &op) if (result) { return *result; } + if (has_predicate) { + // PhysicalAsOfJoin only evaluates the comparisons + throw NotImplementedException("Unsupported ASOF JOIN: the join predicate cannot be evaluated together " + "with these join conditions"); + } } // Check to see if we can leverage partitioning diff --git a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp index 4431083a1..3a3267568 100644 --- a/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_comparison_join.cpp @@ -247,7 +247,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi return join; } - D_ASSERT(op.left_projection_map.empty()); + // range joins apply both projection maps, the nested loop join only applies the left one of a MARK join idx_t nested_loop_join_threshold = Settings::Get(context); if (left.estimated_cardinality < nested_loop_join_threshold || right.estimated_cardinality < nested_loop_join_threshold) { @@ -273,6 +273,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi } if (PhysicalNestedLoopJoin::IsSupported(op.conditions, op.join_type)) { // inequality join: use nested loop + D_ASSERT(op.left_projection_map.empty() || op.join_type == JoinType::MARK); return Make(op, left, right, std::move(op.conditions), op.join_type, op.estimated_cardinality, std::move(op.filter_pushdown)); } @@ -282,6 +283,7 @@ PhysicalOperator &PhysicalPlanGenerator::PlanComparisonJoin(LogicalComparisonJoi RewriteJoinCondition(cond.RightReference(), left.types.size()); } } + D_ASSERT(op.left_projection_map.empty()); auto condition = JoinCondition::CreateExpression(std::move(op.conditions)); return Make(op, left, right, std::move(condition), op.join_type, op.estimated_cardinality); } diff --git a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp index 6e83fd173..078f2b426 100644 --- a/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp +++ b/src/duckdb/src/execution/physical_plan/plan_set_operation.cpp @@ -39,6 +39,24 @@ static JoinCondition CreateNotDistinctComparison(ClientContext &context, const L return cond; } +//! The types to compare the columns of EXCEPT / INTERSECT with - a collation of either side applies (as for UNION) +static vector GetComparisonTypes(ClientContext &context, const LogicalSetOperation &op, + const vector &left_types, + const vector &right_types) { + // the types only differ in their collations here, but a collation can be missing from a child (e.g. when the + // projection applying a COLLATE was removed) or from the operator (it takes the types of the left child) + vector types; + for (idx_t i = 0; i < left_types.size(); i++) { + auto type = LogicalType::ForceMaxLogicalType(context, left_types[i], right_types[i]); + // the operator types can be outdated (e.g. compressed children), only take a collation from them + if (i < op.types.size() && op.types[i].id() == type.id()) { + type = LogicalType::ForceMaxLogicalType(context, op.types[i], type); + } + types.push_back(std::move(type)); + } + return types; +} + PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { ArenaLinkedList> children(physical_plan->ArenaRef()); for (auto &child : op.children) { @@ -63,7 +81,7 @@ PhysicalOperator &PhysicalPlanGenerator::CreatePlan(LogicalSetOperation &op) { } auto &left = children[0]; auto &right = children[1]; - auto &types = left.get().GetTypes(); + auto types = GetComparisonTypes(context, op, left.get().GetTypes(), right.get().GetTypes()); vector conditions; // create equality condition for all columns for (idx_t i = 0; i < types.size(); i++) { diff --git a/src/duckdb/src/function/aggregate/distributive/count.cpp b/src/duckdb/src/function/aggregate/distributive/count.cpp index 69360d5aa..f62027b09 100644 --- a/src/duckdb/src/function/aggregate/distributive/count.cpp +++ b/src/duckdb/src/function/aggregate/distributive/count.cpp @@ -296,6 +296,7 @@ AggregateFunction CountFunctionBase::GetFunction() { fun.GetSignature().AddParameter("arg", LogicalTypeId::ANY); fun.SetName("count"); fun.SetOrderDependent(AggregateOrderDependent::NOT_ORDER_DEPENDENT); + fun.SetCollationHandling(FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS); fun.SetStructStateExport(GetCountStateType); fun.SetStatisticsCallback(CountPropagateStats); return fun; diff --git a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp index c36df8b1d..318702a0b 100644 --- a/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp +++ b/src/duckdb/src/function/aggregate/sorted_aggregate_function.cpp @@ -63,12 +63,22 @@ struct SortedAggregateBindData : public FunctionData { using BindInfoPtr = unique_ptr; using OrderBys = vector; - SortedAggregateBindData(ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, - BindInfoPtr &bind_info, OrderBys &order_bys) + //! fixed_layout: the buffer struct and per-key columns to use instead of mapping the keys onto the arguments + SortedAggregateBindData( + ClientContext &context, Expressions &children, BoundAggregateFunction &aggregate, BindInfoPtr &bind_info, + OrderBys &order_bys, + optional_ptr>> fixed_layout = nullptr) : context(context), function(aggregate), bind_info(std::move(bind_info)), threshold(Settings::Get(context)) { vector order_spec; - buffered_types = MapSortedColumns(children, order_bys, order_spec); + if (fixed_layout) { + for (auto &child : StructType::GetChildTypes(fixed_layout->first)) { + buffered_types.emplace_back(child.second); + } + order_spec = fixed_layout->second; + } else { + buffered_types = MapSortedColumns(children, order_bys, order_spec); + } const idx_t argument_count = children.size(); // the arguments are the leading buffered columns (referencing their input slot); appended sort keys follow buffered_cols.resize(buffered_types.size()); @@ -86,9 +96,11 @@ struct SortedAggregateBindData : public FunctionData { BuildSort(order_spec); } - SortedAggregateBindData(ClientContext &context, BoundAggregateExpression &expr) + SortedAggregateBindData( + ClientContext &context, BoundAggregateExpression &expr, + optional_ptr>> fixed_layout = nullptr) : SortedAggregateBindData(context, expr.GetChildrenMutable(), expr.FunctionMutable(), expr.BindInfoMutable(), - expr.GetOrderBysMutable()->orders) { + expr.GetOrderBysMutable()->orders, fixed_layout) { } SortedAggregateBindData(ClientContext &context, BoundWindowExpression &expr) @@ -616,29 +628,34 @@ void FunctionBinder::BindSortedAggregate(ClientContext &context, BoundAggregateE auto &children = expr.GetChildrenMutable(); auto &order_bys = *expr.GetOrderBysMutable(); + // the layout of an exported state was fixed at bind time and is part of its type - keep it, as the optimizer may + // have changed the expressions since (e.g. so that an ORDER BY key no longer matches the argument it matched) + pair> export_layout; if (state_export) { + export_layout.first = ListType::GetChildType(expr.GetReturnType()); + auto &logical_fields = StructType::GetChildTypes(export_layout.first); + ExportAggregateFunction::GetSortedStateOrders(expr.GetReturnType(), export_layout.second); + if (export_layout.second.size() != order_bys.orders.size() || children.size() > logical_fields.size()) { + throw InternalException("Exported state layout of ordered aggregate \"%s\" does not match its ORDER BY", + bound_function.GetName()); + } // the statistics optimizer may have narrowed the buffered column types since the exported type was fixed at // bind time (e.g. a small-range group column INTEGER->TINYINT) - cast them back so the buffer matches its - // declared type. The widening preserves sort order; casting reused columns to the same type keeps matching. - LogicalType plan_struct; - vector order_columns; - idx_t argument_count; - GetSortedAggregateStateLayout(expr, plan_struct, order_columns, argument_count); - auto &logical_fields = StructType::GetChildTypes(ListType::GetChildType(expr.GetReturnType())); + // declared type. The widening preserves sort order. auto cast_to = [&](unique_ptr &e, const LogicalType &type) { if (e->GetReturnType() != type) { e = BoundCastExpression::AddCastToType(context, std::move(e), type); } }; - for (idx_t i = 0; i < children.size() && i < logical_fields.size(); i++) { + for (idx_t i = 0; i < children.size(); i++) { cast_to(children[i], logical_fields[i].second); } - for (idx_t o = 0; o < order_bys.orders.size() && o < order_columns.size(); o++) { - cast_to(order_bys.orders[o].expression, logical_fields[order_columns[o].column].second); + for (idx_t o = 0; o < order_bys.orders.size(); o++) { + cast_to(order_bys.orders[o].expression, logical_fields[export_layout.second[o].column].second); } } - auto sorted_bind = make_uniq(context, expr); + auto sorted_bind = make_uniq(context, expr, state_export ? &export_layout : nullptr); if (!sorted_bind->sorted_on_args) { // The arguments are the children plus the sort columns. diff --git a/src/duckdb/src/function/cast/array_casts.cpp b/src/duckdb/src/function/cast/array_casts.cpp index 6e373fb6b..118dd5690 100644 --- a/src/duckdb/src/function/cast/array_casts.cpp +++ b/src/duckdb/src/function/cast/array_casts.cpp @@ -52,11 +52,9 @@ static bool ArrayToArrayCast(Vector &source, Vector &result, idx_t count, CastPa auto msg = StringUtil::Format("Cannot cast array of size %u to array of size %u", source_array_size, target_array_size); HandleCastError::AssignError(msg, parameters); - if (!parameters.strict) { - // if this was a TRY_CAST, we know every row will fail, so just return null - ConstantVector::SetNull(result, count_t(count)); - return false; - } + // every row fails, so just return null + ConstantVector::SetNull(result, count_t(count)); + return false; } auto &cast_data = parameters.cast_data->Cast(); diff --git a/src/duckdb/src/function/cast/bignum_casts.cpp b/src/duckdb/src/function/cast/bignum_casts.cpp index 489a94270..11b6e0ec3 100644 --- a/src/duckdb/src/function/cast/bignum_casts.cpp +++ b/src/duckdb/src/function/cast/bignum_casts.cpp @@ -276,6 +276,18 @@ BoundCastInfo Bignum::NumericToBignumCastSwitch(const LogicalType &source) { } } +struct BignumToDoubleErrorCast { + template + static inline bool Operation(SRC input, DST &result, CastParameters ¶meters) { + if (Bignum::BignumToDouble(input, result, parameters.strict)) { + return true; + } + HandleCastError::AssignError( + StringUtil::Format("Could not convert bignum '%s' to Double", Bignum::BignumToVarchar(input)), parameters); + return false; + } +}; + BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const LogicalType &source, const LogicalType &target) { D_ASSERT(source.id() == LogicalTypeId::BIGNUM); @@ -313,7 +325,7 @@ BoundCastInfo DefaultCasts::BignumCastSwitch(BindCastInput &input, const Logical case LogicalTypeId::VARCHAR: return BoundCastInfo(&VectorCastHelpers::StringCast); case LogicalTypeId::DOUBLE: - return BoundCastInfo(&VectorCastHelpers::TryCastLoop); + return BoundCastInfo(&VectorCastHelpers::TryCastErrorLoop); default: return TryVectorNullCast; } diff --git a/src/duckdb/src/function/cast/cast_statistics.cpp b/src/duckdb/src/function/cast/cast_statistics.cpp index b382f1633..baebb777a 100644 --- a/src/duckdb/src/function/cast/cast_statistics.cpp +++ b/src/duckdb/src/function/cast/cast_statistics.cpp @@ -153,6 +153,10 @@ static unique_ptr StatisticsPropagateVariant(const BaseStatistic // only try this for non-nested return nullptr; } + if (input.GetStatsType() != StatisticsType::VARIANT_STATS) { + // e.g. the unknown statistics of a prepared statement parameter + return nullptr; + } if (!VariantStats::IsShredded(input)) { // not shredded return nullptr; diff --git a/src/duckdb/src/function/cast/string_cast.cpp b/src/duckdb/src/function/cast/string_cast.cpp index 1a9d2b44f..ce5893030 100644 --- a/src/duckdb/src/function/cast/string_cast.cpp +++ b/src/duckdb/src/function/cast/string_cast.cpp @@ -4,6 +4,7 @@ #include "duckdb/common/vector/list_vector.hpp" #include "duckdb/common/vector/map_vector.hpp" #include "duckdb/common/vector/struct_vector.hpp" +#include "duckdb/common/vector/vector_iterator.hpp" #include "duckdb/function/cast/default_casts.hpp" #include "duckdb/function/cast/vector_cast_helpers.hpp" #include "duckdb/common/exception/conversion_exception.hpp" @@ -323,8 +324,10 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, // set the list size after the child casts, since the casts may have replaced the child buffers ListVector::SetListSize(result, total); - if (!vector_cast_data.all_converted) { - auto &key_validity = FlatVector::ValidityMutable(result_key_child); + if (!vector_cast_data.all_converted || parameters.error_message) { + // maps with a NULL key - from a failed key cast, or keys of type NULL - fail to convert + // the key cast does not necessarily produce a flat vector (e.g. when casting to NULL keys) + auto key_validity = result_key_child.Validity(); for (idx_t row_idx = 0; row_idx < count; row_idx++) { if (!result_mask.RowIsValid(row_idx)) { continue; @@ -332,13 +335,30 @@ bool VectorStringToMap::StringToNestedTypeCastLoop(const string_t *source_data, auto list = list_data[row_idx]; for (idx_t list_idx = 0; list_idx < list.length; list_idx++) { auto idx = list.offset + list_idx; - if (!key_validity.RowIsValid(idx)) { - result_mask.SetInvalid(row_idx); + if (!key_validity.IsValid(idx)) { + HandleVectorCastError::Operation("Map keys can not be NULL.", result_mask, row_idx, + vector_cast_data); + break; } } } } - MapVector::MapConversionVerify(result, count); + if (!parameters.error_message) { + MapVector::MapConversionVerify(result, count); + return vector_cast_data.all_converted; + } + // TRY_CAST: nullify maps with duplicate keys instead of throwing + SelectionVector row_sel(1); + for (idx_t row_idx = 0; row_idx < count; row_idx++) { + if (!result_mask.RowIsValid(row_idx)) { + continue; + } + row_sel.set_index(0, row_idx); + if (MapVector::CheckMapValidity(result, 1, row_sel) != MapInvalidReason::VALID) { + HandleVectorCastError::Operation("Map keys must be unique.", result_mask, row_idx, + vector_cast_data); + } + } return vector_cast_data.all_converted; } diff --git a/src/duckdb/src/function/cast/union_casts.cpp b/src/duckdb/src/function/cast/union_casts.cpp index e4630e317..b1ad5230f 100644 --- a/src/duckdb/src/function/cast/union_casts.cpp +++ b/src/duckdb/src/function/cast/union_casts.cpp @@ -359,6 +359,11 @@ BoundCastInfo DefaultCasts::ImplicitToUnionCast(BindCastInput &input, const Logi } static bool UnionToVarcharCast(Vector &source, Vector &result, idx_t count, CastParameters ¶meters) { + if (count == 0) { + // the intermediate union vector would not have any storage + FlatVector::SetSize(result, count_t(0)); + return true; + } // first cast all union members to varchar auto &cast_data = parameters.cast_data->Cast(); Vector varchar_union(cast_data.type, count); diff --git a/src/duckdb/src/function/cast/variant/from_variant.cpp b/src/duckdb/src/function/cast/variant/from_variant.cpp index a38820cbd..bb910a1bb 100644 --- a/src/duckdb/src/function/cast/variant/from_variant.cpp +++ b/src/duckdb/src/function/cast/variant/from_variant.cpp @@ -594,8 +594,18 @@ static optional TryCastVariantValueToMap(FromVariantConversionData &conve if (entries->IsNull()) { return Value(target_type); } + auto &children = ListValue::GetChildren(*entries); + for (auto &child : children) { + if (child.IsNull()) { + // a NULL entry has no key + if (conversion_data.error.empty()) { + conversion_data.error = "Map keys can not be NULL."; + } + return optional(); + } + } try { - return Value::MAP(entry_type, ListValue::GetChildren(*entries)); + return Value::MAP(entry_type, children); } catch (const InvalidInputException &ex) { // the entries do not form a valid map (e.g. duplicate keys) if (conversion_data.error.empty()) { @@ -680,6 +690,13 @@ static bool CastVariant(FromVariantConversionData &conversion_data, Vector &resu // Set the value to NULL and mark as failed FlatVector::SetNull(result, offset + i, true); all_valid = false; + } catch (const ConversionException &ex) { + // strict casts of the converted value (e.g. VARCHAR to an ARRAY of a different size) can throw + if (error.empty()) { + error = ErrorData(ex).RawMessage(); + } + FlatVector::SetNull(result, offset + i, true); + all_valid = false; } } if (target_type.id() == LogicalTypeId::MAP) { diff --git a/src/duckdb/src/function/function_binder.cpp b/src/duckdb/src/function/function_binder.cpp index 25f9fc9fd..2d10779e9 100644 --- a/src/duckdb/src/function/function_binder.cpp +++ b/src/duckdb/src/function/function_binder.cpp @@ -1125,6 +1125,8 @@ static string PushArgumentCollations(ClientContext &context, BoundSimpleFunction return string(); case FunctionCollationHandling::PUSH_COMBINABLE_COLLATIONS: return PushCollations(context, bound_function, children, CollationType::COMBINABLE_COLLATIONS); + case FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS: + throw InternalException("PUSH_DISTINCT_COLLATIONS is only supported for aggregate functions"); default: throw InternalException("Unrecognized collation handling"); } @@ -1622,6 +1624,10 @@ unique_ptr FunctionBinder::BindScalarFunction(shared_ptr(std::move(bound_function), std::move(children), std::move(bind_info), is_operator); + if (binder && result_func->Function().GetStability() == FunctionStability::CONSISTENT_WITHIN_QUERY) { + // the result depends on the query it is bound in, also when the bind_expression callback folds it + binder->SetAlwaysRequireRebind(); + } if (result_func->Function().HasBindExpressionCallback()) { // if a bind_expression callback is registered - call it and emit the resulting expression FunctionBindExpressionInput input(context, result_func->FunctionMutable(), result_func->BindInfoMutable().get(), @@ -1698,6 +1704,15 @@ FunctionBinder::BindAggregateFunction(shared_ptr functi vector> children, vector>> keyword_args, unique_ptr filter, AggregateType aggr_type) { + if (aggr_type == AggregateType::DISTINCT && + function->GetCollationHandling() == FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { + for (auto &child : children) { + ExpressionBinder::PushCollation(context, child, child->GetReturnType()); + } + for (auto &argument : keyword_args) { + ExpressionBinder::PushCollation(context, argument.second, argument.second->GetReturnType()); + } + } auto [bound_function, bind_info] = ResolveFunction(std::move(function), children, keyword_args); return make_uniq(std::move(bound_function), std::move(children), std::move(filter), diff --git a/src/duckdb/src/function/pragma/pragma_functions.cpp b/src/duckdb/src/function/pragma/pragma_functions.cpp index c86c04875..4ddce15a7 100644 --- a/src/duckdb/src/function/pragma/pragma_functions.cpp +++ b/src/duckdb/src/function/pragma/pragma_functions.cpp @@ -19,9 +19,9 @@ namespace duckdb { -static void PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { +static string PragmaEnableProfilingStatement(ClientContext &context, const FunctionParameters ¶meters) { auto &config = ClientConfig::GetConfig(context); - config.enable_profiler = true; + return StringUtil::Format("SET enable_profiling=%s", SQLString(config.profiler_print_format)); } void RegisterEnableProfiling(BuiltinFunctions &set) { @@ -33,24 +33,25 @@ void RegisterEnableProfiling(BuiltinFunctions &set) { } static void PragmaDisableProfiling(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); auto &config = ClientConfig::GetConfig(context); config.enable_profiler = false; } -static void PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).enable_progress_bar = true; +static string PragmaEnableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar=true"; } -static void PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).enable_progress_bar = false; +static string PragmaDisableProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar=false"; } -static void PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).print_progress_bar = true; +static string PragmaEnablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar_print=true"; } -static void PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { - ClientConfig::GetConfig(context).print_progress_bar = false; +static string PragmaDisablePrintProgressBar(ClientContext &context, const FunctionParameters ¶meters) { + return "SET enable_progress_bar_print=false"; } //! Warn that a pragma that no longer does anything is on its way out @@ -71,14 +72,17 @@ static void PragmaDisableVerification(ClientContext &context, const FunctionPara } static void PragmaEnableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = true; } static void PragmaForceCheckpoint(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("force_checkpoint"); DBConfig::GetConfig(context).options.force_checkpoint = true; } static void PragmaDisableForceParallelism(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("verify_parallelism"); ClientConfig::GetConfig(context).verify_parallelism = false; } @@ -91,19 +95,21 @@ static void PragmaDisableObjectCache(ClientContext &context, const FunctionParam } static void PragmaEnableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = true; } static void PragmaDisableCheckpointOnShutdown(ClientContext &context, const FunctionParameters ¶meters) { + DBConfig::GetConfig(context).CheckLock("checkpoint_on_shutdown"); DBConfig::GetConfig(context).options.checkpoint_on_shutdown = false; } -static void PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(true)); +static string PragmaEnableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + return "SET SESSION enable_optimizer=true"; } -static void PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { - Settings::Set(context, SetScope::SESSION, Value::BOOLEAN(false)); +static string PragmaDisableOptimizer(ClientContext &context, const FunctionParameters ¶meters) { + return "SET SESSION enable_optimizer=false"; } void PragmaFunctions::RegisterFunction(BuiltinFunctions &set) { diff --git a/src/duckdb/src/function/scalar/generic/getvariable.cpp b/src/duckdb/src/function/scalar/generic/getvariable.cpp index fe1fbb2a9..4eca4fa03 100644 --- a/src/duckdb/src/function/scalar/generic/getvariable.cpp +++ b/src/duckdb/src/function/scalar/generic/getvariable.cpp @@ -57,6 +57,8 @@ ScalarFunction GetVariableFun::GetFunction() { ScalarFunction getvar("getvariable", {}, LogicalType::ANY, nullptr, GetVariableBind, nullptr); getvar.GetSignature().AddParameter("variable_name", LogicalType::VARCHAR); getvar.SetBindExpressionCallback(BindGetVariableExpression); + // the value of the variable can change between queries + getvar.SetStability(FunctionStability::CONSISTENT_WITHIN_QUERY); return getvar; } diff --git a/src/duckdb/src/function/scalar/map/map_contains.cpp b/src/duckdb/src/function/scalar/map/map_contains.cpp index 95624c3a5..5565d0ebd 100644 --- a/src/duckdb/src/function/scalar/map/map_contains.cpp +++ b/src/duckdb/src/function/scalar/map/map_contains.cpp @@ -1,4 +1,5 @@ #include "duckdb/common/vector/map_vector.hpp" +#include "duckdb/function/function_binder.hpp" #include "duckdb/function/scalar/list/contains_or_position.hpp" #include "duckdb/planner/expression/bound_cast_expression.hpp" #include "duckdb/function/scalar/map_functions.hpp" @@ -15,12 +16,52 @@ static void MapContainsFunction(DataChunk &input, ExpressionState &state, Vector ListSearchOp(map_vec, key_vec, arg_vec, result, count); } +static bool TypeHasCollation(const LogicalType &type) { + switch (type.id()) { + case LogicalTypeId::VARCHAR: + return !type.HasAlias() && !StringType::GetCollation(type).empty(); + case LogicalTypeId::LIST: + return TypeHasCollation(ListType::GetChildType(type)); + case LogicalTypeId::ARRAY: + return TypeHasCollation(ArrayType::GetChildType(type)); + default: + return false; + } +} + +static unique_ptr BindMapContainsExpression(FunctionBindExpressionInput &input) { + const auto &key_type = input.children[1]->GetReturnType(); + const auto &map_key_type = MapType::KeyType(input.children[0]->GetReturnType()); + if (!TypeHasCollation(key_type) && !TypeHasCollation(map_key_type)) { + return nullptr; + } + + FunctionBinder function_binder(input.context); + ErrorData error; + vector> children; + children.push_back(std::move(input.children[0])); + auto keys = function_binder.BindScalarFunction(Identifier::DefaultSchema(), "map_keys", std::move(children), error); + if (!keys) { + error.Throw(); + } + children.clear(); + children.push_back(std::move(keys)); + children.push_back(std::move(input.children[1])); + auto result = + function_binder.BindScalarFunction(Identifier::DefaultSchema(), "list_contains", std::move(children), error); + if (!result) { + error.Throw(); + } + return result; +} + ScalarFunction MapContainsFun::GetFunction() { auto key_type = LogicalType::TEMPLATE("K"); auto val_type = LogicalType::TEMPLATE("V"); ScalarFunction fun("map_contains", {}, LogicalType::BOOLEAN, MapContainsFunction); fun.GetSignature().AddParameter("map", LogicalType::MAP(key_type, val_type)).AddParameter("key", key_type); + fun.SetBindExpressionCallback(BindMapContainsExpression); return fun; } diff --git a/src/duckdb/src/function/scalar/operator/arithmetic.cpp b/src/duckdb/src/function/scalar/operator/arithmetic.cpp index ca3983d28..bdc3e8e01 100644 --- a/src/duckdb/src/function/scalar/operator/arithmetic.cpp +++ b/src/duckdb/src/function/scalar/operator/arithmetic.cpp @@ -472,14 +472,16 @@ static void BignumAdd(DataChunk &args, ExpressionState &state, Vector &result) { static void BignumSubtract(DataChunk &args, ExpressionState &state, Vector &result) { auto &heap = StringVector::GetStringHeap(result); + // negate a copy of the right operand - the input must not be modified, it can e.g. also be the left operand + vector negated_rhs; BinaryExecutor::Execute( args.data[0], args.data[1], result, [&](bignum_t a, bignum_t b) { const BignumIntermediate lhs(a); - BignumIntermediate rhs(b); + negated_rhs.resize(b.data.GetSize()); + memcpy(negated_rhs.data(), b.data.GetData(), b.data.GetSize()); + BignumIntermediate rhs(negated_rhs.data(), negated_rhs.size()); rhs.NegateInPlace(); - auto result_value = BignumIntermediate::Add(heap, lhs, rhs); - rhs.NegateInPlace(); - return result_value; + return BignumIntermediate::Add(heap, lhs, rhs); }); } @@ -550,7 +552,9 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi function.SetFallible(); // INTERVAL ordering uses 30-day months but DATE arithmetic uses calendar months, // e.g. '2025-02-01' + INTERVAL '29 days' = '2025-03-02' > + INTERVAL '1 month' = '2025-03-01'. - function.SetArgProperties({inc, unset}); + // Adding months clamps the day, e.g. both '2000-01-30' and '2000-01-31' + INTERVAL '1 month' are + // '2000-02-29', so this is not strictly increasing in the DATE. + function.SetArgProperties({ArgProperties().NonDecreasing(), unset}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, @@ -586,7 +590,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({unset, inc}); + function.SetArgProperties({unset, ArgProperties().NonDecreasing()}); return function; } else if (right_type.id() == LogicalTypeId::TIME) { // TIME +/- INTERVAL wraps modulo 24h. @@ -604,7 +608,8 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({unset, inc}); + // not monotonic: adding months clamps the day but keeps the time, e.g. + // '2000-01-30 23:00' + INTERVAL '1 month' > '2000-01-31 01:00' + INTERVAL '1 month' return function; } break; @@ -640,7 +645,7 @@ ScalarFunction AddFunction::GetFunction(const LogicalType &left_type, const Logi ScalarFunction function("+", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - function.SetArgProperties({inc, unset}); + // not monotonic: see INTERVAL + TIMESTAMP return function; } break; @@ -874,7 +879,8 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const function.SetFallible(); // Monotonic in DATE only; INTERVAL ordering uses 30-day months but DATE arithmetic uses // calendar months so monotonicity in the INTERVAL arg is broken (see OperatorAddFun). - function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); + // Subtracting months clamps the day, so this is not strictly increasing in the DATE. + function.SetArgProperties({ArgProperties().NonDecreasing(), ArgProperties()}); return function; } break; @@ -891,8 +897,9 @@ ScalarFunction SubtractFunction::GetFunction(const LogicalType &left_type, const "-", {left_type, right_type}, LogicalType::TIMESTAMP, ScalarFunction::BinaryFunction); function.SetFallible(); - // Monotonic in TIMESTAMP only; see DATE - INTERVAL above. - function.SetArgProperties({ArgProperties().StrictlyIncreasing(), ArgProperties()}); + // not monotonic: subtracting months clamps the day but keeps the time, e.g. + // '2000-03-30 23:00' - INTERVAL '1 month' > '2000-03-31 01:00' - INTERVAL '1 month' + return function; } break; diff --git a/src/duckdb/src/function/scalar/sequence/nextval.cpp b/src/duckdb/src/function/scalar/sequence/nextval.cpp index c8bd13b70..b8257b420 100644 --- a/src/duckdb/src/function/scalar/sequence/nextval.cpp +++ b/src/duckdb/src/function/scalar/sequence/nextval.cpp @@ -157,6 +157,16 @@ void NextValModifiedDatabases(ClientContext &context, FunctionModifiedDatabasesI input.properties.RegisterDBModify(seq.sequence.ParentCatalog(), context, DatabaseModificationType::SEQUENCE); } +//! currval reads the sequence but holds a reference to its catalog entry, so a prepared statement must be rebound +//! when the catalog changes (e.g. the sequence is dropped) without marking the database as modified +void CurrValReadDatabases(ClientContext &context, FunctionModifiedDatabasesInput &input) { + if (!input.bind_data) { + return; + } + auto &seq = input.bind_data->Cast(); + input.properties.RegisterDBRead(seq.sequence.ParentCatalog(), context); +} + } // namespace ScalarFunction NextvalFun::GetFunction() { @@ -181,6 +191,7 @@ ScalarFunction CurrvalFun::GetFunction() { curr_val.SetBindCallback(NextValBind); curr_val.SetSerializeCallback(Serialize); curr_val.SetDeserializeCallback(Deserialize); + curr_val.SetModifiedDatabasesCallback(CurrValReadDatabases); curr_val.SetInitStateCallback(NextValLocalFunction); curr_val.SetVolatile(); curr_val.SetFallible(); diff --git a/src/duckdb/src/function/scalar/string/contains.cpp b/src/duckdb/src/function/scalar/string/contains.cpp index a980ce1fc..dece4681e 100644 --- a/src/duckdb/src/function/scalar/string/contains.cpp +++ b/src/duckdb/src/function/scalar/string/contains.cpp @@ -32,13 +32,6 @@ struct ContainsUnaligned { } }; -struct ContainsGeneric { - template - static bool MatchRemainder(const unsigned char *haystack, const unsigned char *needle, idx_t needle_size) { - return memcmp(haystack + sizeof(UNSIGNED), needle + sizeof(UNSIGNED), needle_size - sizeof(UNSIGNED)) == 0; - } -}; - template static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, idx_t needle_size, idx_t base_offset) { @@ -67,6 +60,66 @@ static idx_t Contains(const unsigned char *haystack, idx_t haystack_size, const return DConstants::INVALID_INDEX; } +//! Linear-time (Knuth-Morris-Pratt) search, used when the needle keeps almost-matching the haystack +static idx_t ContainsLinear(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, + idx_t needle_size, idx_t base_offset) { + // prefix[i] is the length of the longest proper prefix of needle[0..i] that is also a suffix of it + vector prefix(needle_size, 0); + for (idx_t i = 1, k = 0; i < needle_size; i++) { + while (k > 0 && needle[i] != needle[k]) { + k = prefix[k - 1]; + } + if (needle[i] == needle[k]) { + k++; + } + prefix[i] = k; + } + for (idx_t i = 0, k = 0; i < haystack_size; i++) { + while (k > 0 && haystack[i] != needle[k]) { + k = prefix[k - 1]; + } + if (haystack[i] == needle[k]) { + k++; + } + if (k == needle_size) { + return base_offset + i + 1 - needle_size; + } + } + return DConstants::INVALID_INDEX; +} + +//! Like Contains, but every candidate position compares up to needle_size bytes, which is +//! quadratic when the needle keeps almost-matching - switch to a linear-time search once that happens +static idx_t ContainsLong(const unsigned char *haystack, idx_t haystack_size, const unsigned char *needle, + idx_t needle_size, idx_t base_offset) { + if (needle_size > haystack_size) { + return DConstants::INVALID_INDEX; + } + const idx_t candidate_end = haystack_size - (needle_size - 1); + auto needle_entry = Load(needle); + // the number of candidates whose remainder we compare before switching, roughly linear in the haystack + const idx_t max_full_compares = 16 + (haystack_size * 8) / needle_size; + idx_t full_compares = 0; + for (idx_t offset = 0; offset < candidate_end; offset++) { + auto location = + static_cast(memchr(haystack + offset, needle[0], candidate_end - offset)); + if (!location) { + return DConstants::INVALID_INDEX; + } + offset = UnsafeNumericCast(location - haystack); + if (Load(location) != needle_entry) { + continue; + } + if (memcmp(location + sizeof(uint64_t), needle + sizeof(uint64_t), needle_size - sizeof(uint64_t)) == 0) { + return base_offset + offset; + } + if (++full_compares > max_full_compares) { + return ContainsLinear(location, haystack_size - offset, needle, needle_size, base_offset + offset); + } + } + return DConstants::INVALID_INDEX; +} + struct ContainsOperator { template static inline TR Operation(TA left, TB right) { @@ -105,7 +158,7 @@ idx_t FindStrInStr(const unsigned char *haystack, idx_t haystack_size, const uns case 8: return Contains(haystack, haystack_size, needle, 8, base_offset); default: - return Contains(haystack, haystack_size, needle, needle_size, base_offset); + return ContainsLong(haystack, haystack_size, needle, needle_size, base_offset); } } diff --git a/src/duckdb/src/function/scalar/string/length.cpp b/src/duckdb/src/function/scalar/string/length.cpp index 8fddb4375..e1e3e440e 100644 --- a/src/duckdb/src/function/scalar/string/length.cpp +++ b/src/duckdb/src/function/scalar/string/length.cpp @@ -1,6 +1,8 @@ #include "duckdb/common/exception.hpp" #include "duckdb/common/numeric_utils.hpp" #include "duckdb/common/types/bit.hpp" +#include "duckdb/common/vector/vector_iterator.hpp" +#include "duckdb/common/vector/vector_writer.hpp" #include "duckdb/common/vector_operations/vector_operations.hpp" #include "duckdb/storage/statistics/base_statistics.hpp" #include "duckdb/storage/statistics/numeric_stats.hpp" @@ -220,13 +222,24 @@ void ArrayLengthBinaryFunction(DataChunk &args, ExpressionState &state, Vector & auto &dimensions = data.dimensions; auto max_dimension = static_cast(dimensions.size()); - UnaryExecutor::Execute(dimension, result, [&](int64_t dimension) { - if (dimension < 1 || dimension > max_dimension) { + // the result is NULL for NULL arrays + const auto count = args.size(); + auto array_validity = args.data[0].Validity(); + auto dimension_values = dimension.Values(); + auto writer = FlatVector::Writer(result, count); + for (idx_t i = 0; i < count; i++) { + auto entry = dimension_values[i]; + if (!array_validity.IsValid(i) || !entry.IsValid()) { + writer.WriteNull(); + continue; + } + auto dim = entry.GetValue(); + if (dim < 1 || dim > max_dimension) { throw OutOfRangeException(StringUtil::Format( - "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dimension, max_dimension)); + "array_length dimension '%lld' out of range (min: '1', max: '%lld')", dim, max_dimension)); } - return dimensions[UnsafeNumericCast(dimension - 1)]; - }); + writer.WriteValue(dimensions[UnsafeNumericCast(dim - 1)]); + } } void ArrayOrListLengthBinaryResolveTypes(ResolveScalarFunctionTypesInput &input) { diff --git a/src/duckdb/src/function/scalar/string/regexp.cpp b/src/duckdb/src/function/scalar/string/regexp.cpp index cdc2b5edb..8143e451b 100644 --- a/src/duckdb/src/function/scalar/string/regexp.cpp +++ b/src/duckdb/src/function/scalar/string/regexp.cpp @@ -415,6 +415,15 @@ static unique_ptr RegexExtractBind(BindScalarFunctionInput &input) } } + if (constant_pattern && constant_string.find("\\C") == string::npos) { + auto validation_options = options; + validation_options.set_log_errors(false); + if (RE2(constant_string, validation_options).ok()) { + // A valid UTF-8 pattern without byte escapes cannot return a partial character. + bound_function.SetErrorMode(FunctionErrors::CANNOT_ERROR); + } + } + return make_uniq(options, std::move(constant_string), constant_pattern, group_index, no_match_returns_input); } diff --git a/src/duckdb/src/function/scalar/system/aggregate_export.cpp b/src/duckdb/src/function/scalar/system/aggregate_export.cpp index 7a598344f..d43e1de71 100644 --- a/src/duckdb/src/function/scalar/system/aggregate_export.cpp +++ b/src/duckdb/src/function/scalar/system/aggregate_export.cpp @@ -754,10 +754,14 @@ unique_ptr BindToAggregateState(INPUT &input) { "constant"); } } - auto function_name_val = input.GetNonNullConstant(1); + // the arguments are not yet cast to the parameter types when binding + auto function_name_val = input.GetNonNullConstant(1).DefaultCastAs(LogicalType::VARCHAR); auto function_name = StringValue::Get(function_name_val); auto signature_val = input.GetNonNullConstant(2); + if (signature_val.type().id() != LogicalTypeId::LIST) { + throw BinderException("to_aggregate_state: the signature must be a list of types"); + } // the signature lists all of the argument types in order vector argument_types; for (auto &arg : ListValue::GetChildren(signature_val)) { @@ -905,6 +909,20 @@ unique_ptr ExportAggregateFunction::StateToSQL(const LogicalTy return make_uniq(QualifiedName("system", "main", "to_aggregate_state"), std::move(arguments)); } +void ExportAggregateFunction::GetSortedStateOrders(const LogicalType &state_type, + vector &orders) { + auto ext_info = state_type.GetExtensionInfo(); + if (!ext_info || state_type.id() != LogicalTypeId::LIST) { + throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); + } + auto order_entry = ext_info->properties.find("order_bys"); + if (order_entry == ext_info->properties.end()) { + throw InternalException("GetSortedStateOrders called on a state type that is not an ordered aggregate state"); + } + auto &buffer_struct = ListType::GetChildType(state_type); + ParseOrderBys(order_entry->second, StructType::GetChildTypes(buffer_struct).size(), orders); +} + void ExportAggregateFunction::SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout) { auto &bound_function = aggregate.FunctionMutable(); // functions with an explicit export callback use it as the finalize; others use the field-based serialization diff --git a/src/duckdb/src/function/table/arrow_conversion.cpp b/src/duckdb/src/function/table/arrow_conversion.cpp index 89a30178f..b2bbae167 100644 --- a/src/duckdb/src/function/table/arrow_conversion.cpp +++ b/src/duckdb/src/function/table/arrow_conversion.cpp @@ -48,6 +48,50 @@ T *ArrowBufferData(ArrowArray &array, idx_t buffer_idx) { return (T *)array.buffers[buffer_idx]; // NOLINT } +static bool IsAligned(const_data_ptr_t ptr, idx_t alignment) { + return reinterpret_cast(ptr) % alignment == 0; +} + +//! Arrow buffers are not required to be aligned to their value type, and reading an unaligned buffer through a +//! typed pointer is undefined behavior. This reads the values in place when they are aligned, and from an aligned +//! copy of the [offset, offset + count) range when they are not. +template +class ArrowAlignedBufferData { +public: + ArrowAlignedBufferData(const ArrowArray &array, idx_t buffer_idx, idx_t offset, idx_t count) { + auto source = static_cast(array.buffers[buffer_idx]) + offset * sizeof(T); + if (IsAligned(source, alignof(T))) { + data = reinterpret_cast(source); + return; + } + copy = make_unsafe_uniq_array_uninitialized(count); + memcpy(static_cast(copy.get()), source, count * sizeof(T)); + data = copy.get(); + } + + const T &operator[](idx_t idx) const { + return data[idx]; + } + const T *get() const { + return data; + } + +private: + const T *data; + unsafe_unique_array copy; +}; + +//! Points the vector to the values when they are aligned to their type, and copies them into it when they are not +static void SetVectorData(Vector &vector, data_ptr_t data, idx_t size) { + auto type_size = GetTypeIdSize(vector.GetType().InternalType()); + if (IsAligned(data, MinValue(type_size, sizeof(uint64_t)))) { + FlatVector::SetData(vector, data, count_t(size)); + return; + } + memcpy(FlatVector::GetDataMutable(vector), data, type_size * size); + FlatVector::SetSize(vector, size); +} + static void GetValidityMask(ValidityMask &mask, ArrowArray &array, idx_t chunk_offset, idx_t size, int64_t parent_offset, int64_t nested_offset = -1, bool add_null = false) { // In certain we don't need to or cannot copy arrow's validity mask to duckdb. @@ -126,7 +170,7 @@ static ArrowListOffsetData ConvertArrowListOffsetsTemplated(Vector &vector, Arro } idx_t cur_offset = 0; - auto offsets = ArrowBufferData(array, 1) + effective_offset; + ArrowAlignedBufferData offsets(array, 1, effective_offset, size + 1); start_offset = offsets[0]; auto list_data = FlatVector::Writer(vector, size); for (idx_t i = 0; i < size; i++) { @@ -149,8 +193,8 @@ static ArrowListOffsetData ConvertArrowListViewOffsetsTemplated(Vector &vector, auto &list_size = result.list_size; list_size = 0; - auto offsets = ArrowBufferData(array, 1) + effective_offset; - auto sizes = ArrowBufferData(array, 2) + effective_offset; + ArrowAlignedBufferData offsets(array, 1, effective_offset, size); + ArrowAlignedBufferData sizes(array, 2, effective_offset, size); // In ListArrays the offsets have to be sequential // ListViewArrays do not have this same constraint @@ -342,7 +386,7 @@ static void ArrowToDuckDBMapVerify(const Vector &vector, idx_t count) { } template -static void SetVectorString(Vector &vector, idx_t size, char *cdata, T *offsets) { +static void SetVectorString(Vector &vector, idx_t size, char *cdata, const T *offsets) { auto strings = FlatVector::GetDataMutable(vector); for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -359,7 +403,7 @@ static void SetVectorString(Vector &vector, idx_t size, char *cdata, T *offsets) static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, idx_t current_pos) { auto strings = FlatVector::Writer(vector, size); - auto arrow_string = ArrowBufferData(array, 1) + current_pos; + ArrowAlignedBufferData arrow_string(array, 1, current_pos, size); for (idx_t row_idx = 0; row_idx < size; row_idx++) { if (FlatVector::IsNull(vector, row_idx)) { @@ -372,7 +416,7 @@ static void SetVectorStringView(Vector &vector, idx_t size, ArrowArray &array, i // | Bytes 0-3 | Bytes 4-15 | // |------------|---------------------------------------| // | length | data (padded with 0) | - strings.WriteStringRef(string_t(arrow_string[row_idx].GetInlineData(), length)); + strings.WriteValue(string_t(arrow_string[row_idx].GetInlineData(), length)); } else { // This string is not inlined, we have to check a different buffer and offsets // | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | @@ -393,7 +437,7 @@ static void DirectConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs auto data_ptr = ArrowBufferData(array, 1) + internal_type * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data_ptr, count_t(size)); + SetVectorData(vector, data_ptr, size); } template @@ -401,8 +445,8 @@ static void TimeConversion(Vector &vector, ArrowArray &array, idx_t chunk_offset int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_writer = FlatVector::Writer(vector, size); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), + size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { int64_t result; @@ -431,8 +475,8 @@ static void TimeNSConversion(Vector &vector, ArrowArray &array, idx_t chunk_offs int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr(array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), + size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { // dtime_ns_t.micros actually holds nanos (!) @@ -457,8 +501,8 @@ static void UUIDConversion(Vector &vector, const ArrowArray &array, idx_t chunk_ int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = static_cast(array.buffers[1]) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { tgt_ptr[row].lower = static_cast(BSwapIfLE(src_ptr[row].upper)); @@ -483,8 +527,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); auto &validity_mask = FlatVector::ValidityMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); if (validity_mask.CannotHaveNull()) { for (idx_t row = 0; row < size; row++) { if (!TryMultiplyOperator::Operation(src_ptr[row], conversion, tgt_ptr[row].value)) { @@ -506,8 +550,8 @@ static void TimestampTZConversion(Vector &vector, ArrowArray &array, idx_t chunk static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size, int64_t conversion) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].months = 0; @@ -520,8 +564,8 @@ static void IntervalConversionUs(Vector &vector, ArrowArray &array, idx_t chunk_ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = - ArrowBufferData(array, 1) + GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = 0; tgt_ptr[row].micros = 0; @@ -532,8 +576,8 @@ static void IntervalConversionMonths(Vector &vector, ArrowArray &array, idx_t ch static void IntervalConversionMonthDayNanos(Vector &vector, ArrowArray &array, idx_t chunk_offset, int64_t nested_offset, int64_t parent_offset, idx_t size) { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, parent_offset, chunk_offset, nested_offset), size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].days = src_ptr[row].days; tgt_ptr[row].micros = src_ptr[row].nanoseconds / Interval::NANOS_PER_MICRO; @@ -758,9 +802,15 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBRunEndEncoded(Vector &vector, c } } template -void ConvertDecimal(SRC src_ptr, Vector &vector, ArrowArray &array, idx_t size, int64_t nested_offset, - uint64_t parent_offset, idx_t chunk_offset, ValidityMask &val_mask, - DecimalBitWidth arrow_bit_width) { +static void ConvertDecimal(Vector &vector, ArrowArray &array, idx_t size, idx_t offset) { + if (sizeof(SRC) == GetTypeIdSize(vector.GetType().InternalType())) { + auto data = ArrowBufferData(array, 1) + offset * sizeof(SRC); + SetVectorData(vector, data, size); + return; + } + + ArrowAlignedBufferData src_ptr(array, 1, offset, size); + auto &val_mask = FlatVector::Validity(vector); switch (vector.GetType().InternalType()) { case PhysicalType::INT16: { auto tgt_ptr = FlatVector::GetDataMutable(vector); @@ -774,58 +824,36 @@ void ConvertDecimal(SRC src_ptr, Vector &vector, ArrowArray &array, idx_t size, break; } case PhysicalType::INT32: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_32) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } break; } case PhysicalType::INT64: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_64) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } break; } case PhysicalType::INT128: { - if (arrow_bit_width == DecimalBitWidth::DECIMAL_128) { - auto data = ArrowBufferData(array, 1) + - GetTypeIdSize(vector.GetType().InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - FlatVector::SetData(vector, data, count_t(size)); - } else { - auto tgt_ptr = FlatVector::GetDataMutable(vector); - for (idx_t row = 0; row < size; row++) { - if (val_mask.RowIsValid(row)) { - auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); - D_ASSERT(result); - (void)result; - } + auto tgt_ptr = FlatVector::GetDataMutable(vector); + for (idx_t row = 0; row < size; row++) { + if (val_mask.RowIsValid(row)) { + auto result = TryCast::Operation(src_ptr[row], tgt_ptr[row]); + D_ASSERT(result); + (void)result; } } - break; } default: @@ -918,16 +946,18 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar switch (size_type) { case ArrowVariableSizeType::SUPER_SIZE: { auto cdata = ArrowBufferData(array, 2); - auto offsets = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - SetVectorString(vector, size, cdata, offsets); + ArrowAlignedBufferData offsets( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size + 1); + SetVectorString(vector, size, cdata, offsets.get()); break; } case ArrowVariableSizeType::NORMAL: { auto cdata = ArrowBufferData(array, 2); - auto offsets = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - SetVectorString(vector, size, cdata, offsets); + ArrowAlignedBufferData offsets( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size + 1); + SetVectorString(vector, size, cdata, offsets.get()); break; } case ArrowVariableSizeType::VIEW: { @@ -968,8 +998,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::MILLISECONDS: { //! convert date from nanoseconds to days - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); auto tgt_ptr = FlatVector::GetDataMutable(vector); for (idx_t row = 0; row < size; row++) { tgt_ptr[row] = date_t(UnsafeNumericCast(static_cast(src_ptr[row]) / @@ -1003,8 +1034,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1064,8 +1096,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].value = src_ptr[row] / 1000; } @@ -1122,8 +1155,9 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar } case ArrowDateTimeType::NANOSECONDS: { auto tgt_ptr = FlatVector::GetDataMutable(vector); - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + ArrowAlignedBufferData src_ptr( + array, 1, GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset), + size); for (idx_t row = 0; row < size; row++) { tgt_ptr[row].micros = src_ptr[row] / 1000; tgt_ptr[row].days = 0; @@ -1147,34 +1181,18 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDB(Vector &vector, ArrowArray &ar break; } case LogicalTypeId::DECIMAL: { - auto &val_mask = FlatVector::ValidityMutable(vector); auto &datetime_info = arrow_type.GetTypeInfo(); - auto bit_width = datetime_info.GetBitWidth(); - - switch (bit_width) { - case DecimalBitWidth::DECIMAL_32: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + auto offset = GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + switch (datetime_info.GetBitWidth()) { + case DecimalBitWidth::DECIMAL_32: + ConvertDecimal(vector, array, size, offset); break; - } - - case DecimalBitWidth::DECIMAL_64: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + case DecimalBitWidth::DECIMAL_64: + ConvertDecimal(vector, array, size, offset); break; - } - - case DecimalBitWidth::DECIMAL_128: { - auto src_ptr = ArrowBufferData(array, 1) + - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); - ConvertDecimal(src_ptr, vector, array, size, nested_offset, parent_offset, chunk_offset, val_mask, - bit_width); + case DecimalBitWidth::DECIMAL_128: + ConvertDecimal(vector, array, size, offset); break; - } default: throw NotImplementedException("Unsupported precision for Arrow Decimal Type."); } @@ -1488,9 +1506,16 @@ void ArrowToDuckDBConversion::ColumnArrowToDuckDBDictionary(Vector &vector, Arro } auto offset_type = arrow_type.GetDuckType(); //! Get Pointer to Indices of Dictionary - auto indices = ArrowBufferData(array, 1) + - GetTypeIdSize(offset_type.InternalType()) * - GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + auto index_size = GetTypeIdSize(offset_type.InternalType()); + auto indices = + ArrowBufferData(array, 1) + + index_size * GetEffectiveOffset(array, NumericCast(parent_offset), chunk_offset, nested_offset); + unsafe_unique_array aligned_indices; + if (!IsAligned(indices, index_size)) { + aligned_indices = make_unsafe_uniq_array_uninitialized((index_size * size + 7) / 8); + memcpy(aligned_indices.get(), indices, index_size * size); + indices = reinterpret_cast(aligned_indices.get()); + } SelectionVector sel; if (has_nulls) { diff --git a/src/duckdb/src/function/table/checkpoint.cpp b/src/duckdb/src/function/table/checkpoint.cpp index a59e3dd9d..c696a4d4d 100644 --- a/src/duckdb/src/function/table/checkpoint.cpp +++ b/src/duckdb/src/function/table/checkpoint.cpp @@ -11,7 +11,7 @@ struct CheckpointBindData : public FunctionData { explicit CheckpointBindData(optional_ptr db) : db(db) { } - optional_ptr db; + mutable optional_ptr db; public: unique_ptr Copy() const override { @@ -49,7 +49,7 @@ static unique_ptr CheckpointBind(ClientContext &context, TableFunc template static void TemplatedCheckpointFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { auto &bind_data = data_p.bind_data->Cast(); - auto &transaction_manager = TransactionManager::Get(*bind_data.db.get_mutable()); + auto &transaction_manager = TransactionManager::Get(*bind_data.db); transaction_manager.Checkpoint(context, FORCE); } diff --git a/src/duckdb/src/function/table/range.cpp b/src/duckdb/src/function/table/range.cpp index 8af91754c..038189e31 100644 --- a/src/duckdb/src/function/table/range.cpp +++ b/src/duckdb/src/function/table/range.cpp @@ -262,7 +262,8 @@ struct RangeDateTimeBindData : public TableFunctionData { return; } - cardinality = idx_t(delta / increment); + // a step pointing away from the end produces an empty range + cardinality = delta / increment < 0 ? 0 : idx_t(delta / increment); } idx_t cardinality; diff --git a/src/duckdb/src/function/table/system/create_external_resource.cpp b/src/duckdb/src/function/table/system/create_external_resource.cpp index 046a3678c..65a585e5f 100644 --- a/src/duckdb/src/function/table/system/create_external_resource.cpp +++ b/src/duckdb/src/function/table/system/create_external_resource.cpp @@ -212,6 +212,11 @@ static void CreateExternalResourceFunction(ClientContext &context, TableFunction type->create_function); } handle = RequireResourceMap(result->Collection().GetRows().GetValue(0, 0), type->create_function, "handle"); + if (handle.IsNull()) { + // the handle identifies the resource for status and teardown - without one it can never be destroyed + throw InvalidInputException("create_external_resource: create function \"%s\" returned a NULL handle", + type->create_function); + } LogExternalResourceOperation(context, bind_data.type_name, bind_data.resource_name, "create", string(), NoExtraInfo()); } catch (std::exception &ex) { diff --git a/src/duckdb/src/function/table/system/enable_profiling.cpp b/src/duckdb/src/function/table/system/enable_profiling.cpp index 0b830beb6..f5979b71e 100644 --- a/src/duckdb/src/function/table/system/enable_profiling.cpp +++ b/src/duckdb/src/function/table/system/enable_profiling.cpp @@ -1,5 +1,6 @@ #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/client_context.hpp" +#include "duckdb/main/config.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/common/file_system.hpp" #include "duckdb/main/profiler/gathered_metrics.hpp" @@ -89,6 +90,21 @@ static unique_ptr BindEnableProfiling(ClientContext &context, Tabl // The metrics are placed by position however they were passed; the overload settled pattern or list of names bind_data->metrics = input.inputs[0]; + auto &config = DBConfig::GetConfig(context); + config.CheckLock(EnableProfilingSetting::Name); + if (!bind_data->coverage.IsNull()) { + config.CheckLock(ProfilingCoverageSetting::Name); + } + if (!bind_data->save_location.IsNull()) { + config.CheckLock(ProfilingOutputSetting::Name); + } + if (!bind_data->mode.IsNull()) { + config.CheckLock(ProfilingModeSetting::Name); + } + if (!bind_data->metrics.IsNull()) { + config.CheckLock(TrackedMetricsSetting::Name); + } + return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); @@ -102,6 +118,7 @@ static void DisableProfiling(ClientContext &context, TableFunctionInput &data, D static unique_ptr BindDisableProfiling(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { + DBConfig::GetConfig(context).CheckLock(EnableProfilingSetting::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/system/external_resources.cpp b/src/duckdb/src/function/table/system/external_resources.cpp index faef29aa9..c612011ed 100644 --- a/src/duckdb/src/function/table/system/external_resources.cpp +++ b/src/duckdb/src/function/table/system/external_resources.cpp @@ -241,6 +241,9 @@ static unique_ptr RegisterExternalResourceBind(ClientContext &cont if (input.inputs[0].IsNull() || input.inputs[1].IsNull()) { throw InvalidInputException("register_external_resource: the type and name must not be NULL"); } + if (input.inputs[2].IsNull()) { + throw InvalidInputException("register_external_resource: the handle must not be NULL"); + } resource.type = StringValue::Get(input.inputs[0]); resource.name = StringValue::Get(input.inputs[1]); // The handle is the durable identity and doubles as the deleter payload. diff --git a/src/duckdb/src/function/table/system/logging_utils.cpp b/src/duckdb/src/function/table/system/logging_utils.cpp index 198e83b5d..1b1a0004d 100644 --- a/src/duckdb/src/function/table/system/logging_utils.cpp +++ b/src/duckdb/src/function/table/system/logging_utils.cpp @@ -6,6 +6,8 @@ #include "duckdb/logging/log_manager.hpp" #include "duckdb/logging/logging.hpp" #include "duckdb/main/client_context.hpp" +#include "duckdb/main/config.hpp" +#include "duckdb/main/settings.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_entries.hpp" #include "duckdb/main/extension_helper.hpp" @@ -68,6 +70,13 @@ static unique_ptr BindEnableLogging(ClientContext &context, TableF throw InvalidInputException("EnableLogging: expected 0 or 1 parameter"); } + auto &config = DBConfig::GetConfig(context); + config.CheckLock(EnableLogging::Name); + config.CheckLock(EnabledLogTypes::Name); + config.CheckLock(LoggingLevel::Name); + config.CheckLock(LoggingMode::Name); + config.CheckLock(LoggingStorage::Name); + auto result = make_uniq(); bool storage_isset = false; @@ -170,6 +179,7 @@ static void TruncateLogs(ClientContext &context, TableFunctionInput &data, DataC static unique_ptr BindDisableLogging(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { + DBConfig::GetConfig(context).CheckLock(EnableLogging::Name); return_types.emplace_back(LogicalType::BOOLEAN); names.emplace_back("Success"); diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 44ac31f20..22461be92 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48043" +#define DUCKDB_PATCH_VERSION "0-alpha48178" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48043" +#define DUCKDB_VERSION "v2.0.0-alpha48178" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "43690c453d" +#define DUCKDB_SOURCE_ID "57d34370c8" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/window/window_value_function.cpp b/src/duckdb/src/function/window/window_value_function.cpp index a9735aa08..6d9e66ecc 100644 --- a/src/duckdb/src/function/window/window_value_function.cpp +++ b/src/duckdb/src/function/window/window_value_function.cpp @@ -628,6 +628,19 @@ unique_ptr WindowLeadLagExecutor::GetLocal(ExecutionContext &con return make_uniq(context, glstate); } +//! The index of the row at the offset - saturated if it does not fit, as it is outside the partition anyway +static int64_t LeadLagIndex(ExpressionType type, int64_t row_idx, int64_t offset) { + int64_t result; + if (type == ExpressionType::WINDOW_LEAD) { + if (!TryAddOperator::Operation(row_idx, offset, result)) { + result = offset < 0 ? NumericLimits::Minimum() : NumericLimits::Maximum(); + } + } else if (!TrySubtractOperator::Operation(row_idx, offset, result)) { + result = offset < 0 ? NumericLimits::Maximum() : NumericLimits::Minimum(); + } + return result; +} + void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_chunk, DataChunk &bounds, Vector &result, idx_t row_idx, OperatorSinkInput &sink) { auto &glstate = sink.global_state.Cast(); @@ -668,12 +681,7 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c frame = FrameBounds(frame_begin[i], MaxValue(frame_end[i], frame_begin[i])); const auto own_row = glstate.row_tree->Rank(frame.start, frame.end, row_idx) - 1; // (2) adjust the row number by adding or subtracting an offset - auto val_idx = NumericCast(own_row); - if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { - val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); - } else { - val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); - } + const auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(own_row), offset); const auto frame_width = NumericCast(frame.end - frame.start); if (val_idx >= 0 && val_idx < frame_width) { // (3) find the row at that offset @@ -727,12 +735,7 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c } offset = leadlag_offset.GetCell(i); } - int64_t val_idx = (int64_t)row_idx; - if (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD) { - val_idx = AddOperatorOverflowCheck::Operation(val_idx, offset); - } else { - val_idx = SubtractOperatorOverflowCheck::Operation(val_idx, offset); - } + auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(row_idx), offset); idx_t delta = 0; if (val_idx < (int64_t)row_idx) { @@ -1546,7 +1549,8 @@ void WindowFillExecutor::GetSharing(WindowExecutor &executor, WindowSharedExpres if (arg_order_idx.empty()) { // We use the range ordering, even if it has not been defined if (executor.range_idx == DConstants::INVALID_INDEX) { - D_ASSERT(wexpr.OrderBy().size() == 1); + // the first ordering - also when it is shared with an argument ordering that is a prefix of the orderings + D_ASSERT(!wexpr.OrderBy().empty()); // We don't need the validity mask because we have also requested the valid range for the ordering. executor.range_idx = shared.RegisterCollection(wexpr.OrderBy()[0].expression, false); } diff --git a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp index cc7ef078c..b859af388 100644 --- a/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp +++ b/src/duckdb/src/include/duckdb/catalog/catalog_entry/sequence_catalog_entry.hpp @@ -64,6 +64,8 @@ class SequenceCatalogEntry : public StandardEntry { int64_t NextValue(DuckTransaction &transaction); int64_t SetValue(DuckTransaction &transaction, int64_t value, bool is_called); void ReplayValue(uint64_t usage_count, int64_t counter, optional last_value); + //! Derive the last value from the counter - storage versions prior to v2.0.0 do not store it + void DeriveLastValue(); string ToSQL() const override; diff --git a/src/duckdb/src/include/duckdb/common/allocator.hpp b/src/duckdb/src/include/duckdb/common/allocator.hpp index 12f9eb5fb..c69fbe2cf 100644 --- a/src/duckdb/src/include/duckdb/common/allocator.hpp +++ b/src/duckdb/src/include/duckdb/common/allocator.hpp @@ -125,6 +125,8 @@ class Allocator { DUCKDB_API static Allocator &DefaultAllocator(); DUCKDB_API static shared_ptr &DefaultAllocatorReference(); + //! Total mapped allocator memory, if supported by this allocator. + optional_idx GetMappedMemory() const; static bool SupportsFlush(); static optional_idx DecayDelay(); static void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count); diff --git a/src/duckdb/src/include/duckdb/common/enum_util.hpp b/src/duckdb/src/include/duckdb/common/enum_util.hpp index 6a2297a91..5159f95e2 100644 --- a/src/duckdb/src/include/duckdb/common/enum_util.hpp +++ b/src/duckdb/src/include/duckdb/common/enum_util.hpp @@ -292,6 +292,8 @@ enum class HLLStorageType : uint8_t; enum class HTTPClientCachePolicy : uint8_t; +enum class HTTPRetryOutcomeOwner : uint8_t; + enum class HTTPStatusCode : uint16_t; enum class HTTPTransportReusePolicy : uint8_t; @@ -1059,6 +1061,9 @@ const char* EnumUtil::ToChars(HLLStorageType value); template<> const char* EnumUtil::ToChars(HTTPClientCachePolicy value); +template<> +const char* EnumUtil::ToChars(HTTPRetryOutcomeOwner value); + template<> const char* EnumUtil::ToChars(HTTPStatusCode value); @@ -2014,6 +2019,9 @@ HLLStorageType EnumUtil::FromString(const char *value); template<> HTTPClientCachePolicy EnumUtil::FromString(const char *value); +template<> +HTTPRetryOutcomeOwner EnumUtil::FromString(const char *value); + template<> HTTPStatusCode EnumUtil::FromString(const char *value); diff --git a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp index b0d0e9c2d..10617fde2 100644 --- a/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp +++ b/src/duckdb/src/include/duckdb/common/exception/http_exception.hpp @@ -23,6 +23,11 @@ class HTTPException : public Exception { explicit HTTPException(const string &message) : Exception(ExceptionType::HTTP, message) { } + //! Rebuild an HTTP exception from extra info captured earlier (e.g. to extend its message). + explicit HTTPException(const unordered_map &extra_info, const string &message) + : Exception(extra_info, ExceptionType::HTTP, message) { + } + template ::status = 0, typename... ARGS> explicit HTTPException(RESPONSE &response, const string &msg, ARGS &&...params) : HTTPException(static_cast(response.status), response.body, response.headers, response.reason, msg, diff --git a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp index 534020b92..a5b0d97fc 100644 --- a/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp +++ b/src/duckdb/src/include/duckdb/common/hive_partitioning.hpp @@ -8,6 +8,7 @@ #pragma once +#include "duckdb/common/identifier.hpp" #include "duckdb/common/types/column/partitioned_column_data.hpp" #include "duckdb/common/open_file_info.hpp" #include "duckdb/original/std/sstream.hpp" @@ -18,7 +19,8 @@ namespace duckdb { struct MultiFilePushdownInfo; struct HivePartitioningFilterInfo { - unordered_map column_map; + identifier_map_t column_map; + Identifier filename_column; bool hive_enabled; bool filename_enabled; }; diff --git a/src/duckdb/src/include/duckdb/common/hugeint.hpp b/src/duckdb/src/include/duckdb/common/hugeint.hpp index acdc4fb4b..844106506 100644 --- a/src/duckdb/src/include/duckdb/common/hugeint.hpp +++ b/src/duckdb/src/include/duckdb/common/hugeint.hpp @@ -17,7 +17,9 @@ struct hugeint_t { // NOLINT: use numeric casing public: hugeint_t() = default; - DUCKDB_API hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` + //! constexpr so that arithmetic against a constant operand can be folded at compile + //! time; the body is at the bottom of this header + constexpr hugeint_t(int64_t value); // NOLINT: Allow implicit conversion from `int64_t` constexpr hugeint_t(int64_t upper, uint64_t lower) : lower(lower), upper(upper) { } constexpr hugeint_t(const hugeint_t &rhs) = default; @@ -28,20 +30,22 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API string ToString() const; // comparison operators - DUCKDB_API bool operator==(const hugeint_t &rhs) const; - DUCKDB_API bool operator!=(const hugeint_t &rhs) const; - DUCKDB_API bool operator<=(const hugeint_t &rhs) const; - DUCKDB_API bool operator<(const hugeint_t &rhs) const; - DUCKDB_API bool operator>(const hugeint_t &rhs) const; - DUCKDB_API bool operator>=(const hugeint_t &rhs) const; + //! constexpr so that comparisons fold at compile time + constexpr bool operator==(const hugeint_t &rhs) const; + constexpr bool operator!=(const hugeint_t &rhs) const; + constexpr bool operator<=(const hugeint_t &rhs) const; + constexpr bool operator<(const hugeint_t &rhs) const; + constexpr bool operator>(const hugeint_t &rhs) const; + constexpr bool operator>=(const hugeint_t &rhs) const; // arithmetic operators - DUCKDB_API hugeint_t operator+(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator-(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator*(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator/(const hugeint_t &rhs) const; + //! constexpr so the compiler can fold these when an operand is a compile-time constant + constexpr hugeint_t operator+(const hugeint_t &rhs) const; + constexpr hugeint_t operator-(const hugeint_t &rhs) const; + constexpr hugeint_t operator*(const hugeint_t &rhs) const; + constexpr hugeint_t operator/(const hugeint_t &rhs) const; DUCKDB_API hugeint_t operator%(const hugeint_t &rhs) const; - DUCKDB_API hugeint_t operator-() const; + constexpr hugeint_t operator-() const; // bitwise operators DUCKDB_API hugeint_t operator>>(const hugeint_t &rhs) const; @@ -79,6 +83,321 @@ struct hugeint_t { // NOLINT: use numeric casing DUCKDB_API explicit operator uhugeint_t() const; }; +//===--------------------------------------------------------------------===// +// constexpr 128-bit arithmetic +// +// These live here rather than next to the rest of Hugeint because +// duckdb/common/types/hugeint.hpp includes duckdb/common/limits.hpp, which +// includes this file back. Everything the digit-parsing loops need has to be +// reachable from here, and the bodies are at the bottom of the file because a +// member function body defined inside the class is looked up at the class's +// closing brace -- above the helpers below. +//===--------------------------------------------------------------------===// + +//! NumericLimits::Minimum()/Maximum(), spelled out because limits.hpp +//! includes this file back so the traits cannot be used here +constexpr hugeint_t HugeintMinimum() { + return hugeint_t(INT64_MIN, 0); +} + +constexpr hugeint_t HugeintMaximum() { + return hugeint_t(INT64_MAX, UINT64_MAX); +} + +constexpr bool HugeintEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper == rhs.upper && lhs.lower == rhs.lower; +} + +constexpr bool HugeintNotEquals(hugeint_t lhs, hugeint_t rhs) { + return !HugeintEquals(lhs, rhs); +} + +constexpr bool HugeintGreaterThan(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower > rhs.lower); +} + +constexpr bool HugeintGreaterThanEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper > rhs.upper || (lhs.upper == rhs.upper && lhs.lower >= rhs.lower); +} + +constexpr bool HugeintLessThan(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower < rhs.lower); +} + +constexpr bool HugeintLessThanEquals(hugeint_t lhs, hugeint_t rhs) { + return lhs.upper < rhs.upper || (lhs.upper == rhs.upper && lhs.lower <= rhs.lower); +} + +//! Two's-complement negation, written out because the operators below cannot call +//! operator-() on themselves without the fold failing +constexpr hugeint_t HugeintNegate(hugeint_t n) { + return hugeint_t(static_cast(~static_cast(n.upper) + (n.lower == 0)), 0 - n.lower); +} + +constexpr int HugeintSign(hugeint_t n) { + // the sign is not decided by upper alone: upper == 0 with a non-zero lower is positive + return int(HugeintGreaterThan(n, hugeint_t(0))) - int(HugeintLessThan(n, hugeint_t(0))); +} + +constexpr hugeint_t HugeintAbs(hugeint_t n) { + return n.upper < 0 ? HugeintNegate(n) : n; +} + +constexpr hugeint_t HugeintAddOne(hugeint_t n) { + const int carry = (n.lower == UINT64_MAX) ? 1 : 0; + return hugeint_t(static_cast(static_cast(n.upper) + carry), n.lower + 1); +} + +constexpr hugeint_t HugeintSubtractOne(hugeint_t n) { + // lower wraps to UINT64_MAX, so a borrow is taken out of upper + const int borrow = (n.lower == 0) ? 1 : 0; + return hugeint_t(static_cast(static_cast(n.upper) - borrow), n.lower - 1); +} + +//! Helpers for the bit-at-a-time division, from the same uint128_t reference the +//! runtime implementation uses +constexpr uint8_t HugeintHighestBit(hugeint_t bits) { + uint8_t out = 0; + if (bits.upper) { + // bit index 64 is the lowest bit of upper + out = 64; + uint64_t up = static_cast(bits.upper); + while (up) { + up >>= 1; + out++; + } + } else { + uint64_t low = bits.lower; + while (low) { + low >>= 1; + out++; + } + } + return out; +} + +constexpr bool HugeintIsBitSet(hugeint_t lhs, uint8_t bit_position) { + if (bit_position < 64) { + return lhs.lower & (uint64_t(1) << uint64_t(bit_position)); + } + return static_cast(lhs.upper) & (uint64_t(1) << uint64_t(bit_position - 64)); +} + +//! Shift left by 1..63 bits. Called by the division loop below with amount == 1. +//! Takes no D_ASSERT: in assertion builds that macro is an unconditional function call, which +//! would make this function unusable in a constant expression. +constexpr hugeint_t HugeintLeftShift(hugeint_t lhs, uint32_t amount) { + hugeint_t result {0, 0}; + result.lower = lhs.lower << amount; + result.upper = static_cast((static_cast(lhs.upper) << amount) + (lhs.lower >> (64 - amount))); + return result; +} + +constexpr hugeint_t HugeintMultiplyUnchecked(hugeint_t lhs, hugeint_t rhs) { + // Unchecked multiplication uses the low 128 bits, including for negative operands. +#if ((__GNUC__ >= 5) || defined(__clang__)) && defined(__SIZEOF_INT128__) + const auto left = (static_cast<__uint128_t>(static_cast(lhs.upper)) << 64) | lhs.lower; + const auto right = (static_cast<__uint128_t>(static_cast(rhs.upper)) << 64) | rhs.lower; + const auto result = left * right; + return hugeint_t(static_cast(result >> 64), static_cast(result)); +#else + // Multiply code adapted from: + // https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp + + // split values into 4 32-bit parts + uint64_t top[4] {uint64_t(lhs.upper) >> 32, uint64_t(lhs.upper) & 0xffffffff, lhs.lower >> 32, + lhs.lower & 0xffffffff}; + uint64_t bottom[4] {uint64_t(rhs.upper) >> 32, uint64_t(rhs.upper) & 0xffffffff, rhs.lower >> 32, + rhs.lower & 0xffffffff}; + uint64_t products[4][4] {}; + + // multiply each component of the values + for (idx_t x = 0; x < 4; x++) { + for (idx_t y = 0; y < 4; y++) { + products[x][y] = top[x] * bottom[y]; + } + } + + // first row + uint64_t fourth32 = (products[3][3] & 0xffffffff); + uint64_t third32 = (products[3][2] & 0xffffffff) + (products[3][3] >> 32); + uint64_t second32 = (products[3][1] & 0xffffffff) + (products[3][2] >> 32); + uint64_t first32 = (products[3][0] & 0xffffffff) + (products[3][1] >> 32); + + // second row + third32 += (products[2][3] & 0xffffffff); + second32 += (products[2][2] & 0xffffffff) + (products[2][3] >> 32); + first32 += (products[2][1] & 0xffffffff) + (products[2][2] >> 32); + + // third row + second32 += (products[1][3] & 0xffffffff); + first32 += (products[1][2] & 0xffffffff) + (products[1][3] >> 32); + + // fourth row + first32 += (products[0][3] & 0xffffffff); + + // move carry to next digit + third32 += fourth32 >> 32; + second32 += third32 >> 32; + first32 += second32 >> 32; + + // remove carry from current digit + fourth32 &= 0xffffffff; + third32 &= 0xffffffff; + second32 &= 0xffffffff; + first32 &= 0xffffffff; + + return hugeint_t(static_cast((first32 << 32) + second32), (third32 << 32) + fourth32); +#endif +} + +//! Result of a 128-bit division. Hugeint::DivMod's out-parameter form is declared in +//! types/hugeint.hpp, which is not visible from this header +struct HugeintDivModResult { + hugeint_t quotient; + hugeint_t remainder; +}; + +constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs); + +//! Precondition: lhs or rhs equals HugeintMinimum(). Only reached through HugeintDivMod. +//! Takes no D_ASSERT for the same reason as HugeintLeftShift. +constexpr HugeintDivModResult HugeintDivModMinimum(hugeint_t lhs, hugeint_t rhs) { + // rhs == MINIMUM: the quotient is 1 with remainder 0 only when lhs == MINIMUM; + // every other lhs has |lhs| < |MINIMUM|, so the quotient is 0 and the remainder is lhs + if (rhs.upper == INT64_MIN && rhs.lower == 0) { + if (lhs.upper == INT64_MIN && lhs.lower == 0) { + return HugeintDivModResult {hugeint_t(1), hugeint_t(0)}; + } + return HugeintDivModResult {hugeint_t(0), lhs}; + } + + auto shifted = HugeintDivMod(HugeintAddOne(lhs), rhs); + auto quotient = shifted.quotient; + auto remainder = shifted.remainder; + + // If the 1 mattered we need to adjust the result, otherwise the remainder + if (HugeintEquals(HugeintAddOne(HugeintAbs(remainder)), HugeintAbs(rhs))) { + quotient = HugeintSign(rhs) > 0 ? HugeintSubtractOne(quotient) : HugeintAddOne(quotient); + remainder = hugeint_t(0); + } else { + remainder = HugeintSubtractOne(remainder); + } + return HugeintDivModResult {quotient, remainder}; +} + +//! Bit-at-a-time 128-bit division, adapted from +//! https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp +constexpr HugeintDivModResult HugeintDivMod(hugeint_t lhs, hugeint_t rhs) { + if (HugeintEquals(rhs, hugeint_t(0))) { + return HugeintDivModResult {hugeint_t(0), lhs}; + } + + // MINIMUM cannot be negated, so it has to be handled before any sign is stripped + if (HugeintEquals(lhs, HugeintMinimum()) || HugeintEquals(rhs, HugeintMinimum())) { + return HugeintDivModMinimum(lhs, rhs); + } + + bool lhs_negative = lhs.upper < 0; + bool rhs_negative = rhs.upper < 0; + if (lhs_negative) { + lhs = HugeintNegate(lhs); + } + if (rhs_negative) { + rhs = HugeintNegate(rhs); + } + + hugeint_t quotient {0, 0}; + hugeint_t remainder {0, 0}; + + const uint8_t highest_bit_set = HugeintHighestBit(lhs); + for (uint8_t x = highest_bit_set; x > 0; x--) { + // left-shift the current quotient and remainder by 1 + quotient = HugeintLeftShift(quotient, 1); + remainder = HugeintLeftShift(remainder, 1); + + // we get the value of the bit at position X, where position 0 is the least-significant bit + if (HugeintIsBitSet(lhs, x - 1)) { + remainder.lower += 1; + if (remainder.lower == 0) { + remainder.upper += 1; + } + } + if (HugeintGreaterThanEquals(remainder, rhs)) { + // the remainder has passed the division multiplier: subtract the divisor, + // carrying a borrow out of lower, and add one to the quotient + const int64_t borrow = remainder.lower < rhs.lower ? 1 : 0; + remainder.lower -= rhs.lower; + remainder.upper -= rhs.upper + borrow; + quotient.lower += 1; + if (quotient.lower == 0) { + quotient.upper += 1; + } + } + } + if (lhs_negative ^ rhs_negative) { + quotient = HugeintNegate(quotient); + } + if (lhs_negative) { + remainder = HugeintNegate(remainder); + } + return HugeintDivModResult {quotient, remainder}; +} + +constexpr hugeint_t::hugeint_t(int64_t value) // NOLINT: Allow implicit conversion from `int64_t` + : lower(static_cast(value)), upper(value < 0 ? -1 : 0) { +} + +constexpr bool hugeint_t::operator==(const hugeint_t &rhs) const { + return HugeintEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator!=(const hugeint_t &rhs) const { + return HugeintNotEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator<(const hugeint_t &rhs) const { + return HugeintLessThan(*this, rhs); +} + +constexpr bool hugeint_t::operator<=(const hugeint_t &rhs) const { + return HugeintLessThanEquals(*this, rhs); +} + +constexpr bool hugeint_t::operator>(const hugeint_t &rhs) const { + return HugeintGreaterThan(*this, rhs); +} + +constexpr bool hugeint_t::operator>=(const hugeint_t &rhs) const { + return HugeintGreaterThanEquals(*this, rhs); +} + +constexpr hugeint_t hugeint_t::operator+(const hugeint_t &rhs) const { + return hugeint_t(static_cast(static_cast(upper) + static_cast(rhs.upper) + + ((lower + rhs.lower) < lower)), + lower + rhs.lower); +} + +constexpr hugeint_t hugeint_t::operator-(const hugeint_t &rhs) const { + return hugeint_t(static_cast(static_cast(upper) - static_cast(rhs.upper) - + ((lower - rhs.lower) > lower)), + lower - rhs.lower); +} + +constexpr hugeint_t hugeint_t::operator*(const hugeint_t &rhs) const { + return HugeintMultiplyUnchecked(*this, rhs); +} + +constexpr hugeint_t hugeint_t::operator/(const hugeint_t &rhs) const { + // this is the unchecked division: dividing by zero yields 0 and MINIMUM / -1 wraps, + // matching Hugeint::Divide. Callers that need overflow checks use TryDivMod. + return HugeintDivMod(*this, rhs).quotient; +} + +constexpr hugeint_t hugeint_t::operator-() const { + return HugeintNegate(*this); +} + } // namespace duckdb namespace std { diff --git a/src/duckdb/src/include/duckdb/common/mutex.hpp b/src/duckdb/src/include/duckdb/common/mutex.hpp index 11e3b9bd9..9d55a8141 100644 --- a/src/duckdb/src/include/duckdb/common/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/mutex.hpp @@ -34,6 +34,32 @@ class DUCKDB_CAPABILITY("mutex") annotated_mutex : public internal::mutex_impl_t } }; +// Annotated shared_mutex implementation. +class DUCKDB_CAPABILITY("mutex") annotated_shared_mutex : public internal::mutex_impl_t { +private: + using Impl = internal::mutex_impl_t; + +public: + void lock() DUCKDB_ACQUIRE() { + Impl::lock(); + } + void unlock() DUCKDB_RELEASE() { + Impl::unlock(); + } + bool try_lock() DUCKDB_TRY_ACQUIRE(true) { + return Impl::try_lock(); + } + void lock_shared() DUCKDB_ACQUIRE_SHARED() { + Impl::lock_shared(); + } + void unlock_shared() DUCKDB_RELEASE_SHARED() { + Impl::unlock_shared(); + } + bool try_lock_shared() DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_shared(); + } +}; + // Annotated lock_guard implementation. template class DUCKDB_SCOPED_CAPABILITY annotated_lock_guard : public internal::lock_impl_t> { @@ -99,6 +125,50 @@ class DUCKDB_SCOPED_CAPABILITY annotated_unique_lock : public internal::lock_imp } }; +// Annotated shared_lock implementation. +template +class DUCKDB_SCOPED_CAPABILITY annotated_shared_lock : public internal::lock_impl_t> { +private: + using Impl = internal::lock_impl_t>; + +public: + annotated_shared_lock() = default; + explicit annotated_shared_lock(M &m) DUCKDB_ACQUIRE_SHARED(m) : Impl(m) { + } + annotated_shared_lock(M &m, std::defer_lock_t t) noexcept DUCKDB_EXCLUDES(m) : Impl(m, t) { + } + annotated_shared_lock(M &m, std::try_to_lock_t t) DUCKDB_TRY_ACQUIRE_SHARED(true, m) : Impl(m, t) { + } + annotated_shared_lock(M &m, std::adopt_lock_t t) DUCKDB_REQUIRES_SHARED(m) : Impl(m, t) { + } + + // Disable copy and enable move. + annotated_shared_lock(const annotated_shared_lock &) = delete; + annotated_shared_lock &operator=(const annotated_shared_lock &) = delete; + annotated_shared_lock(annotated_shared_lock &&) = default; + annotated_shared_lock &operator=(annotated_shared_lock &&) = default; + + ~annotated_shared_lock() DUCKDB_RELEASE() = default; + + void lock() DUCKDB_ACQUIRE_SHARED() { + Impl::lock(); + } + bool try_lock() DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock(); + } + template + bool try_lock_for(const std::chrono::duration &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_for(timeout); + } + template + bool try_lock_until(const std::chrono::time_point &timeout) DUCKDB_TRY_ACQUIRE_SHARED(true) { + return Impl::try_lock_until(timeout); + } + void unlock() DUCKDB_RELEASE() { + Impl::unlock(); + } +}; + // Unannotated mutex type, which is alias for STL ones. using mutex = std::mutex; template diff --git a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp index 0f9b6a338..755515ff9 100644 --- a/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/decimal_cast_operators.hpp @@ -497,6 +497,17 @@ struct DecimalCastData { StoreType limit; }; +//! StoreType max/min divided by 10, for the overflow check in DecimalCastOperation::HandleDigit. +//! These are forced to be compile-time constants so the division is folded away: with a hugeint_t +//! store type it is a 128-bit division that would otherwise run for every digit. hugeint_t::operator/ +//! is constexpr, but it is too large to be inlined at the call site, so the compiler does not fold it +//! unless constant evaluation is actually required. +template +struct DecimalCastLimits { + static constexpr T MaximumDiv10 = NumericLimits::Maximum() / 10; + static constexpr T MinimumDiv10 = NumericLimits::Minimum() / 10; +}; + struct DecimalCastOperation { template static bool HandleDigit(T &state, uint8_t digit) { @@ -510,12 +521,12 @@ struct DecimalCastOperation { } state.digit_count++; if (NEGATIVE) { - if (state.result < (NumericLimits::Minimum() / 10)) { + if (state.result < DecimalCastLimits::MinimumDiv10) { return false; } state.result = state.result * 10 - digit; } else { - if (state.result > (NumericLimits::Maximum() / 10)) { + if (state.result > DecimalCastLimits::MaximumDiv10) { return false; } state.result = state.result * 10 + digit; diff --git a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp index 6e1fb2db5..4debd3d80 100644 --- a/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp +++ b/src/duckdb/src/include/duckdb/common/operator/integer_cast_operator.hpp @@ -83,7 +83,7 @@ struct IntegerDecimalCastData { using StoreType = int64_t; StoreType result; StoreType decimal; - uint16_t decimal_digits; + idx_t decimal_digits; }; template <> @@ -92,7 +92,7 @@ struct IntegerDecimalCastData { using StoreType = uint64_t; StoreType result; StoreType decimal; - uint16_t decimal_digits; + idx_t decimal_digits; }; struct IntegerDecimalCastOperation : IntegerCastOperation { @@ -129,12 +129,12 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { } // Handle decimals - e = UnsafeNumericCast(exponent - state.decimal_digits); + auto shift = static_cast(exponent) - static_cast(state.decimal_digits); store_t remainder = 0; - if (e < 0) { - if (static_cast(-e) < NumericLimits::Digits()) { + if (shift < 0) { + if (static_cast(-shift) < NumericLimits::Digits()) { store_t power = 1; - while (e++ < 0) { + while (shift++ < 0) { power *= 10; } remainder = state.decimal % power; @@ -145,7 +145,7 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { state.decimal = 0; } } else { - while (e-- > 0) { + while (shift-- > 0) { if (!TryMultiplyOperator::Operation(state.decimal, (store_t)10, state.decimal)) { return false; } @@ -168,7 +168,10 @@ struct IntegerDecimalCastOperation : IntegerCastOperation { template static bool HandleDecimal(T &state, uint8_t digit) { using store_t = typename T::StoreType; - if (DUCKDB_UNLIKELY(state.decimal > (NumericLimits::Maximum() - digit) / 10)) { + // digits past this position cannot affect the result for any int16_t exponent + static constexpr uint16_t MAX_DECIMAL_DIGITS = -NumericLimits::Minimum(); + if (DUCKDB_UNLIKELY(state.decimal_digits == MAX_DECIMAL_DIGITS || + state.decimal > (NumericLimits::Maximum() - digit) / 10)) { // Simply ignore any more decimals return true; } @@ -263,6 +266,10 @@ static bool IntegerCastLoop(const char *buf, idx_t len, T &result, bool strict) } } if (StringUtil::CharacterIsSpace(buf[pos])) { + if (pos == start_pos) { + // a sign followed only by whitespace is not a number + return false; + } // skip any trailing spaces while (++pos < len) { if (!StringUtil::CharacterIsSpace(buf[pos])) { diff --git a/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp b/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp new file mode 100644 index 000000000..773357fdc --- /dev/null +++ b/src/duckdb/src/include/duckdb/common/sorting/buffered_merge.hpp @@ -0,0 +1,192 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// duckdb/common/sorting/buffered_merge.hpp +// +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/common/algorithm.hpp" +#include "duckdb/common/helper.hpp" +#include "duckdb/common/typedefs.hpp" +#include "duckdb/common/allocator.hpp" +#include "duckdb/common/exception.hpp" + +namespace duckdb { + +//! Scratch buffer for merging sorted runs: the merge is correct without it, so the allocation must never fail a sort. +//! Starting at half the keys, the size is halved until the buffer manager can provide it (or it becomes too small to +//! be worth having). +class SortMergeBuffer { +public: + //! Number of keys the buffer holds at most: the smaller side of a merge is at most half the keys + static idx_t MaximumKeyCount(idx_t key_count) { + return key_count / 2 + 1; + } + //! Memory the buffer takes at most, so that it can be budgeted along with the keys it merges + static idx_t MaximumSize(idx_t key_count, idx_t key_size) { + return MaximumKeyCount(key_count) * key_size; + } + + SortMergeBuffer(Allocator &allocator, idx_t key_count, idx_t key_size) : count(0) { + static constexpr idx_t MINIMUM_KEYS = 1024; + for (idx_t keys = MaximumKeyCount(key_count); keys >= MINIMUM_KEYS; keys /= 2) { + try { + data = allocator.Allocate(keys * key_size); + count = keys; + return; + } catch (OutOfMemoryException &) { + // try a smaller buffer + } + } + } + + template + SORT_KEY *Keys() { + return reinterpret_cast(data.get()); + } + idx_t Count() const { + return count; + } + +private: + AllocatedData data; + idx_t count; +}; + +//! Rotates [first, last) so that [middle, last) comes before [first, middle) and returns the new middle. The smaller +//! of the two pieces goes through the scratch buffer when it fits, otherwise this is a plain std::rotate; neither +//! allocates. +template +ITERATOR BufferedRotate(ITERATOR first, ITERATOR middle, ITERATOR last, SORT_KEY *buffer, const idx_t buffer_count) { + const auto left_count = static_cast(middle - first); + const auto right_count = static_cast(last - middle); + if (left_count > right_count && right_count <= buffer_count) { + std::move(middle, last, buffer); + std::move_backward(first, middle, last); + std::move(buffer, buffer + right_count, first); + return first + right_count; + } + if (left_count <= buffer_count) { + std::move(first, middle, buffer); + std::move(middle, last, first); + std::move_backward(buffer, buffer + left_count, last); + return last - left_count; + } + return std::rotate(first, middle, last); +} + +//! Merges the sorted ranges [first, middle) and [middle, last) in place using the given scratch buffer, which may be +//! any size (including empty): nothing is allocated. When the smaller side fits in the buffer it is moved out and +//! merged back, forward when it is the left side and backward when it is the right side, which keeps equal keys in +//! order. Otherwise the larger side is split in half, the other side is split at the matching key by binary search, +//! the two middle pieces are rotated and both halves are merged recursively, until the pieces fit the buffer. Without a +//! buffer this is the O(n log n) in-place merge that std::inplace_merge uses when it cannot get memory, with a buffer +//! of half the keys it is a single linear pass, and anything in between makes progressively fewer passes. +template +void BufferedMerge(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare, SORT_KEY *buffer, + const idx_t buffer_count) { + while (true) { + const auto left_count = static_cast(middle - first); + const auto right_count = static_cast(last - middle); + if (left_count == 0 || right_count == 0) { + return; + } + // the ranges are already in order, nothing to merge + if (!compare(*middle, *(middle - 1))) { + return; + } + // one key on each side: swap them (the split below needs at least two keys on the larger side to progress) + if (left_count + right_count == 2) { + std::iter_swap(first, middle); + return; + } + if (left_count <= right_count && left_count <= buffer_count) { + // move the left side out and merge forward into [first, last) + std::move(first, middle, buffer); + auto buffer_end = buffer + left_count; + auto buffer_it = buffer; + auto right_it = middle; + auto out = first; + while (buffer_it != buffer_end && right_it != last) { + if (compare(*right_it, *buffer_it)) { + *out = std::move(*right_it); + ++right_it; + } else { + *out = std::move(*buffer_it); + ++buffer_it; + } + ++out; + } + std::move(buffer_it, buffer_end, out); + return; + } + if (right_count <= buffer_count) { + // move the right side out and merge backward into [first, last) + std::move(middle, last, buffer); + auto buffer_begin = buffer; + auto buffer_it = buffer + right_count; + auto left_it = middle; + auto out = last; + while (buffer_it != buffer_begin && left_it != first) { + --out; + if (compare(*(buffer_it - 1), *(left_it - 1))) { + --left_it; + *out = std::move(*left_it); + } else { + --buffer_it; + *out = std::move(*buffer_it); + } + } + // anything left in the buffer goes to the front of the remaining gap + std::move_backward(buffer_begin, buffer_it, out); + return; + } + // neither side fits: split the larger side in half and the other side at the matching key, so that + // everything in [first_cut, middle) is greater than or equal to everything in [middle, second_cut) ... + // (copies of existing iterators: a default-constructed block iterator has no state to iterate over) + ITERATOR first_cut = first; + ITERATOR second_cut = middle; + if (left_count > right_count) { + first_cut = first + left_count / 2; + second_cut = std::lower_bound(middle, last, *first_cut, compare); + } else { + second_cut = middle + right_count / 2; + first_cut = std::upper_bound(first, middle, *second_cut, compare); + } + // ... swap those two pieces, which leaves two independent merges on either side of the new middle + auto new_middle = BufferedRotate(first_cut, middle, second_cut, buffer, buffer_count); + // recurse into the smaller one and loop on the larger one, which bounds the recursion depth by log2(n) + if (new_middle - first < last - new_middle) { + BufferedMerge(first, first_cut, new_middle, compare, buffer, buffer_count); + first = new_middle; + middle = second_cut; + } else { + BufferedMerge(new_middle, second_cut, last, compare, buffer, buffer_count); + last = new_middle; + middle = first_cut; + } + } +} + +//! Merge policy for vergesort (and the sorts it calls into) that merges through a SortMergeBuffer, so that every run +//! merge of a sort, including the nested ones, uses the one buffer that was budgeted for it and nothing else +template +class BufferedMergePolicy { +public: + explicit BufferedMergePolicy(SortMergeBuffer &buffer) : keys(buffer.Keys()), count(buffer.Count()) { + } + + template + void operator()(ITERATOR first, ITERATOR middle, ITERATOR last, const COMPARE &compare) const { + BufferedMerge(first, middle, last, compare, keys, count); + } + +private: + SORT_KEY *keys; + idx_t count; +}; + +} // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp index fedb2dba4..560c58078 100644 --- a/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp +++ b/src/duckdb/src/include/duckdb/common/sorting/sorted_run.hpp @@ -58,6 +58,8 @@ class SortedRun { idx_t Count() const; //! Size of this sorted run idx_t SizeInBytes() const; + //! Size of this sorted run plus the merge buffer that Finalize allocates to sort it + idx_t SizeInBytesForSorting() const; private: mutex merger_global_state_lock; diff --git a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp index 5bc8261cc..61355a8ac 100644 --- a/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp +++ b/src/duckdb/src/include/duckdb/common/thread_annotation/mutex.hpp @@ -12,17 +12,21 @@ #pragma once #include +#include namespace duckdb { // Forward declaration for annotated mutex types. class annotated_mutex; +class annotated_shared_mutex; // Forward declaration for annotated lock types. template class annotated_unique_lock; template class annotated_lock_guard; +template +class annotated_shared_lock; namespace internal { @@ -40,6 +44,12 @@ struct standard_impl<::duckdb::annotated_mutex> { using type = std::mutex; }; +// Specialization for `std::shared_mutex`. +template <> +struct standard_impl<::duckdb::annotated_shared_mutex> { + using type = std::shared_mutex; +}; + // Type alias for lock types. template using mutex_impl_t = standard_impl_t; @@ -56,6 +66,12 @@ struct standard_impl<::duckdb::annotated_lock_guard> { using type = std::lock_guard>; }; +// Specialization for `std::shared_lock`. +template +struct standard_impl<::duckdb::annotated_shared_lock> { + using type = std::shared_lock>; +}; + template using lock_impl_t = standard_impl_t; diff --git a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp index cf943612d..b1346c193 100644 --- a/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp +++ b/src/duckdb/src/include/duckdb/common/types/arrow_string_view_type.hpp @@ -71,11 +71,11 @@ union arrow_string_view_t { const char *GetInlineData() const { return IsInline() ? inlined.data : ref.prefix; } - int32_t GetBufferIndex() { + int32_t GetBufferIndex() const { D_ASSERT(!IsInline()); return ref.buffer_index; } - int32_t GetOffset() { + int32_t GetOffset() const { D_ASSERT(!IsInline()); return ref.offset; } diff --git a/src/duckdb/src/include/duckdb/common/types/time.hpp b/src/duckdb/src/include/duckdb/common/types/time.hpp index 764ed6857..d15c2752d 100644 --- a/src/duckdb/src/include/duckdb/common/types/time.hpp +++ b/src/duckdb/src/include/duckdb/common/types/time.hpp @@ -29,7 +29,8 @@ class Time { optional_ptr nanos = nullptr); // No hour limit DUCKDB_API static bool TryConvertInterval(const char *buf, idx_t len, idx_t &pos, dtime_t &result, - bool strict = false, optional_ptr nanos = nullptr); + bool strict = false, optional_ptr nanos = nullptr, + bool negative = false); //! Convert a time object to a string in the format "hh:mm:ss" DUCKDB_API static string ToString(dtime_t time); @@ -56,7 +57,7 @@ class Time { private: static bool TryConvertInternal(const char *buf, idx_t len, idx_t &pos, dtime_t &result, bool strict, - optional_ptr nanos = nullptr); + optional_ptr nanos = nullptr, bool negative = false); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp index 96c1612de..a2b86440c 100644 --- a/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp +++ b/src/duckdb/src/include/duckdb/execution/expression_executor_state.hpp @@ -74,6 +74,8 @@ struct ExecuteFunctionState : public ExpressionState { public: unique_ptr local_state; + //! The buffers of all-constant arguments, which can be shared with the input and have to get their size back + vector> constant_argument_buffers; private: //! The column index of the "unary" input column that may be a dictionary vector diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp index d2eab3a84..1671b85fc 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_batch_insert.hpp @@ -27,13 +27,13 @@ class PhysicalBatchInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality); //! The table to insert into - optional_ptr insert_table; + mutable optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table vector> bound_constraints; //! Table schema, in case of CREATE TABLE AS - optional_ptr schema; + mutable optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Preferred input batch size diff --git a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp index b67be1ab7..965f4ff11 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/persistent/physical_insert.hpp @@ -87,7 +87,7 @@ class PhysicalInsert : public PhysicalOperator { unique_ptr info, idx_t estimated_cardinality, bool parallel); //! The table to insert into - optional_ptr insert_table; + mutable optional_ptr insert_table; //! The insert types vector insert_types; //! The bound constraints for the table @@ -95,7 +95,7 @@ class PhysicalInsert : public PhysicalOperator { //! If the returning statement is present, return the whole chunk bool return_chunk; //! Table schema, in case of CREATE TABLE AS - optional_ptr schema; + mutable optional_ptr schema; //! Create table info, in case of CREATE TABLE AS unique_ptr info; //! Whether or not the INSERT can be executed in parallel diff --git a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp index b12125a65..417427b69 100644 --- a/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp +++ b/src/duckdb/src/include/duckdb/execution/operator/projection/physical_projection.hpp @@ -24,6 +24,7 @@ class PhysicalProjection : public PhysicalOperator { vector> select_list; public: + void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const override; unique_ptr GetOperatorState(ExecutionContext &context) const override; OperatorResultType Execute(ExecutionContext &context, DataChunk &input, DataChunk &chunk, GlobalOperatorState &gstate, OperatorState &state) const override; diff --git a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp index ddb6e10b1..df0a6d347 100644 --- a/src/duckdb/src/include/duckdb/execution/physical_operator.hpp +++ b/src/duckdb/src/include/duckdb/execution/physical_operator.hpp @@ -91,6 +91,11 @@ class PhysicalOperator { return false; } + //! Initialize the vectors this operator uses for its output + virtual void InitializeOutputChunk(Allocator &allocator, DataChunk &chunk) const { + chunk.Initialize(allocator, types); + } + //! Functions to help decide how to set up pipeline dependencies idx_t EstimatedThreadCount() const; bool CanSaturateThreads(ClientContext &context) const; diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp index 104c71ae4..25faa7444 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/json_to_variant.hpp @@ -162,7 +162,8 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res VarintEncode(length, str_blob_data); memcpy(str_blob_data + length_varint_size, const_data_ptr_cast(str), length); } - blob_offset_data[result_index] += length_varint_size + length; + OffsetData::AddBlobOffset(blob_offset_data[result_index], length_varint_size); + OffsetData::AddBlobOffset(blob_offset_data[result_index], length); break; } case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: { @@ -182,7 +183,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_uint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(uint64_t)); } - blob_offset_data[result_index] += sizeof(uint64_t); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(uint64_t)); break; } case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: { @@ -192,7 +193,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res auto value = unsafe_yyjson_get_sint(val); memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(int64_t)); } - blob_offset_data[result_index] += sizeof(int64_t); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(int64_t)); break; } case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: @@ -211,7 +212,7 @@ static bool ConvertJSONPrimitive(yyjson_val *val, ToVariantGlobalResultData &res } memcpy(blob_data + blob_offset_data[result_index], const_data_ptr_cast(&value), sizeof(double)); } - blob_offset_data[result_index] += sizeof(double); + OffsetData::AddBlobOffset(blob_offset_data[result_index], sizeof(double)); break; } default: diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp index 0c972435e..86abb7d4b 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/primitive_to_variant.hpp @@ -16,6 +16,14 @@ struct EnumConversionPayload { EnumConversionPayload(const string_t *values, idx_t size) : values(values), size(size) { } +public: + const string_t &GetString(idx_t index) const { + if (index >= size) { + throw InternalException("ENUM index %llu is out of range for an ENUM with %llu values", index, size); + } + return values[index]; + } + public: const string_t *values; idx_t size; @@ -101,19 +109,19 @@ static void WriteData(data_ptr_t ptr, const T &val, uint32_t lengths[3], EnumCon template <> void WriteData(data_ptr_t ptr, const uint8_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint16_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } template <> void WriteData(data_ptr_t ptr, const uint32_t &val, uint32_t lengths[3], EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); WriteData(ptr, str, lengths, empty_payload); } @@ -184,19 +192,19 @@ static void GetValueSize(const T &val, uint32_t lengths[3], idx_t &lengths_size, template <> void GetValueSize(const uint8_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint16_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } template <> void GetValueSize(const uint32_t &val, uint32_t lengths[3], idx_t &lengths_size, EnumConversionPayload &payload) { EmptyConversionPayloadToVariant empty_payload; - auto &str = payload.values[val]; + auto &str = payload.GetString(val); GetValueSize(str, lengths, lengths_size, empty_payload); } @@ -215,9 +223,10 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob auto &variant = result.variant; uint32_t lengths[3]; - idx_t lengths_size = 0; for (idx_t i = 0; i < count; i++) { auto index = source[i]; + // the blob space to reserve for this row - nothing for NULL rows + idx_t lengths_size = 0; auto result_index = selvec ? selvec->get_index(i) : i; auto &blob_offset = blob_offset_data[result_index]; @@ -225,7 +234,6 @@ static bool ConvertPrimitiveTemplated(ToVariantSourceData &source, ToVariantGlob if (TYPE_ID != VariantLogicalType::VARIANT_NULL && source_validity.RowIsValid(index)) { //! Write the value auto &val = source_data[index]; - lengths_size = 0; GetValueSize(val, lengths, lengths_size, payload); WriteVariantMetadata(result, result_index, values_offset_data, blob_offset, values_index_selvec, i, GetTypeId(val)); diff --git a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp index 83c37856a..65ead7f16 100644 --- a/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp +++ b/src/duckdb/src/include/duckdb/function/cast/variant/to_variant_fwd.hpp @@ -74,8 +74,7 @@ template void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t &blob_offset, idx_t length, idx_t children_offset) { const auto length_varint_size = GetVarintSize(length); - const auto child_offset_varint_size = - NumericCast(length_varint_size ? GetVarintSize(children_offset) : 0); + const auto child_offset_varint_size = NumericCast(length ? GetVarintSize(children_offset) : 0); if (WRITE_DATA) { auto &blob_value = result.blob_data[result_index]; @@ -86,7 +85,7 @@ void WriteContainerData(VariantVectorData &result, idx_t result_index, uint32_t } } - blob_offset += length_varint_size + child_offset_varint_size; + OffsetData::AddBlobOffset(blob_offset, length_varint_size + child_offset_varint_size); } struct ContainerSelectionVectors { diff --git a/src/duckdb/src/include/duckdb/function/compression_function.hpp b/src/duckdb/src/include/duckdb/function/compression_function.hpp index 54cebc0c5..0068ee52b 100644 --- a/src/duckdb/src/include/duckdb/function/compression_function.hpp +++ b/src/duckdb/src/include/duckdb/function/compression_function.hpp @@ -10,6 +10,7 @@ #include "duckdb/common/common.hpp" #include "duckdb/common/enums/compression_type.hpp" +#include "duckdb/common/enums/scan_vector_type.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/insertion_order_preserving_map.hpp" #include "duckdb/common/mutex.hpp" @@ -162,8 +163,11 @@ typedef void (*compression_scan_vector_t)(ColumnSegment &segment, ColumnScanStat typedef void (*compression_scan_partial_t)(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset); //! Function prototype used for reading a subset of the values of a vector indicated by a selection vector +//! The selected values are written to 'result' starting at 'result_offset'. Emitting a compressed vector (e.g. a +//! constant vector) is only allowed if 'scan_type' is SCAN_ENTIRE_VECTOR typedef void (*compression_select_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType scan_type); //! Function prototype used for applying a filter to a vector while scanning that vector typedef void (*compression_filter_t)(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, diff --git a/src/duckdb/src/include/duckdb/function/function.hpp b/src/duckdb/src/include/duckdb/function/function.hpp index aab9fa6b5..affaae7c9 100644 --- a/src/duckdb/src/include/duckdb/function/function.hpp +++ b/src/duckdb/src/include/duckdb/function/function.hpp @@ -56,10 +56,14 @@ enum class FunctionStability : uint8_t { CONSISTENT = 0, VOLATILE = 1, CONSISTEN //! PROPAGATE_COLLATIONS -> this function combines collation from its inputs and emits them again (default) //! PUSH_COMBINABLE_COLLATIONS -> combinable collations are executed for the input arguments //! IGNORE_COLLATIONS -> collations are completely ignored by the function +//! PUSH_DISTINCT_COLLATIONS -> aggregate DISTINCT arguments are replaced by their comparison keys +//! Only valid for aggregates that can consume keys instead of original values. +//! Ordinary (non-DISTINCT) calls keep their original arguments. enum class FunctionCollationHandling : uint8_t { PROPAGATE_COLLATIONS = 0, PUSH_COMBINABLE_COLLATIONS = 1, - IGNORE_COLLATIONS = 2 + IGNORE_COLLATIONS = 2, + PUSH_DISTINCT_COLLATIONS = 3 }; struct FunctionData { diff --git a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp index 4a4361232..6e77374f9 100644 --- a/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp +++ b/src/duckdb/src/include/duckdb/function/scalar/generic_common.hpp @@ -16,6 +16,7 @@ namespace duckdb { class BoundFunctionExpression; +struct SortedAggregateStateOrder; struct ConstantOrNull { static bool IsConstantOrNull(BoundFunctionExpression &expr, const Value &val); @@ -32,6 +33,8 @@ struct ExportAggregateFunction { static unique_ptr Bind(unique_ptr child_aggregate); static void SetStateExport(BoundAggregateExpression &aggregate, LogicalType state_layout); static unique_ptr StateToSQL(const LogicalType &type, unique_ptr value); + //! The per-key (column, modifiers) of the AGGREGATE_STATE type of an ordered aggregate + static void GetSortedStateOrders(const LogicalType &state_type, vector &orders); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/client_context.hpp b/src/duckdb/src/include/duckdb/main/client_context.hpp index 42984f6e5..62ef1312e 100644 --- a/src/duckdb/src/include/duckdb/main/client_context.hpp +++ b/src/duckdb/src/include/duckdb/main/client_context.hpp @@ -161,6 +161,8 @@ class ClientContext : public enable_shared_from_this { //! Check for interrupt or timeout, throws InterruptException if triggered DUCKDB_API void InterruptCheck() const; + //! InterruptCheck that reads the clock on every call, for waits that block between checks + DUCKDB_API void BlockingInterruptCheck() const; //! Enable query profiling DUCKDB_API void EnableProfiling(); diff --git a/src/duckdb/src/include/duckdb/main/config.hpp b/src/duckdb/src/include/duckdb/main/config.hpp index ff6e23f80..29c921aca 100644 --- a/src/duckdb/src/include/duckdb/main/config.hpp +++ b/src/duckdb/src/include/duckdb/main/config.hpp @@ -172,6 +172,45 @@ struct DBConfigOptions { bool operator==(const DBConfigOptions &other) const; }; +//! The replacement scans of a database. Scans can be registered while queries are being bound, so the list is +//! guarded by a lock and the binder iterates over a snapshot. +class ReplacementScanList { +public: + ReplacementScanList() = default; + ReplacementScanList(ReplacementScanList &&other) noexcept { + lock_guard guard(other.lock); + scans = std::move(other.scans); + } + ReplacementScanList &operator=(ReplacementScanList &&other) noexcept { + if (this != &other) { + lock_guard other_guard(other.lock); + lock_guard guard(lock); + scans = std::move(other.scans); + } + return *this; + } + + template + void emplace_back(ARGS &&...args) { // NOLINT + auto scan = make_shared_ptr(std::forward(args)...); + lock_guard guard(lock); + scans.push_back(std::move(scan)); + } + void push_back(ReplacementScan scan) { // NOLINT + auto shared_scan = make_shared_ptr(std::move(scan)); + lock_guard guard(lock); + scans.push_back(std::move(shared_scan)); + } + vector> Snapshot() const { + lock_guard guard(lock); + return scans; + } + +private: + mutable mutex lock; + vector> scans; +}; + struct DBConfig { friend class DatabaseInstance; friend class StorageManager; @@ -183,7 +222,7 @@ struct DBConfig { DUCKDB_API ~DBConfig(); //! Replacement table scans are automatically attempted when a table name cannot be found in the schema - vector replacement_scans; + ReplacementScanList replacement_scans; //! The FileSystem to use, can be overwritten to allow for injecting custom file systems for testing purposes (e.g. //! RamFS or something similar) @@ -320,16 +359,21 @@ struct DBConfig { //! Returns the default value of an option static SettingLookupResult TryGetDefaultValue(optional_ptr option, Value &result); - bool CanAccessFile(const string &path, FileType type); + //! The opener (if any) is used to expand "~" using the connection-specific home_directory + bool CanAccessFile(const string &path, FileType type, optional_ptr opener = nullptr); void AddAllowedConfig(const Identifier &config_name); void AddAllowedDirectory(const string &path); void AddAllowedPath(const string &path); + //! Whether the given optimizer is disabled (disabled_optimizers) + DUCKDB_API bool IsOptimizerDisabled(OptimizerType type) const; + DUCKDB_API set GetDisabledOptimizers() const; + DUCKDB_API void SetDisabledOptimizers(set disabled_optimizers); //! Allows a database file and its WAL files, so a database can be opened while external access is disabled. //! Only possible through API calls, not SQL calls. void AddAllowedDatabasePath(const string &database_path); vector GetAllowedDirectories() const; vector GetAllowedPaths() const; - string SanitizeAllowedPath(const string &path) const; + string SanitizeAllowedPath(const string &path, optional_ptr opener = nullptr) const; ExtensionCallbackManager &GetCallbackManager(); const ExtensionCallbackManager &GetCallbackManager() const; @@ -345,6 +389,8 @@ struct DBConfig { mutable mutex config_lock; //! Guards allowed_paths and allowed_directories, which a running instance can extend while files are being opened mutable mutex allowed_paths_lock; + //! Guards disabled_optimizers, which can be changed while other connections plan queries + mutable mutex disabled_optimizers_lock; unique_ptr compression_functions; unique_ptr encoding_functions; unique_ptr arrow_extensions; diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index ceb405b13..0671844d9 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -717,10 +717,12 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"pi", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"position", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_clear_cache", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, + {"postgres_close_pinned_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_configure_pool", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_execute", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"postgres_hstore_get", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_hstore_to_json", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"postgres_pin_connection", "postgres_scanner", CatalogType::SCALAR_FUNCTION_ENTRY}, {"postgres_query", "postgres_scanner", CatalogType::TABLE_FUNCTION_ENTRY}, {"pow", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, {"power", "core_functions", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -1357,7 +1359,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"mysql_bit1_as_boolean", "mysql_scanner"}, {"mysql_debug_show_queries", "mysql_scanner"}, {"mysql_enable_filter_pushdown", "mysql_scanner"}, - {"mysql_enable_remote_pushdown_ddl", "mysql_scanner"}, + {"mysql_enable_remote_pushdown", "mysql_scanner"}, {"mysql_enable_transactions", "mysql_scanner"}, {"mysql_incomplete_dates_as_nulls", "mysql_scanner"}, {"mysql_pool_acquire_mode", "mysql_scanner"}, @@ -1377,13 +1379,12 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_connection_cache", "postgres_scanner"}, {"pg_connection_limit", "postgres_scanner"}, {"pg_debug_show_queries", "postgres_scanner"}, - {"pg_experimental_filter_pushdown", "postgres_scanner"}, + {"pg_enable_filter_pushdown", "postgres_scanner"}, {"pg_idle_in_transaction_timeout_millis", "postgres_scanner"}, {"pg_null_byte_replacement", "postgres_scanner"}, {"pg_numeric_as_varchar", "postgres_scanner"}, {"pg_numeric_nan_as_null", "postgres_scanner"}, {"pg_oauth_token", "postgres_scanner"}, - {"pg_order_pushdown", "postgres_scanner"}, {"pg_pages_per_task", "postgres_scanner"}, {"pg_pool_acquire_mode", "postgres_scanner"}, {"pg_pool_enable_reaper_thread", "postgres_scanner"}, @@ -1393,6 +1394,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"pg_pool_max_connections", "postgres_scanner"}, {"pg_pool_max_lifetime_millis", "postgres_scanner"}, {"pg_pool_wait_timeout_millis", "postgres_scanner"}, + {"pg_query_mode", "postgres_scanner"}, {"pg_staleness_query", "postgres_scanner"}, {"pg_staleness_query_enabled", "postgres_scanner"}, {"pg_statement_timeout_millis", "postgres_scanner"}, diff --git a/src/duckdb/src/include/duckdb/main/extension_manager.hpp b/src/duckdb/src/include/duckdb/main/extension_manager.hpp index b7c0f731b..d45cc4e4b 100644 --- a/src/duckdb/src/include/duckdb/main/extension_manager.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_manager.hpp @@ -32,10 +32,16 @@ class ExtensionActiveLoad { ExtensionActiveLoad(DatabaseInstance &db, ExtensionInfo &info, Identifier extension_name_p, Identifier alias_p) : db(db), load_lock(info.lock), info(info), extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; + ExtensionActiveLoad(DatabaseInstance &db, shared_ptr info_p, Identifier extension_name_p, + Identifier alias_p) + : db(db), info_owner(std::move(info_p)), load_lock(info_owner->lock), info(*info_owner), + extension_name(std::move(extension_name_p)), alias(std::move(alias_p)) {}; ~ExtensionActiveLoad() = default; DatabaseInstance &db; + //! Keeps the info alive when the extension is removed from the manager during the load + shared_ptr info_owner; unique_lock load_lock; ExtensionInfo &info; Identifier extension_name; @@ -52,7 +58,7 @@ class ExtensionManager { DUCKDB_API bool ExtensionIsLoaded(const string &name); DUCKDB_API vector GetExtensions(); - DUCKDB_API optional_ptr GetExtensionInfo(const string &name); + DUCKDB_API shared_ptr GetExtensionInfo(const string &name); DUCKDB_API unique_ptr BeginLoad(const ExtensionLoadOptions &options); DUCKDB_API static ExtensionManager &Get(DatabaseInstance &db); @@ -63,7 +69,7 @@ class ExtensionManager { private: DatabaseInstance &db; mutex lock; - identifier_map_t> loaded_extensions_info; + identifier_map_t> loaded_extensions_info; string extension_load_prefix; }; diff --git a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp index 42305444e..7ea6228e3 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_retry_budget.hpp @@ -9,6 +9,9 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/mutex.hpp" +#include "duckdb/common/shared_ptr.hpp" +#include "duckdb/common/time_point.hpp" #include namespace duckdb { @@ -16,32 +19,116 @@ namespace duckdb { struct HTTPParams; //! An attempt's decision; constructing one does not consume retry budget. +//! +//! The loop that runs an operation is its coordinator: after inspecting a response (status, headers and body) it +//! decides explicitly whether the operation succeeded, failed for good, is retried, or is re-sent corrected. The budget +//! only admits retries, waits between them and keeps the host quota's books, refunding once after a reported success. +//! Finish() is the decision of a loop that does not judge the operation itself (core's request loop, or a provider +//! that has not adopted the explicit protocol): with a status-owned budget the quota is then settled from the HTTP +//! status core recorded; a coordinator-owned budget waits for the coordinator's report. class HTTPRetryDecision { friend class HTTPRetryBudget; friend class HTTPUtil; public: + //! Stop without judging the operation: a nested loop leaves the decision to the coordinator. The outermost loop of + //! a status-owned budget settles the quota from the HTTP status core recorded (the pre-protocol behaviour); a + //! coordinator-owned budget stays unsettled until ReportSuccess() or ReportFailure(). DUCKDB_API static HTTPRetryDecision Finish(); + //! The operation succeeded: the coordinator validated the response, the quota is refunded once. Only the owner + //! of a coordinator-owned budget decides this. + DUCKDB_API static HTTPRetryDecision Success(); + //! The operation failed for good (e.g. an error document behind HTTP 200): nothing is refunded. Only the owner of + //! a coordinator-owned budget decides this. + DUCKDB_API static HTTPRetryDecision Failure(); + //! Re-send after correcting the request (credentials, region, endpoint): no retry is consumed and no back-off is + //! waited, bounded by HTTPRetryBudget::MAX_CORRECTIONS per operation. + DUCKDB_API static HTTPRetryDecision Correction(); + //! Retry after a received server error (e.g. HTTP 500). DUCKDB_API static HTTPRetryDecision Retry(); + //! Retry after a transport failure (connection error, timeout, truncated body): no response was received. + DUCKDB_API static HTTPRetryDecision Transient(); + //! Retry after a throttling response (HTTP 429/503), honoring an optional Retry-After header value. + DUCKDB_API static HTTPRetryDecision Throttled(const string &retry_after = string()); private: - enum class Type : uint8_t { FINISH, RETRY, THROTTLED }; + enum class Type : uint8_t { FINISH, SUCCESS, FAILURE, CORRECTION, RETRY, TRANSIENT, THROTTLED }; explicit HTTPRetryDecision(Type type_p, string retry_after_p = {}) : type(type_p), retry_after(std::move(retry_after_p)) { } - static HTTPRetryDecision Throttled(const string &retry_after); private: Type type; string retry_after; }; -//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. +//! Retry tokens shared by every operation against one host, after the AWS SDK retry quota. +//! Retries spend tokens and successes refund them, so a host that keeps failing stops being retried. +//! A host nobody has talked to for a while starts over with a full quota, like the CRT drops an idle host's bucket. +class HTTPRetryQuota { +public: + static constexpr int64_t INITIAL_CAPACITY = 500; + //! Cost of retrying a received server error or throttle: the server answered, it is just unhappy. + static constexpr int64_t RETRY_COST = 5; + //! Cost of retrying a transport failure: nothing is known about the server, so it costs more. + static constexpr int64_t TRANSIENT_RETRY_COST = 10; + //! Refund for a request that succeeded without retrying. + static constexpr int64_t NO_RETRY_REFUND = 1; + //! A quota untouched for this long is full again when it is next used. + static constexpr int64_t IDLE_RESET_MS = 60 * 1000; + +public: + //! Take `cost` tokens; false (and nothing taken) when fewer are available. + DUCKDB_API bool TryAcquire(int64_t cost, int64_t now_ms = TimePoint::GetTickMs()); + //! Return tokens, never above the initial capacity. + DUCKDB_API void Release(int64_t amount, int64_t now_ms = TimePoint::GetTickMs()); + DUCKDB_API int64_t Available() const; + //! Milliseconds since the quota was last charged or refunded. + DUCKDB_API int64_t IdleMs(int64_t now_ms = TimePoint::GetTickMs()) const; + +private: + //! Refill an idle quota and record the use; the lock must be held. + void Touch(int64_t now_ms); + +private: + mutable mutex lock; + int64_t capacity = INITIAL_CAPACITY; + int64_t last_used_ms = TimePoint::GetTickMs(); +}; + +//! Who settles an operation's outcome with the host quota, fixed when the operation starts. +enum class HTTPRetryOutcomeOwner : uint8_t { + //! Nobody validates beyond the HTTP status: the status core recorded settles the quota once the outermost loop + //! stops. Core's own request loop and loops that have not adopted the explicit protocol. + STATUS, + //! The coordinator that runs the operation validates the response and settles the quota itself, with a + //! Success/Failure decision or ReportSuccess/ReportFailure after its loop; core's status is never final for it. + //! An operation whose coordinator never reports stays charged, like one whose outcome is unknown. + COORDINATOR +}; + +//! Operation-local budget shared by synchronous nested retry loops, never concurrent loops. One budget is one +//! logical operation (one `amz-sdk-invocation-id`): every send it makes, including corrected re-sends, is one of +//! its attempts, as in the AWS SDK's attempt loop. The host quota is settled once per operation, when its outcome is +//! known, by the owner fixed at construction: a coordinator reports it (Success/Failure decisions or +//! ReportSuccess/ReportFailure, whenever it has validated the response, inside or after its loop); for a status-owned +//! budget it is taken from the HTTP status core recorded (RecordStatusOutcome). A status recorded inside an outer Run +//! waits for that loop to stop, since an outer loop may still retry. class HTTPRetryBudget { friend class HTTPUtil; public: - DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms); + //! Longest single back-off sleep, as in the AWS SDKs. + static constexpr uint64_t MAX_BACKOFF_MS = 20000; + //! Retries granted beyond `http_retries` for throttled responses: the server asked to slow down, not to stop. + static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; + //! Corrected re-sends admitted per operation: a credential refresh and a region redirect, with room for a + //! provider that needs both again after the other; past this the operation ends with the last response. + static constexpr uint64_t MAX_CORRECTIONS = 4; + +public: + DUCKDB_API explicit HTTPRetryBudget(const HTTPParams ¶ms, + HTTPRetryOutcomeOwner owner = HTTPRetryOutcomeOwner::STATUS); HTTPRetryBudget(const HTTPRetryBudget &) = delete; HTTPRetryBudget &operator=(const HTTPRetryBudget &) = delete; @@ -51,21 +138,88 @@ class HTTPRetryBudget { DUCKDB_API void Run(const std::function &attempt); //! Retries admitted for HTTP 429 or 503 responses. DUCKDB_API uint64_t ThrottledRetries() const; + //! Retries admitted across all participating requests. + DUCKDB_API uint64_t RetriesUsed() const; + //! Number the next send of the operation: the first call returns 1. + DUCKDB_API uint64_t BeginAttempt(); + //! Sends so far that were neither the first nor an admitted retry: re-sends after the coordinator corrected the + //! request (credentials, region), which do not consume retries. + DUCKDB_API uint64_t Corrections() const; + //! Most attempts the operation can make: the first one, `http_retries`, the throttle extras and the corrected + //! re-sends made so far, so that every numbered attempt stays within the bound advertised with it. + DUCKDB_API uint64_t MaxAttempts() const; + //! Whether the current Run refused a retry because the host's quota ran out. + DUCKDB_API bool QuotaExhausted() const; + //! Whether the current Run refused a corrected re-send because MAX_CORRECTIONS were made already. + DUCKDB_API bool CorrectionsExhausted() const; + //! Charge the next admitted retries to this host's quota; a request to another host rebinds. + DUCKDB_API void BindQuota(shared_ptr quota); + //! Who settles this operation's outcome. + DUCKDB_API HTTPRetryOutcomeOwner Owner() const; + //! The coordinator's final word, for a coordinator that validates the response after its Run returned: the + //! operation succeeded, refund the last admitted retry to the quota it was charged to, or NO_RETRY_REFUND to the + //! bound quota. Settles once; a later report of the same operation changes nothing. Coordinator-owned budgets only. + DUCKDB_API void ReportSuccess(); + //! The coordinator's final word: the operation failed, the last admitted retry is not refunded. Coordinator-owned + //! budgets only. + DUCKDB_API void ReportFailure(); + //! What the HTTP status of the last response says, recorded by core's request loop. For a status-owned budget it + //! settles the quota, at once outside a Run or when the outermost Run stops; a coordinator-owned budget ignores + //! it, its coordinator judges the response. + DUCKDB_API void RecordStatusOutcome(bool success); + //! Whether a Run of this budget is in progress, i.e. a nested loop is running inside an outer one. + DUCKDB_API bool InRun() const; + + //! Back-off window for `retry` is [0, min(wait * backoff^(retry - 1), MAX_BACKOFF_MS)]: full jitter, or its upper + //! half for throttled retries so they always keep some distance. The result is raised to `floor_ms`. + DUCKDB_API static uint64_t BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, + uint64_t random_value, bool throttled = false); + //! Parse a Retry-After header (delay in seconds or an HTTP-date) into a delay in milliseconds. + DUCKDB_API static bool TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms); private: //! Core's hook runs after admission/backoff, outside the attempt's transport-error handling. void Run(const std::function &attempt, const std::function &before_retry); bool ConsumeAndWait(const HTTPRetryDecision &decision); + //! Admit a corrected re-send within MAX_CORRECTIONS. + bool AdmitCorrection(); + uint64_t ThrottleExtraRetries() const; + //! Settle the operation's outcome with the quota once: refund on success, then forget the last admitted retry. + void Settle(bool success); + //! Explicit decisions and reports belong to a coordinator-owned budget; anything else is a programming error. + void RequireCoordinator(const char *what) const; + //! Forget the last admitted retry, so that nothing is refunded for it. + void ForgetLastRetry(); private: //! Retry settings captured for the operation. const uint64_t retries; const uint64_t retry_wait_ms; const float retry_backoff; + //! Who settles the outcome with the quota. + const HTTPRetryOutcomeOwner owner; //! Retries admitted across all participating requests. uint64_t retries_used = 0; uint64_t throttled_retries = 0; + //! Sends numbered so far. + uint64_t attempts = 0; + //! Corrected re-sends admitted by Correction() decisions. + uint64_t corrections_admitted = 0; + //! Nesting depth of Run; status outcomes recorded while this is positive wait for the outermost loop's decision. + uint64_t run_depth = 0; + //! Outcome core recorded from the HTTP status, pending the outermost loop's stop (status-owned budgets only). + enum class RecordedOutcome : uint8_t { NONE, SUCCESS, FAILURE }; + RecordedOutcome status_outcome = RecordedOutcome::NONE; + //! Whether the operation's outcome has been settled with the quota; a new admitted retry starts a new settlement. + bool outcome_settled = false; + //! Quota of the host the current request goes to, if any. + shared_ptr quota; + //! Quota and tokens of the most recent admitted retry, refunded on success. + shared_ptr last_retry_quota; + int64_t last_retry_cost = 0; + bool quota_exhausted = false; + bool corrections_exhausted = false; }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/main/http/http_util.hpp b/src/duckdb/src/include/duckdb/main/http/http_util.hpp index 309dcfe77..40385438e 100644 --- a/src/duckdb/src/include/duckdb/main/http/http_util.hpp +++ b/src/duckdb/src/include/duckdb/main/http/http_util.hpp @@ -14,6 +14,9 @@ #include "duckdb/common/enums/http_status_code.hpp" #include "duckdb/common/types/timestamp.hpp" #include "duckdb/common/time_point.hpp" +#include "duckdb/common/mutex.hpp" +#include "duckdb/common/unordered_map.hpp" +#include "duckdb/main/http/http_retry_budget.hpp" #include namespace duckdb { @@ -399,8 +402,18 @@ class HTTPUtil { virtual unique_ptr SendRequest(BaseRequest &request, unique_ptr &client); virtual void LogRequest(BaseRequest &request, optional_ptr response); - //! Whether a failed request should be retried, possibly using HTTPResponse information, and allowing overrides + //! Whether a failed request should be retried. Judges received responses and statuses a handler turned into an + //! exception alike; core knows only the generic statuses (IsRetryableStatus), a provider adds what its service + //! says in the body (e.g. an S3 error document whose code is retryable behind a final-looking status). DUCKDB_API virtual bool ShouldRetry(const BaseRequest &request, const HTTPResponse &response); + //! Runs before every attempt with the 1-based attempt number and the configured maximum, e.g. to tag headers. + DUCKDB_API virtual void PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts); + //! Retry quota shared by all requests of this provider against one host. + DUCKDB_API shared_ptr GetRetryQuota(const string &proto_host_port); + //! Whether a received status is worth retrying: 408, 418, 429 and the server errors that can clear up. + DUCKDB_API static bool IsRetryableStatus(HTTPStatusCode status); + //! Hosts remembered for retry quotas; past this the host unused for longest makes room. + static constexpr idx_t MAX_RETRY_QUOTAS = 1024; //! Whether replaying this request is safe. POST is the only method we cannot assume is idempotent. DUCKDB_API static bool IsIdempotent(RequestType type); @@ -419,9 +432,15 @@ class HTTPUtil { HTTPClientCachePolicy initial_cache_policy, HTTPClientCachePolicy &retry_cache_policy); +private: + //! Protects retry_quotas. + mutex retry_quota_lock; + //! Per-host retry quotas, created on first use; budgets keep theirs alive across an eviction. + unordered_map> retry_quotas; + public: static duckdb::unique_ptr - RunRequestWithRetry(const std::function(void)> &on_request, const BaseRequest &request, + RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, const std::function &retry_cb); }; } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp index 0647b333d..25407233e 100644 --- a/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/join_order/join_order_optimizer.hpp @@ -21,6 +21,12 @@ class JoinOrderOptimizer { explicit JoinOrderOptimizer(ClientContext &context); public: + //! Deepest nesting of child optimizers (one per non-reorderable operator, projection, aggregate, ...) that is still + //! join-order optimized. Every level recurses through Optimize/Build/ExtractJoinRelations, so a very deep plan + //! (e.g. a long chain of EXCEPT/INTERSECT) would otherwise overflow the stack long before the plan depth limit is + //! hit; subplans below this depth keep their original join order, which is always a valid plan. + static constexpr idx_t MAX_RECURSION_DEPTH = 128; + JoinOrderOptimizer CreateChildOptimizer(); //! Perform join reordering inside a plan unique_ptr Optimize(unique_ptr plan, optional_ptr stats = nullptr); diff --git a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp index 334f3cde2..c7171f095 100644 --- a/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/relation_statistics/relation_statistics.hpp @@ -12,6 +12,7 @@ #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/types.hpp" #include "duckdb/planner/column_binding.hpp" +#include "duckdb/planner/column_binding_map.hpp" namespace duckdb { @@ -54,4 +55,18 @@ struct RelationStats { Identifier table_name; }; +//! Looks up column statistics by binding, the first relation and column with a binding wins +class RelationColumnStatsLookup { +public: + RelationColumnStatsLookup() = default; + explicit RelationColumnStatsLookup(const RelationStats &stats); + +public: + void AddRelation(const RelationStats &stats); + optional_ptr Find(ColumnBinding binding) const; + +private: + column_binding_map_t> columns; +}; + } // namespace duckdb diff --git a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp index 716dc2f72..82a6d54d4 100644 --- a/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp +++ b/src/duckdb/src/include/duckdb/optimizer/remove_unused_columns.hpp @@ -129,6 +129,8 @@ class BaseColumnPruner : public LogicalOperatorVisitor { private: //! The current mode of the pruner, enables/disables certain behaviors BaseColumnPrunerMode mode = BaseColumnPrunerMode::DEFAULT; + //! True while visiting the child of OPERATOR_TRY. Do not push throwing CASTs (#25236). + bool inside_try = false; }; //! The RemoveUnusedColumns optimizer traverses the logical operator tree and removes any columns that are not required diff --git a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp index afc6450a5..b40a20121 100644 --- a/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp +++ b/src/duckdb/src/include/duckdb/parallel/scan_read_ahead.hpp @@ -14,6 +14,8 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/map.hpp" #include "duckdb/common/mutex.hpp" +#include "duckdb/common/condition_variable.hpp" +#include "duckdb/common/optional.hpp" #include "duckdb/common/deque.hpp" #include "duckdb/common/vector.hpp" #include "duckdb/common/serializer/async_memory_governor.hpp" @@ -177,6 +179,10 @@ class ScanReadAhead { read_ahead.pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; } --read_ahead.active_producers; + // a pushed job was signalled already, a released slot frees one thread and a done scan all of them + if (!committed || read_ahead.IsDone()) { + read_ahead.SignalProgress(read_ahead.IsDone()); + } } ScanReadAhead &read_ahead; @@ -198,16 +204,29 @@ class ScanReadAhead { lock_guard guard(lock); return !pending_jobs.empty(); } + //! The number of jobs that may be in flight under the current budget + idx_t ReservationDepth() const; //! Reserve an in-flight job slot for producing a job bool TryReserveSlot(); //! Schedule the job's I/O and admit the job to the queue void PushJob(unique_ptr job, vector> io_tasks); //! Release a read-ahead slot void ReleaseSlot(); + //! Wake a thread waiting for another thread to push a job, release a slot or finish producing, or all of them + void SignalProgress(bool wake_all); + //! SignalProgress for a caller that holds the lock + void NotifyProgress(bool wake_all); + //! Wake one waiting thread, or all of them + void WakeWaiters(bool wake_all); + //! Wait until the read-ahead made progress since the given epoch, or the timeout passed + void WaitForProgress(ClientContext &context, idx_t epoch); + idx_t ProgressEpoch() const; private: //! Minimum budget charge per job, beyond its scheduled I/O a job carries scan-state overhead (decode buffers) static constexpr idx_t MINIMUM_JOB_IO_CHARGE = 16ULL * 1024 * 1024; + //! How long a thread yields for another thread to push a job before it waits for it + static constexpr idx_t PRODUCER_YIELD_MICROS = 1000; private: ClientContext &context; @@ -233,6 +252,11 @@ class ScanReadAhead { atomic done {false}; //! Threads that reserved a slot but have not pushed their job yet atomic active_producers {0}; + //! Incremented whenever a waiting thread could make progress + atomic progress_epoch {0}; + //! Threads waiting for progress, only these need a notification + atomic waiting_threads {0}; + condition_variable progress; //! File-opens scheduled on the async pool that have not completed yet, shared with the open tasks shared_ptr> pending_opens; //! Async I/O executor (async pool) diff --git a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp index d1d76b01e..a2054a041 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/ast/expression_chain.hpp @@ -11,13 +11,13 @@ struct IsDistinctFromTail { struct ComparisonExpressionTail { ExpressionType comparison_type; - vector not_keywords; unique_ptr expression; }; struct BetweenInLikeOperator { bool has_not = false; unique_ptr expression; + vector> casts; }; struct ParsedOperator { @@ -31,7 +31,7 @@ struct OtherOperatorTail { unique_ptr expression; }; -enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR }; +enum class ExpressionTailType : uint8_t { IS_TEST, DISTINCT, COMPARISON, OTHER_OPERATOR, CAST }; struct IsExpressionTail { ExpressionTailType type = ExpressionTailType::IS_TEST; @@ -39,6 +39,7 @@ struct IsExpressionTail { IsDistinctFromTail distinct; ComparisonExpressionTail comparison; OtherOperatorTail other; + unique_ptr cast; }; struct BinaryExpressionTail { diff --git a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp index 1d5f222e6..f8a55ed8f 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/compiled_grammar.hpp @@ -1,5 +1,6 @@ #pragma once +#include "duckdb/common/pair.hpp" #include "duckdb/parser/peg/matcher.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/peg/parsed_grammar.hpp" @@ -41,7 +42,7 @@ struct CompiledGrammar { static shared_ptr DefaultGrammar(); //! Compile the base DuckDB grammar. static shared_ptr Create(); - //! Compile a grammar for the selected extensions without changing the client configuration. + //! Get the (cached) grammar for the selected extensions without changing the client configuration. static shared_ptr Create(const ClientContext &context, const vector &active_extensions); private: @@ -53,19 +54,27 @@ struct CompiledGrammar { const Matcher &top_level_statement_matcher; }; -//! Per-database holder for the compiled base grammar. +//! Per-database holder for the compiled base grammar and the grammars compiled for active grammar extensions. struct ParserCache { public: ParserCache(); ~ParserCache(); + //! The maximum number of grammars compiled for grammar extensions that are kept + static constexpr idx_t MAX_CACHED_EXTENSION_MATCHERS = 16; + shared_ptr GetMatcher(); + //! The grammar compiled for the given grammar extensions, in order - shared by all connections that use them + shared_ptr GetMatcher(const ClientContext &context, const vector &active_extensions); //! The grammar that forwards statements instead of interpreting them, used while CONNECT-ed shared_ptr GetPassthroughMatcher(const ClientContext &context); private: std::mutex mutex; shared_ptr matcher; + std::mutex extension_mutex; + //! The compiled grammars with the names of their grammar extensions in order, from least to most recently used + vector, shared_ptr>> extension_matchers; std::mutex passthrough_mutex; unique_ptr passthrough_dialect; }; diff --git a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp index 42cd7a5c1..0b57885bf 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/inlined_grammar.hpp @@ -1381,7 +1381,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 32, 60, 45, 32, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 47, 32, 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 47, 32, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 10, 67, 114, 101, 97, 116, 101, 84, 121, 112, 101, 70, 114, 111, 109, 84, 121, 112, 101, 32, 60, 45, 32, 84, 121, 112, 101, 10, 69, 110, 117, 109, 83, 101, 108, 101, 99, 116, 84, 121, 112, 101, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 41, 10, - 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 10, + 69, 110, 117, 109, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 76, 105, 115, 116, 32, 60, 45, 32, 39, 69, 78, 85, 77, 39, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 83, 116, 114, 105, 110, 103, 76, 105, 116, 101, 114, 97, 108, 41, 63, 41, 32, 65, 114, 114, 97, 121, 66, 111, 117, 110, 100, 115, 42, 10, 67, 114, 101, 97, 116, 101, 86, 105, 101, 119, 83, 116, 109, 116, 32, 60, 45, 32, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 63, 32, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 63, 32, 39, 86, 73, 69, 87, 39, 32, 73, 102, 78, 111, 116, 69, 120, 105, 115, 116, 115, 63, 32, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 32, 73, 110, 115, 101, 114, 116, 67, 111, 108, 117, 109, 110, 76, 105, 115, 116, 63, 32, 87, 105, 116, 104, 76, 105, 115, 116, 63, 32, 39, 65, 83, 39, 32, 83, 101, 108, 101, 99, 116, 83, 116, 97, 116, 101, 109, 101, 110, 116, 73, 110, 116, 101, 114, 110, 97, 108, 10, 67, 114, 101, 97, 116, 101, 82, 101, 99, 117, 114, 115, 105, 118, 101, 32, 60, 45, 32, 39, 82, 69, 67, 85, 82, 83, 73, 86, 69, 39, 10, 67, 114, 101, 97, 116, 101, 83, 101, 99, 117, 114, 101, 32, 60, 45, 32, 39, 83, 69, 67, 85, 82, 69, 39, 10, @@ -1445,7 +1445,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 9, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 10, 68, 114, 111, 112, 84, 114, 105, 103, 103, 101, 114, 32, 60, 45, 32, 39, 84, 82, 73, 71, 71, 69, 82, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 84, 114, 105, 103, 103, 101, 114, 78, 97, 109, 101, 32, 39, 79, 78, 39, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 10, 68, 114, 111, 112, 84, 97, 98, 108, 101, 32, 60, 45, 32, 84, 97, 98, 108, 101, 79, 114, 86, 105, 101, 119, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 41, 10, - 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 41, 10, + 68, 114, 111, 112, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 111, 109, 109, 101, 110, 116, 77, 97, 99, 114, 111, 84, 97, 98, 108, 101, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 41, 10, 68, 114, 111, 112, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 70, 117, 110, 99, 116, 105, 111, 110, 84, 121, 112, 101, 77, 97, 99, 114, 111, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 41, 10, 68, 114, 111, 112, 83, 99, 104, 101, 109, 97, 32, 60, 45, 32, 39, 83, 67, 72, 69, 77, 65, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 78, 97, 109, 101, 41, 10, 68, 114, 111, 112, 73, 110, 100, 101, 120, 32, 60, 45, 32, 39, 73, 78, 68, 69, 88, 39, 32, 73, 102, 69, 120, 105, 115, 116, 115, 63, 32, 76, 105, 115, 116, 40, 81, 117, 97, 108, 105, 102, 105, 101, 100, 73, 110, 100, 101, 120, 78, 97, 109, 101, 41, 10, @@ -1664,11 +1664,12 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 35, 32, 76, 69, 86, 69, 76, 32, 52, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 63, 10, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 67, 111, 110, 116, 105, 110, 117, 97, 116, 105, 111, 110, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, + 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 47, 32, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 47, 32, 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 10, 73, 115, 84, 101, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 84, 101, 115, 116, 10, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 84, 97, 105, 108, 32, 60, 45, 32, 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 84, 97, 105, 108, 10, 73, 115, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 10, 73, 115, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 84, 97, 105, 108, 10, + 73, 115, 67, 97, 115, 116, 84, 97, 105, 108, 32, 60, 45, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 10, 73, 115, 84, 101, 115, 116, 32, 60, 45, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 111, 116, 78, 117, 108, 108, 32, 47, 32, 73, 115, 78, 117, 108, 108, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 10, 73, 115, 76, 105, 116, 101, 114, 97, 108, 86, 97, 108, 117, 101, 32, 60, 45, 32, 84, 114, 117, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 70, 97, 108, 115, 101, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 78, 117, 108, 108, 76, 105, 116, 101, 114, 97, 108, 32, 47, 32, 85, 110, 107, 110, 111, 119, 110, 76, 105, 116, 101, 114, 97, 108, 10, @@ -1684,7 +1685,10 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 73, 115, 68, 105, 115, 116, 105, 110, 99, 116, 70, 114, 111, 109, 79, 112, 32, 60, 45, 32, 39, 73, 83, 39, 32, 39, 78, 79, 84, 39, 63, 32, 39, 68, 73, 83, 84, 73, 78, 67, 84, 39, 32, 39, 70, 82, 79, 77, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 54, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 42, 10, - 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 63, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 84, 97, 105, 108, 32, 60, 45, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 10, + 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 47, 32, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, + 35, 32, 78, 79, 84, 32, 97, 102, 116, 101, 114, 32, 97, 32, 99, 111, 109, 112, 97, 114, 105, 115, 111, 110, 32, 111, 112, 101, 114, 97, 116, 111, 114, 32, 98, 105, 110, 100, 115, 32, 101, 118, 101, 114, 121, 116, 104, 105, 110, 103, 32, 117, 112, 32, 116, 111, 32, 116, 104, 101, 32, 73, 83, 32, 108, 101, 118, 101, 108, 58, 32, 97, 32, 60, 32, 78, 79, 84, 32, 98, 32, 73, 83, 32, 78, 85, 76, 76, 32, 105, 115, 32, 97, 32, 60, 32, 40, 78, 79, 84, 32, 40, 98, 32, 73, 83, 32, 78, 85, 76, 76, 41, 41, 10, + 78, 111, 116, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 110, 100, 32, 60, 45, 32, 78, 111, 116, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 115, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 67, 111, 109, 112, 97, 114, 105, 115, 111, 110, 79, 112, 101, 114, 97, 116, 111, 114, 32, 60, 45, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 69, 113, 117, 97, 108, 32, 47, 10, 32, 32, 32, 32, 79, 112, 101, 114, 97, 116, 111, 114, 78, 111, 116, 69, 113, 117, 97, 108, 32, 47, 10, @@ -1700,7 +1704,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 79, 112, 101, 114, 97, 116, 111, 114, 71, 114, 101, 97, 116, 101, 114, 84, 104, 97, 110, 69, 113, 117, 97, 108, 115, 32, 60, 45, 32, 39, 62, 61, 39, 10, 35, 32, 76, 69, 86, 69, 76, 32, 55, 10, 66, 101, 116, 119, 101, 101, 110, 73, 110, 76, 105, 107, 101, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 42, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 63, 10, - 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 10, + 73, 110, 80, 114, 101, 100, 105, 99, 97, 116, 101, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 73, 110, 67, 108, 97, 117, 115, 101, 32, 67, 97, 115, 116, 79, 112, 101, 114, 97, 116, 111, 114, 42, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 32, 60, 45, 32, 39, 78, 79, 84, 39, 63, 32, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 66, 101, 116, 119, 101, 101, 110, 76, 105, 107, 101, 79, 112, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 60, 45, 32, 66, 101, 116, 119, 101, 101, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 10, 76, 105, 107, 101, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 76, 105, 107, 101, 86, 97, 114, 105, 97, 116, 105, 111, 110, 115, 32, 79, 116, 104, 101, 114, 79, 112, 101, 114, 97, 116, 111, 114, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 32, 69, 115, 99, 97, 112, 101, 67, 108, 97, 117, 115, 101, 63, 10, @@ -1998,7 +2002,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 84, 97, 98, 108, 101, 83, 117, 98, 113, 117, 101, 114, 121, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 76, 97, 116, 101, 114, 97, 108, 63, 32, 83, 117, 98, 113, 117, 101, 114, 121, 82, 101, 102, 101, 114, 101, 110, 99, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 66, 97, 115, 101, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 66, 97, 115, 101, 84, 97, 98, 108, 101, 78, 97, 109, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 65, 116, 67, 108, 97, 117, 115, 101, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 32, 60, 45, 32, 67, 111, 108, 73, 100, 79, 114, 83, 116, 114, 105, 110, 103, 32, 39, 58, 39, 10, - 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, + 86, 97, 108, 117, 101, 115, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 86, 97, 108, 117, 101, 115, 67, 108, 97, 117, 115, 101, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 80, 97, 114, 101, 110, 115, 84, 97, 98, 108, 101, 82, 101, 102, 32, 60, 45, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 67, 111, 108, 111, 110, 63, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 82, 101, 102, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 32, 83, 97, 109, 112, 108, 101, 67, 108, 97, 117, 115, 101, 63, 10, 74, 111, 105, 110, 79, 114, 80, 105, 118, 111, 116, 32, 60, 45, 32, 74, 111, 105, 110, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 47, 32, 84, 97, 98, 108, 101, 85, 110, 112, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 10, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 32, 60, 45, 32, 39, 80, 73, 86, 79, 84, 39, 32, 80, 97, 114, 101, 110, 115, 40, 84, 97, 98, 108, 101, 80, 105, 118, 111, 116, 67, 108, 97, 117, 115, 101, 66, 111, 100, 121, 41, 32, 84, 97, 98, 108, 101, 65, 108, 105, 97, 115, 63, 10, @@ -2027,7 +2031,7 @@ const unsigned char INLINED_PEG_GRAMMAR[] = { 81, 117, 97, 108, 105, 102, 105, 101, 100, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 32, 60, 45, 32, 67, 97, 116, 97, 108, 111, 103, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 63, 32, 83, 99, 104, 101, 109, 97, 81, 117, 97, 108, 105, 102, 105, 99, 97, 116, 105, 111, 110, 42, 32, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 78, 97, 109, 101, 10, 84, 97, 98, 108, 101, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 115, 32, 60, 45, 32, 80, 97, 114, 101, 110, 115, 40, 76, 105, 115, 116, 40, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 41, 63, 41, 10, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 47, 32, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 10, - 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 10, + 78, 97, 109, 101, 100, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 80, 111, 115, 105, 116, 105, 111, 110, 97, 108, 70, 117, 110, 99, 116, 105, 111, 110, 65, 114, 103, 117, 109, 101, 110, 116, 32, 60, 45, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 32, 60, 45, 32, 84, 121, 112, 101, 70, 117, 110, 99, 78, 97, 109, 101, 32, 84, 121, 112, 101, 63, 32, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 69, 120, 112, 114, 101, 115, 115, 105, 111, 110, 10, 78, 97, 109, 101, 100, 80, 97, 114, 97, 109, 101, 116, 101, 114, 65, 115, 115, 105, 103, 110, 109, 101, 110, 116, 32, 60, 45, 32, 39, 58, 61, 39, 32, 47, 32, 39, 61, 62, 39, 10, diff --git a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp index b7358cb14..64936d1c2 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/matcher/number_literal_matcher.hpp @@ -61,7 +61,15 @@ class NumberLiteralMatcher : public AtomicMatcher { return false; } bool scientific_notation = false; + bool has_dot = token_text[0] == '.'; for (idx_t i = 1; i < token_text.size(); i++) { + if (token_text[i] == '.') { + // a number has at most one dot, and none in its exponent + if (has_dot || scientific_notation) { + return false; + } + has_dot = true; + } if (Tokenizer::CharacterIsScientific(token_text[i])) { if (scientific_notation) { throw ParserException("Already found scientific notation"); diff --git a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp index 815164e5e..7fa82e018 100644 --- a/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp +++ b/src/duckdb/src/include/duckdb/parser/peg/transformer/peg_transformer.hpp @@ -2647,6 +2647,9 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeIsCastTailTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -2692,6 +2695,13 @@ class PEGTransformerFactory { GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static void InitializeComparisonOperandTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); + static arena_ptr FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static void InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); + static arena_ptr FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process); static void InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); static arena_ptr FinalizeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process); @@ -4747,7 +4757,8 @@ class PEGTransformerFactory { static unique_ptr TransformEnumSelectType(PEGTransformer &transformer, unique_ptr select_statement_internal); static unique_ptr TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal); + const optional> &string_literal, + const optional> &array_bounds); static unique_ptr TransformCreateViewStmt(PEGTransformer &transformer, const optional &create_secure, const optional &create_recursive, const optional &if_not_exists, @@ -4821,7 +4832,7 @@ class PEGTransformerFactory { static unique_ptr TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name); + const vector &table_function_name); static unique_ptr TransformDropFunction(PEGTransformer &transformer, const bool &function_type_macro, const optional &if_exists, const vector &function_identifier); @@ -5121,7 +5132,7 @@ class PEGTransformerFactory { TransformColDefAndExpressionTail(PEGTransformer &transformer, unique_ptr is_distinct_from_expression); static unique_ptr TransformLogicalNotExpression(PEGTransformer &transformer, - optional> not_expression, + const optional> ¬_expression, unique_ptr is_expression); static vector TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword); static bool TransformNotKeyword(PEGTransformer &transformer); @@ -5138,6 +5149,8 @@ class PEGTransformerFactory { ComparisonExpressionTail comparison_expression_tail); static IsExpressionTail TransformIsOtherOperatorTail(PEGTransformer &transformer, OtherOperatorTail other_operator_tail); + static IsExpressionTail TransformIsCastTail(PEGTransformer &transformer, + unique_ptr cast_operator); static unique_ptr TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value); static Value TransformUnknownLiteral(PEGTransformer &transformer); @@ -5156,10 +5169,12 @@ class PEGTransformerFactory { static unique_ptr TransformComparisonExpression(PEGTransformer &transformer, unique_ptr between_in_like_expression, optional> comparison_expression_tail); - static ComparisonExpressionTail - TransformComparisonExpressionTail(PEGTransformer &transformer, const ExpressionType &comparison_operator, - optional> not_expression, - unique_ptr between_in_like_expression); + static ComparisonExpressionTail TransformComparisonExpressionTail(PEGTransformer &transformer, + const ExpressionType &comparison_operator, + unique_ptr comparison_operand); + static unique_ptr TransformNotComparisonOperand(PEGTransformer &transformer, + const vector ¬_expression, + unique_ptr is_expression); static ExpressionType TransformOperatorEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorNotEqual(PEGTransformer &transformer); static ExpressionType TransformOperatorLessThan(PEGTransformer &transformer); @@ -5170,7 +5185,8 @@ class PEGTransformerFactory { PEGTransformer &transformer, unique_ptr other_operator_expression, optional> in_predicate, optional between_like_op); static BetweenInLikeOperator TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause); + unique_ptr in_clause, + optional>> cast_operator); static BetweenInLikeOperator TransformBetweenLikeOp(PEGTransformer &transformer, const bool &has_result, unique_ptr between_like_op_expression); static unique_ptr TransformLikeClause(PEGTransformer &transformer, const string &like_variations, @@ -5607,7 +5623,8 @@ class PEGTransformerFactory { static unique_ptr TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias); + const optional &table_alias, + optional> sample_clause); static unique_ptr TransformParensTableRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr table_ref, @@ -5669,7 +5686,9 @@ class PEGTransformerFactory { const Identifier &table_function_name); static vector TransformTableFunctionArguments(PEGTransformer &transformer, optional> function_argument); - static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, MacroParameter named_parameter); + static FunctionArgument TransformNamedFunctionArgument(PEGTransformer &transformer, + const Identifier &type_func_name, + unique_ptr expression); static FunctionArgument TransformPositionalFunctionArgument(PEGTransformer &transformer, unique_ptr expression); static MacroParameter TransformNamedParameter(PEGTransformer &transformer, const Identifier &type_func_name, diff --git a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp index 8b9d9ab16..2745d3e32 100644 --- a/src/duckdb/src/include/duckdb/planner/expression_binder.hpp +++ b/src/duckdb/src/include/duckdb/planner/expression_binder.hpp @@ -112,6 +112,8 @@ class ExpressionBinder { optional_ptr macro_binding; optional_ptr> lambda_bindings; + //! The number of (outer) lambda bindings whose parameters can not be referenced, e.g. inside an aggregate + idx_t inaccessible_lambda_binding_count = 0; public: unique_ptr Bind(unique_ptr &expr, optional_ptr result_type = nullptr, diff --git a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp index 262c0d5b3..aa07f4491 100644 --- a/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp +++ b/src/duckdb/src/include/duckdb/planner/filter/table_filter_functions.hpp @@ -9,6 +9,7 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/mutex.hpp" #include "duckdb/common/optional_ptr.hpp" #include "duckdb/common/enums/expression_type.hpp" #include "duckdb/common/enums/filter_propagate_result.hpp" @@ -219,9 +220,9 @@ struct DynamicFilterData { public: DynamicFilterData(ExpressionType comparison_type, Value constant); - mutex lock; + mutable annotated_shared_mutex lock; ExpressionType comparison_type; - Value constant; + Value constant DUCKDB_GUARDED_BY(lock); atomic initialized = {false}; unique_ptr ToExpression(const Expression &column) const; diff --git a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp index 7a67a26d9..3e0a7a6a4 100644 --- a/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp +++ b/src/duckdb/src/include/duckdb/planner/operator/logical_join.hpp @@ -31,6 +31,10 @@ class LogicalJoin : public LogicalOperator { static void GetExpressionBindings(const Expression &expr, unordered_set &bindings); //! Moves the state shared by all logical joins when replacing one join operator with another static void MoveJoinState(LogicalJoin &source, LogicalJoin &target); + //! Replaces a join by one of its sides while keeping the join's output layout: the side's projection map, if + //! any, is carried by a filter without conditions so that operators above the join keep seeing the same columns + static unique_ptr PromoteJoinSide(unique_ptr child, + vector projection_map); bool HasProjectionMap() const override { return !left_projection_map.empty() || !right_projection_map.empty(); diff --git a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp index 84e7d68cc..211a6158b 100644 --- a/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp +++ b/src/duckdb/src/include/duckdb/planner/sql_export_helpers.hpp @@ -14,6 +14,7 @@ #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/common/type_visitor.hpp" +#include "duckdb/planner/expression.hpp" #include "duckdb/planner/logical_plan_verification_result.hpp" namespace duckdb { @@ -67,6 +68,12 @@ struct SQLExportHelpers { return issue; } + //! Constant ORDER BY keys have no effect, and a literal key would be bound as a position or rejected + static bool IsConstantOrderKey(const Expression &bound, const ParsedExpression &exported) { + return bound.GetExpressionClass() == ExpressionClass::BOUND_CONSTANT || + exported.GetExpressionClass() == ExpressionClass::CONSTANT; + } + static unique_ptr OrderExpression(const LogicalType &type, unique_ptr expression) { if (type.id() == LogicalTypeId::VARCHAR) { diff --git a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp index 0a5dff2c1..329c24704 100644 --- a/src/duckdb/src/include/duckdb/storage/block_allocator.hpp +++ b/src/duckdb/src/include/duckdb/storage/block_allocator.hpp @@ -46,10 +46,10 @@ class BlockAllocator { bool SupportsFlush() const; void ThreadFlush(bool allocator_background_threads, idx_t threshold, idx_t thread_count) const; void FlushAll(optional_idx extra_memory = optional_idx()) const; - //! Memory freed since the last flush, which the allocator may still hold - idx_t GetDeallocatedSinceFlush() const; - //! Flush if at least threshold bytes were freed since the last flush, returns whether a flush happened - bool TryFlushDeallocated(idx_t threshold) const; + //! Estimated memory freed but still held by the allocator + idx_t GetEstimatedUnflushedMemory() const; + //! Flush if at least threshold bytes were freed since the last reclamation check, returns whether a flush happened + bool TryFlushDeallocated(idx_t threshold, idx_t memory_limit, idx_t used_memory) const; private: bool IsActive() const; @@ -72,8 +72,8 @@ class BlockAllocator { const hugeint_t uuid; //! Fallback allocator Allocator &allocator; - //! Memory freed since the last flush - mutable atomic deallocated_since_flush {0}; + //! Estimated memory freed but still held by the allocator + mutable atomic estimated_unflushed_memory {0}; //! Block size (power of two) const idx_t block_size; diff --git a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp index 0af0858c3..25a39088e 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/dict_fsst/decompression.hpp @@ -26,7 +26,7 @@ struct CompressedStringScanState : public SegmentScanState { void ScanToDictionaryVector(ColumnSegment &segment, Vector &result, idx_t result_offset, idx_t start, idx_t scan_count); const SelectionVector &GetSelVec(idx_t start, idx_t scan_count); - void Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count); + void Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, idx_t sel_count); bool AllowDictionaryScan(idx_t scan_count); diff --git a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp index 3d13ccad0..099a2d2f6 100644 --- a/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp +++ b/src/duckdb/src/include/duckdb/storage/compression/empty_validity.hpp @@ -98,7 +98,7 @@ class EmptyValidityCompression { TableFilterState &filter_state) { } static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { } }; diff --git a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp index 2befc2e99..df0b6ac82 100644 --- a/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp +++ b/src/duckdb/src/include/duckdb/storage/string_uncompressed.hpp @@ -120,7 +120,7 @@ struct UncompressedStringStorage { idx_t result_offset); static void StringScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, Vector &result); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp index 47b8893ea..8456d63dd 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_data.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_data.hpp @@ -247,7 +247,7 @@ class ColumnData : public enable_shared_from_this { idx_t ScanVector(TransactionData transaction, idx_t vector_index, ColumnScanState &state, Vector &result, idx_t target_scan, UpdateScanType update_type); void SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count); + idx_t sel_count, ScanVectorType scan_type); void FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp index aac02c130..25a5cf012 100644 --- a/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp +++ b/src/duckdb/src/include/duckdb/storage/table/column_segment.hpp @@ -63,7 +63,8 @@ class ColumnSegment : public SegmentBase { //! Scan one vector from this segment void Scan(ColumnScanState &state, idx_t scan_count, Vector &result, idx_t result_offset, ScanVectorType scan_type); //! Scan a subset of a vector (defined by the selection vector) - void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count); + void Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, idx_t sel_count, + idx_t result_offset, ScanVectorType scan_type); //! Scan one vector while applying a filter to the vector, returning only the matching elements void Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, idx_t &sel_count, const TableFilter &filter, TableFilterState &filter_state); diff --git a/src/duckdb/src/main/capi/v1/arrow-c.cpp b/src/duckdb/src/main/capi/v1/arrow-c.cpp index e07b834e4..d2432e60c 100644 --- a/src/duckdb/src/main/capi/v1/arrow-c.cpp +++ b/src/duckdb/src/main/capi/v1/arrow-c.cpp @@ -138,17 +138,18 @@ duckdb_error_data duckdb_data_chunk_from_arrow(duckdb_connection connection, str auto &arrow_types = arrow_table->GetColumns(); dchunk->SetChildCardinality(duckdb::NumericCast(arrow_array->length)); + // All columns share ownership of the array, so it is only released once no column references it anymore + auto owned_data = duckdb::make_shared_ptr(); + owned_data->arrow_array = *arrow_array; + // We set it to nullptr to effectively transfer the ownership + arrow_array->release = nullptr; for (idx_t i = 0; i < dchunk->ColumnCount(); i++) { auto &parent_array = *arrow_array; auto &array = parent_array.children[i]; auto arrow_type = arrow_types.at(i); auto array_physical_type = arrow_type->GetPhysicalType(); auto array_state = duckdb::make_uniq(*conn->context); - // We need to make sure that our chunk will hold the ownership - array_state->owned_data = duckdb::make_shared_ptr(); - array_state->owned_data->arrow_array = *arrow_array; - // We set it to nullptr to effectively transfer the ownership - arrow_array->release = nullptr; + array_state->owned_data = owned_data; try { switch (array_physical_type) { case duckdb::ArrowArrayPhysicalType::DICTIONARY_ENCODED: diff --git a/src/duckdb/src/main/client_context.cpp b/src/duckdb/src/main/client_context.cpp index 7db374a9e..62ae13551 100644 --- a/src/duckdb/src/main/client_context.cpp +++ b/src/duckdb/src/main/client_context.cpp @@ -1106,7 +1106,16 @@ void ClientContext::InterruptCheck() const { throw InterruptException(); } // Only check timeout every N calls to avoid expensive steady_clock::now() syscall - if (query_deadline.IsValid() && ++timeout_check_counter % TIMEOUT_CHECK_INTERVAL == 0 && QueryDeadlinePassed()) { + if (query_deadline.IsValid() && ++timeout_check_counter % TIMEOUT_CHECK_INTERVAL == 0) { + BlockingInterruptCheck(); + } +} + +void ClientContext::BlockingInterruptCheck() const { + if (IsInterrupted()) { + throw InterruptException(); + } + if (QueryDeadlinePassed()) { throw InterruptException(QUERY_DEADLINE_ERROR); } } diff --git a/src/duckdb/src/main/client_context_execution.cpp b/src/duckdb/src/main/client_context_execution.cpp index 147fcfd40..2b204776e 100644 --- a/src/duckdb/src/main/client_context_execution.cpp +++ b/src/duckdb/src/main/client_context_execution.cpp @@ -467,6 +467,7 @@ bool ClientContext::IsActiveResult(ClientContextLock &lock, BaseQueryResult &res } bool ClientContext::ExecutionIsFinished() { + lock_guard guard(context_lock); if (!active_query || !active_query->executor || active_query->HasNextStatement()) { return false; } diff --git a/src/duckdb/src/main/config.cpp b/src/duckdb/src/main/config.cpp index 66f3ed20b..83c4bf9ba 100644 --- a/src/duckdb/src/main/config.cpp +++ b/src/duckdb/src/main/config.cpp @@ -922,8 +922,8 @@ const ExtensionCallbackManager &DBConfig::GetCallbackManager() const { return *callback_manager; } -string DBConfig::SanitizeAllowedPath(const string &path_p) const { - auto result = file_system->CanonicalizePath(path_p); +string DBConfig::SanitizeAllowedPath(const string &path_p, optional_ptr opener) const { + auto result = file_system->CanonicalizePath(FileSystem::ExpandPath(path_p, opener)); // allowed_directories/allowed_path always uses forward slashes regardless of the OS auto path_sep = file_system->PathSeparator(path_p); if (path_sep != "/") { @@ -976,6 +976,21 @@ void DBConfig::AddAllowedDirectory(const string &path) { options.allowed_directories.insert(allowed_directory); } +bool DBConfig::IsOptimizerDisabled(OptimizerType type) const { + lock_guard guard(disabled_optimizers_lock); + return options.disabled_optimizers.find(type) != options.disabled_optimizers.end(); +} + +set DBConfig::GetDisabledOptimizers() const { + lock_guard guard(disabled_optimizers_lock); + return options.disabled_optimizers; +} + +void DBConfig::SetDisabledOptimizers(set disabled_optimizers) { + lock_guard guard(disabled_optimizers_lock); + options.disabled_optimizers = std::move(disabled_optimizers); +} + void DBConfig::AddAllowedPath(const string &path) { auto allowed_path = SanitizeAllowedPath(path); lock_guard guard(allowed_paths_lock); @@ -999,12 +1014,12 @@ vector DBConfig::GetAllowedPaths() const { return vector(options.allowed_paths.begin(), options.allowed_paths.end()); } -bool DBConfig::CanAccessFile(const string &input_path, FileType type) { +bool DBConfig::CanAccessFile(const string &input_path, FileType type, optional_ptr opener) { if (Settings::Get(*this)) { // all external access is allowed return true; } - string path = SanitizeAllowedPath(input_path); + string path = SanitizeAllowedPath(input_path, opener); lock_guard guard(allowed_paths_lock); if (options.allowed_paths.count(path) > 0) { diff --git a/src/duckdb/src/main/extension/extension_helper.cpp b/src/duckdb/src/main/extension/extension_helper.cpp index 7ab8b9303..2835029e8 100644 --- a/src/duckdb/src/main/extension/extension_helper.cpp +++ b/src/duckdb/src/main/extension/extension_helper.cpp @@ -237,6 +237,9 @@ bool ExtensionHelper::TryAutoLoadAvailableExtension(DatabaseInstance &instance, return true; } try { + if (!Settings::Get(instance)) { + return false; + } auto &fs = FileSystem::GetFileSystem(instance); ExtensionHelper::LoadExternalExtension(instance, fs, AutoLoadOptions(extension_name)); return true; diff --git a/src/duckdb/src/main/extension_manager.cpp b/src/duckdb/src/main/extension_manager.cpp index c888a2405..ee186a7fa 100644 --- a/src/duckdb/src/main/extension_manager.cpp +++ b/src/duckdb/src/main/extension_manager.cpp @@ -56,7 +56,7 @@ void ExtensionManager::RemoveExtensionInfo(const string &name) { loaded_extensions_info.erase(Identifier(name)); } -optional_ptr ExtensionManager::GetExtensionInfo(const string &name) { +shared_ptr ExtensionManager::GetExtensionInfo(const string &name) { auto extension_name = ExtensionHelper::GetExtensionName(name); lock_guard guard(lock); @@ -64,7 +64,7 @@ optional_ptr ExtensionManager::GetExtensionInfo(const string &nam if (entry == loaded_extensions_info.end()) { return nullptr; } - return entry->second.get(); + return entry->second; } vector ExtensionManager::GetExtensions() { @@ -101,7 +101,7 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO extension_name = ExtensionHelper::GetExtensionName(options.extension_name); string original_extension_name = extension_name; - optional_ptr info; + shared_ptr info; auto entry = loaded_extensions_info.end(); if (options.alias.empty()) { @@ -113,9 +113,9 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO // check if the extension is not already registered under an alias // we don't have an entry yet - create one - auto extension_info = make_uniq(); + auto extension_info = make_shared_ptr(); extension_info->orig_ext_name = original_extension_name; - info = extension_info.get(); + info = extension_info; if (!options.alias.empty()) { // if aliasing is used, we need to register the alias instead of extension name @@ -142,13 +142,13 @@ unique_ptr ExtensionManager::BeginLoad(const ExtensionLoadO return nullptr; } // it is not loaded yet - try to load it - info = entry->second.get(); + info = entry->second; } extension_list_lock.unlock(); // we have an extension and we want to try to load it - instantiate the load // we instantiate the ExtensionActiveLoad which also grabs the lock for loading the specific extension - auto result = make_uniq(db, *info, Identifier(original_extension_name), options.alias); + auto result = make_uniq(db, info, Identifier(original_extension_name), options.alias); // we now have a lock for loading the extension // HOWEVER - another thread might have finished loading in the meantime - double check to avoid a double load diff --git a/src/duckdb/src/main/http/http_retry_budget.cpp b/src/duckdb/src/main/http/http_retry_budget.cpp index 3fb00e874..7ba67f2a8 100644 --- a/src/duckdb/src/main/http/http_retry_budget.cpp +++ b/src/duckdb/src/main/http/http_retry_budget.cpp @@ -3,11 +3,14 @@ #include "duckdb/common/limits.hpp" #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/random_engine.hpp" +#include "duckdb/common/string_util.hpp" +#include "duckdb/common/types/timestamp.hpp" +#include "duckdb/function/scalar/strftime_format.hpp" #include "duckdb/main/http/http_util.hpp" +#include #ifndef DUCKDB_NO_THREADS #include -#include #include #endif @@ -17,22 +20,226 @@ HTTPRetryDecision HTTPRetryDecision::Finish() { return HTTPRetryDecision(Type::FINISH); } +HTTPRetryDecision HTTPRetryDecision::Success() { + return HTTPRetryDecision(Type::SUCCESS); +} + +HTTPRetryDecision HTTPRetryDecision::Failure() { + return HTTPRetryDecision(Type::FAILURE); +} + +HTTPRetryDecision HTTPRetryDecision::Correction() { + return HTTPRetryDecision(Type::CORRECTION); +} + HTTPRetryDecision HTTPRetryDecision::Retry() { return HTTPRetryDecision(Type::RETRY); } +HTTPRetryDecision HTTPRetryDecision::Transient() { + return HTTPRetryDecision(Type::TRANSIENT); +} + HTTPRetryDecision HTTPRetryDecision::Throttled(const string &retry_after) { return HTTPRetryDecision(Type::THROTTLED, retry_after); } -HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms) - : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff) { +void HTTPRetryQuota::Touch(int64_t now_ms) { + if (now_ms - last_used_ms >= IDLE_RESET_MS) { + capacity = INITIAL_CAPACITY; + } + last_used_ms = now_ms; +} + +bool HTTPRetryQuota::TryAcquire(int64_t cost, int64_t now_ms) { + lock_guard guard(lock); + Touch(now_ms); + if (cost > capacity) { + return false; + } + capacity -= cost; + return true; +} + +void HTTPRetryQuota::Release(int64_t amount, int64_t now_ms) { + lock_guard guard(lock); + Touch(now_ms); + capacity = MinValue(capacity + amount, INITIAL_CAPACITY); +} + +int64_t HTTPRetryQuota::Available() const { + lock_guard guard(lock); + return capacity; +} + +int64_t HTTPRetryQuota::IdleMs(int64_t now_ms) const { + lock_guard guard(lock); + return now_ms - last_used_ms; +} + +HTTPRetryBudget::HTTPRetryBudget(const HTTPParams ¶ms, HTTPRetryOutcomeOwner owner_p) + : retries(params.retries), retry_wait_ms(params.retry_wait_ms), retry_backoff(params.retry_backoff), + owner(owner_p) { +#ifdef DUCKDB_NO_THREADS + // Without threads there is no back-off sleep, so the schedule settings go unused. + (void)retry_wait_ms; + (void)retry_backoff; +#endif } uint64_t HTTPRetryBudget::ThrottledRetries() const { return throttled_retries; } +uint64_t HTTPRetryBudget::RetriesUsed() const { + return retries_used; +} + +uint64_t HTTPRetryBudget::BeginAttempt() { + return ++attempts; +} + +uint64_t HTTPRetryBudget::ThrottleExtraRetries() const { +#ifndef DUCKDB_NO_THREADS + // http_retries=0 switches retries off entirely, throttled or not. + return retries > 0 ? THROTTLE_EXTRA_RETRIES : 0; +#else + // Without threads we cannot sleep between retries, so do not add zero-delay retries. + return 0; +#endif +} + +uint64_t HTTPRetryBudget::Corrections() const { + // The first send and every admitted retry are accounted for; any other send was a corrected re-send. + const auto accounted = 1 + retries_used; + return attempts > accounted ? attempts - accounted : 0; +} + +uint64_t HTTPRetryBudget::MaxAttempts() const { + return 1 + retries + ThrottleExtraRetries() + Corrections(); +} + +bool HTTPRetryBudget::InRun() const { + return run_depth > 0; +} + +bool HTTPRetryBudget::QuotaExhausted() const { + return quota_exhausted; +} + +bool HTTPRetryBudget::CorrectionsExhausted() const { + return corrections_exhausted; +} + +void HTTPRetryBudget::BindQuota(shared_ptr quota_p) { + quota = std::move(quota_p); +} + +HTTPRetryOutcomeOwner HTTPRetryBudget::Owner() const { + return owner; +} + +void HTTPRetryBudget::RequireCoordinator(const char *what) const { + if (owner != HTTPRetryOutcomeOwner::COORDINATOR) { + throw InternalException( + "HTTP retry budget: %s is the coordinator's decision, but this operation's outcome is " + "owned by the HTTP status; construct the budget with HTTPRetryOutcomeOwner::COORDINATOR", + what); + } +} + +void HTTPRetryBudget::ReportSuccess() { + RequireCoordinator("ReportSuccess"); + Settle(true); +} + +void HTTPRetryBudget::ReportFailure() { + RequireCoordinator("ReportFailure"); + Settle(false); +} + +void HTTPRetryBudget::RecordStatusOutcome(bool success) { + if (owner != HTTPRetryOutcomeOwner::STATUS) { + // The coordinator validates the response and reports; what the status says is never final here. + return; + } + status_outcome = success ? RecordedOutcome::SUCCESS : RecordedOutcome::FAILURE; + if (!InRun()) { + // Nobody else is going to judge this response. + Settle(success); + } +} + +void HTTPRetryBudget::Settle(bool success) { + status_outcome = RecordedOutcome::NONE; + if (outcome_settled) { + // Refunded or charged already: the operation has one outcome. + return; + } + outcome_settled = true; + if (success) { + if (last_retry_quota) { + last_retry_quota->Release(last_retry_cost); + } else if (quota) { + quota->Release(HTTPRetryQuota::NO_RETRY_REFUND); + } + } + ForgetLastRetry(); +} + +void HTTPRetryBudget::ForgetLastRetry() { + last_retry_quota.reset(); + last_retry_cost = 0; +} + +uint64_t HTTPRetryBudget::BackoffMs(uint64_t retry_wait_ms, float retry_backoff, uint64_t retry, uint64_t floor_ms, + uint64_t random_value, bool throttled) { + D_ASSERT(retry > 0); + const auto exponent = static_cast(retry - 1); + // Cap in the double domain so the exponential cannot overflow the cast. + const auto base_ms = + MinValue(static_cast(retry_wait_ms) * pow(static_cast(retry_backoff), exponent), + static_cast(MAX_BACKOFF_MS)); + const auto base = static_cast(base_ms); + // Throttled retries never collapse onto the moment the server asked us to slow down. + const auto lower = throttled ? base / 2 : 0; + const auto jittered = lower + random_value % (base - lower + 1); + return MaxValue(jittered, MinValue(floor_ms, MAX_BACKOFF_MS)); +} + +static const StrpTimeFormat &RetryAfterDateFormat() { + // RFC 7231 IMF-fixdate, e.g. "Sun, 06 Nov 1994 08:49:37 GMT". + static const StrpTimeFormat format = []() { + StrpTimeFormat result; + auto error = StrTimeFormat::ParseFormatSpecifier("%a, %d %b %Y %H:%M:%S GMT", result); + D_ASSERT(error.empty()); + (void)error; + return result; + }(); + return format; +} + +bool HTTPRetryBudget::TryParseRetryAfterMs(const string &retry_after, uint64_t &delay_ms) { + string value = retry_after; + StringUtil::Trim(value); + if (value.empty()) { + return false; + } + uint64_t seconds = 0; + if (TryCast::Operation(string_t(value), seconds)) { + delay_ms = seconds > MAX_BACKOFF_MS / 1000 ? MAX_BACKOFF_MS : seconds * 1000; + return true; + } + timestamp_t target; + if (!RetryAfterDateFormat().TryParseTimestamp(value.c_str(), value.size(), target)) { + return false; + } + const auto now_ms = Timestamp::GetEpochMs(Timestamp::GetCurrentTimestamp()); + const auto target_ms = Timestamp::GetEpochMs(target); + delay_ms = target_ms > now_ms ? static_cast(target_ms - now_ms) : 0; + return true; +} + void HTTPRetryBudget::Run(const std::function &attempt) { Run(attempt, {}); } @@ -42,60 +249,110 @@ void HTTPRetryBudget::Run(const std::function &attempt, if (!attempt) { throw InternalException("HTTP retry loop requires an attempt callback"); } + // A refusal belongs to the run that hit it, not to later requests sharing the budget. + quota_exhausted = false; + corrections_exhausted = false; + const bool outermost = !InRun(); + if (outermost) { + // A new operation leg: whatever an earlier leg settled is history. + status_outcome = RecordedOutcome::NONE; + outcome_settled = false; + } + struct RunDepthGuard { + explicit RunDepthGuard(uint64_t &depth_p) : depth(depth_p) { + depth++; + } + ~RunDepthGuard() { + depth--; + } + uint64_t &depth; + } depth_guard(run_depth); for (;;) { auto decision = attempt(); - if (decision.type == HTTPRetryDecision::Type::FINISH || !ConsumeAndWait(decision)) { + switch (decision.type) { + case HTTPRetryDecision::Type::SUCCESS: + // The coordinator validated the response: the explicit outcome is final wherever it is decided. + RequireCoordinator("a Success decision"); + Settle(true); return; - } - if (before_retry) { - before_retry(); + case HTTPRetryDecision::Type::FAILURE: + RequireCoordinator("a Failure decision"); + Settle(false); + return; + case HTTPRetryDecision::Type::FINISH: + if (outermost && status_outcome != RecordedOutcome::NONE) { + // A status-owned operation is over and nobody judges it: the outcome core recorded from the status + // is settled now. Without one the caller records the outcome itself after the run. A + // coordinator-owned budget never records a status and waits for the coordinator's report. + Settle(status_outcome == RecordedOutcome::SUCCESS); + } + return; + case HTTPRetryDecision::Type::CORRECTION: + if (!AdmitCorrection()) { + return; + } + continue; + default: + if (!ConsumeAndWait(decision)) { + return; + } + if (before_retry) { + before_retry(); + } } } } +bool HTTPRetryBudget::AdmitCorrection() { + // The corrected request is a new send of the same operation: the last response does not count. + status_outcome = RecordedOutcome::NONE; + if (corrections_admitted >= MAX_CORRECTIONS) { + // The operation ends with the response it has, and its retries did not get it through. + corrections_exhausted = true; + ForgetLastRetry(); + return false; + } + corrections_admitted++; + return true; +} + bool HTTPRetryBudget::ConsumeAndWait(const HTTPRetryDecision &decision) { D_ASSERT(decision.type != HTTPRetryDecision::Type::FINISH); const bool throttled = decision.type == HTTPRetryDecision::Type::THROTTLED; -#ifndef DUCKDB_NO_THREADS - static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 5; -#else - // Without threads we cannot sleep between retries, so do not add zero-delay retries. - static constexpr uint64_t THROTTLE_EXTRA_RETRIES = 0; -#endif - const auto extra_retries = throttled ? THROTTLE_EXTRA_RETRIES : 0; + const auto extra_retries = throttled ? ThrottleExtraRetries() : 0; + // Asking for a retry means the last attempt did not get the operation through, whatever a nested loop recorded. + status_outcome = RecordedOutcome::NONE; if (retries_used >= retries && retries_used - retries >= extra_retries) { + // Nothing left to refund: the retries already spent did not get this request through. + ForgetLastRetry(); + return false; + } + const auto cost = decision.type == HTTPRetryDecision::Type::TRANSIENT ? HTTPRetryQuota::TRANSIENT_RETRY_COST + : HTTPRetryQuota::RETRY_COST; + if (quota && !quota->TryAcquire(cost)) { + quota_exhausted = true; + ForgetLastRetry(); return false; } + // The admitted retry is the one a later success refunds; its outcome is still open. + last_retry_quota = quota; + last_retry_cost = cost; + outcome_settled = false; retries_used++; if (throttled) { throttled_retries++; } #ifndef DUCKDB_NO_THREADS - if (retries_used > 1 || throttled) { - static constexpr uint64_t THROTTLE_MAX_BACKOFF_MS = 10000; - const auto backoff_exp = static_cast(throttled ? retries_used - 1 : retries_used - 2); - const auto backoff_ms = (double)retry_wait_ms * pow(retry_backoff, backoff_exp); - // Cap in the double domain to avoid overflow in the cast. - uint64_t sleep_amount = (uint64_t)MinValue(backoff_ms, (double)NumericLimits::Maximum()); - if (throttled) { - sleep_amount = MinValue(sleep_amount, THROTTLE_MAX_BACKOFF_MS); - if (!decision.retry_after.empty()) { - // Honor a numeric Retry-After (seconds), capped like the backoff. - uint64_t retry_after_s = 0; - if (TryCast::Operation(string_t(decision.retry_after), retry_after_s)) { - retry_after_s = MinValue(retry_after_s, THROTTLE_MAX_BACKOFF_MS / 1000); - sleep_amount = MaxValue(sleep_amount, retry_after_s * 1000); - } - } - // Subtractive jitter ([base/2, base]) de-synchronizes retry bursts while honoring the cap. - RandomEngine random; - sleep_amount -= random.NextRandomInteger64() % (sleep_amount / 2 + 1); - } - std::this_thread::sleep_for(std::chrono::milliseconds(sleep_amount)); + uint64_t floor_ms = 0; + if (throttled && !decision.retry_after.empty()) { + TryParseRetryAfterMs(decision.retry_after, floor_ms); + } + RandomEngine random; + const auto sleep_ms = + BackoffMs(retry_wait_ms, retry_backoff, retries_used, floor_ms, random.NextRandomInteger64(), throttled); + if (sleep_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } -#else - (void)retry_wait_ms; - (void)retry_backoff; #endif return true; } diff --git a/src/duckdb/src/main/http/http_util.cpp b/src/duckdb/src/main/http/http_util.cpp index cc7896ca2..435977999 100644 --- a/src/duckdb/src/main/http/http_util.cpp +++ b/src/duckdb/src/main/http/http_util.cpp @@ -123,17 +123,26 @@ bool HTTPResponse::ShouldRetry() const { // request errors are eligible for retry return true; } + // Core judges by status only; what a service says in the body is the provider's call (HTTPUtil::ShouldRetry). + return HTTPUtil::IsRetryableStatus(status); +} + +bool HTTPUtil::IsRetryableStatus(HTTPStatusCode status) { switch (status) { case HTTPStatusCode::RequestTimeout_408: case HTTPStatusCode::ImATeapot_418: case HTTPStatusCode::TooManyRequests_429: - case HTTPStatusCode::InternalServerError_500: - case HTTPStatusCode::BadGateway_502: - case HTTPStatusCode::ServiceUnavailable_503: - case HTTPStatusCode::GatewayTimeout_504: return true; - default: + // Server errors that describe the request or the server's abilities, not a passing condition. + case HTTPStatusCode::NotImplemented_501: + case HTTPStatusCode::HttpVersionNotSupported_505: + case HTTPStatusCode::NetworkAuthenticationRequired_511: return false; + default: { + // Any other server error is eligible, as in the AWS SDKs. + const auto code = static_cast(status); + return code >= 500 && code < 600; + } } } @@ -145,6 +154,32 @@ bool HTTPUtil::ShouldRetry(const BaseRequest &request, const HTTPResponse &respo return response.ShouldRetry(); } +void HTTPUtil::PrepareAttempt(BaseRequest &request, idx_t attempt, idx_t max_attempts) { + // default: requests are sent as-is +} + +shared_ptr HTTPUtil::GetRetryQuota(const string &proto_host_port) { + lock_guard guard(retry_quota_lock); + auto entry = retry_quotas.find(proto_host_port); + if (entry != retry_quotas.end()) { + return entry->second; + } + if (retry_quotas.size() >= MAX_RETRY_QUOTAS) { + // Forget the host that has been quiet for longest; an idle quota would have been reset anyway. + const auto now_ms = TimePoint::GetTickMs(); + auto victim = retry_quotas.begin(); + for (auto it = retry_quotas.begin(); it != retry_quotas.end(); ++it) { + if (it->second->IdleMs(now_ms) > victim->second->IdleMs(now_ms)) { + victim = it; + } + } + retry_quotas.erase(victim); + } + auto quota = make_shared_ptr(); + retry_quotas.emplace(proto_host_port, quota); + return quota; +} + unique_ptr HTTPUtil::Request(BaseRequest &request) { unique_ptr client; return Request(request, client); @@ -368,28 +403,61 @@ void HTTPUtil::DecomposeURL(const string &input, string &path_out, string &proto } } -// Retry eligible requests using an operation-local budget and the existing transport retry hook. +// Handlers turn error statuses into exceptions; rebuild the response they saw so it can be judged like a received one. +static unique_ptr ResponseFromError(const ErrorData &error) { + auto &extra_info = error.ExtraInfo(); + auto status_entry = extra_info.find("status_code"); + int32_t status_code = 0; + if (status_entry == extra_info.end() || + !TryCast::Operation(string_t(status_entry->second), status_code)) { + return nullptr; + } + auto response = make_uniq(HTTPUtil::ToStatusCode(status_code)); + for (auto &entry : extra_info) { + if (entry.first == "response_body") { + response->body = entry.second; + } else if (entry.first == "reason") { + response->reason = entry.second; + } else if (StringUtil::StartsWith(entry.first, "header_")) { + response->headers.Insert(entry.first.substr(7), entry.second); + } + } + return response; +} + +// Retry eligible requests using an operation-local budget, the host's retry quota and the transport retry hook. +// This loop is not the operation's coordinator: it judges statuses (with the provider's ShouldRetry), returns Finish() +// once it stops and records what the status says. A coordinator-owned budget shared by a provider loop +// (request.retry_budget) ignores that record and settles the quota from the provider's validated outcome; the local +// status-owned budget settles it from the status. duckdb::unique_ptr -HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, - const BaseRequest &request, const std::function &retry_cb) { +HTTPUtil::RunRequestWithRetry(const std::function(void)> &on_request, BaseRequest &request, + const std::function &retry_cb) { auto ¶ms = request.params; HTTPRetryBudget local_retry_budget(params); auto retry_budget = request.retry_budget; if (!retry_budget) { retry_budget = local_retry_budget; } + // Charge this request's host, even when a shared budget was bound to another host before. + retry_budget->BindQuota(params.http_util.GetRetryQuota(request.proto_host_port)); unique_ptr response; std::exception_ptr caught_e; + ErrorData caught_error; string exception_error; bool should_retry = false; retry_budget->Run( [&]() { response.reset(); caught_e = nullptr; + caught_error = ErrorData(); exception_error.clear(); - string caught_status; - string caught_retry_after; + unique_ptr caught_response; + // Attempts are numbered per operation, so a re-send after a correction counts as the attempt it is; the + // bound grows with such re-sends, which do not consume retries. + const auto attempt = retry_budget->BeginAttempt(); + params.http_util.PrepareAttempt(request, attempt, retry_budget->MaxAttempts()); try { response = on_request(); if (response) { @@ -398,66 +466,97 @@ HTTPUtil::RunRequestWithRetry(const std::function(void) } catch (IOException &e) { exception_error = e.what(); caught_e = std::current_exception(); + caught_error = ErrorData(e); } catch (HTTPException &e) { exception_error = e.what(); caught_e = std::current_exception(); - // Handlers turn error statuses into exceptions; recover the throttle information. - ErrorData error_data(e); - auto entry = error_data.ExtraInfo().find("status_code"); - if (entry != error_data.ExtraInfo().end()) { - caught_status = entry->second; - } - auto retry_entry = error_data.ExtraInfo().find("header_Retry-After"); - if (retry_entry != error_data.ExtraInfo().end()) { - caught_retry_after = retry_entry->second; - } + caught_error = ErrorData(e); + caught_response = ResponseFromError(caught_error); } - // Request errors and caught exceptions are eligible without a response status. - should_retry = !response || params.http_util.ShouldRetry(request, *response); + if (response) { + should_retry = params.http_util.ShouldRetry(request, *response); + } else if (caught_response) { + // A thrown status is judged like a received one, provider rules included. + should_retry = params.http_util.ShouldRetry(request, *caught_response); + } else { + // Without a status the transport failed and nothing is known about the server. + should_retry = true; + } if (!should_retry) { - auto response_code = static_cast(response->status); - response->success = (response_code >= 200 && response_code < 300) || - response->status == HTTPStatusCode::NotModified_304; + if (response) { + auto response_code = static_cast(response->status); + response->success = (response_code >= 200 && response_code < 300) || + response->status == HTTPStatusCode::NotModified_304; + } return HTTPRetryDecision::Finish(); } if (!HTTPUtil::IsIdempotent(request.type)) { return HTTPRetryDecision::Finish(); } - const bool throttled = (response && (response->status == HTTPStatusCode::TooManyRequests_429 || - response->status == HTTPStatusCode::ServiceUnavailable_503)) || - caught_status == "429" || caught_status == "503"; - if (throttled) { - if (response && response->headers.HasHeader("Retry-After")) { - caught_retry_after = response->headers.GetHeaderValue("Retry-After"); - } - return HTTPRetryDecision::Throttled(caught_retry_after); + if (!response && !caught_response) { + // The transport failed before any status arrived: nothing is known about the server. + return HTTPRetryDecision::Transient(); + } + auto &judged = response ? *response : *caught_response; + if (judged.HasRequestError()) { + return HTTPRetryDecision::Transient(); + } + if (judged.status == HTTPStatusCode::TooManyRequests_429 || + judged.status == HTTPStatusCode::ServiceUnavailable_503) { + return HTTPRetryDecision::Throttled( + judged.headers.HasHeader("Retry-After") ? judged.headers.GetHeaderValue("Retry-After") : string()); } return HTTPRetryDecision::Retry(); }, retry_cb); - if (!should_retry) { + // With a coordinator-owned budget the outcome is settled by the coordinator once it has validated the response: + // an error document behind HTTP 200 is a failure or a retry there and must not refund the retry it consumed. + if (!should_retry && response) { + retry_budget->RecordStatusOutcome(response->success); return response; } + // The request failed: it threw, could not be retried any further, or is not replay-safe. + retry_budget->RecordStatusOutcome(false); + string quota_note; + if (retry_budget->QuotaExhausted()) { + quota_note = StringUtil::Format(" (retry quota for '%s' exhausted: requests to this host keep failing)", + request.proto_host_port); + } if (request.try_request) { if (!response) { response = make_uniq(HTTPStatusCode::INVALID); - response->request_error = exception_error.empty() ? "Unknown error" : std::move(exception_error); + response->request_error = + (exception_error.empty() ? "Unknown error" : std::move(exception_error)) + quota_note; + } else if (!quota_note.empty()) { + // A received error keeps its status; the note goes to the field GetError() reports. + if (response->HasRequestError()) { + response->request_error += quota_note; + } else { + response->reason += quota_note; + } } response->success = false; return response; } auto method = EnumUtil::ToString(request.type); if (caught_e) { - std::rethrow_exception(caught_e); + if (quota_note.empty()) { + std::rethrow_exception(caught_e); + } + // Same type and extra info as the caught exception, with the quota note appended. + if (caught_error.Type() == ExceptionType::HTTP) { + throw HTTPException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); + } + throw IOException(caught_error.ExtraInfo(), caught_error.RawMessage() + quota_note); } if (response && !response->HasRequestError()) { - throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'", - static_cast(response->status), method, request.url); + throw HTTPException(*response, "Request returned HTTP %d for HTTP %s to '%s'%s", + static_cast(response->status), method, request.url, quota_note); } string error = response ? response->GetError() : "Unknown error"; - throw IOException("%s error for HTTP %s to '%s'", error, method, request.url); + throw IOException("%s error for HTTP %s to '%s'%s", error, method, request.url, quota_note); } void HTTPParams::Initialize(optional_ptr opener) { diff --git a/src/duckdb/src/main/parser_context.cpp b/src/duckdb/src/main/parser_context.cpp index 98183a219..ea064a47c 100644 --- a/src/duckdb/src/main/parser_context.cpp +++ b/src/duckdb/src/main/parser_context.cpp @@ -5,6 +5,7 @@ #include "duckdb/main/client_config.hpp" #include "duckdb/main/database.hpp" #include "duckdb/main/extension_callback_manager.hpp" +#include "duckdb/parser/grammar_extension.hpp" #include "duckdb/parser/peg/passthrough_dialect.hpp" namespace duckdb { @@ -39,15 +40,7 @@ shared_ptr CompiledGrammar::Get(ClientContext &context) { shared_ptr CompiledGrammar::Create(const ClientContext &context, const vector &active_extensions) { - vector> selected_extensions; - auto &callback_manager = ExtensionCallbackManager::Get(context); - for (auto &name : active_extensions) { - auto grammar_extension = callback_manager.FindGrammarExtension(name); - if (grammar_extension) { - selected_extensions.emplace_back(*grammar_extension); - } - } - return Create(selected_extensions); + return context.db->GetParserCache().GetMatcher(context, active_extensions); } ParserCache::ParserCache() { @@ -72,6 +65,39 @@ shared_ptr ParserCache::GetMatcher() { return matcher; } +shared_ptr ParserCache::GetMatcher(const ClientContext &context, + const vector &active_extensions) { + vector> selected_extensions; + vector extension_names; + auto &callback_manager = ExtensionCallbackManager::Get(context); + for (auto &name : active_extensions) { + auto grammar_extension = callback_manager.FindGrammarExtension(name); + if (grammar_extension) { + selected_extensions.emplace_back(*grammar_extension); + extension_names.push_back(grammar_extension->Name()); + } + } + // compile while holding the lock, so concurrent first uses of the same extensions only compile once + lock_guard lock(extension_mutex); + for (idx_t i = 0; i < extension_matchers.size(); i++) { + if (extension_matchers[i].first == extension_names) { + // move the entry to the back, which holds the most recently used grammar + auto entry = std::move(extension_matchers[i]); + extension_matchers.erase(extension_matchers.begin() + NumericCast(i)); + extension_matchers.push_back(std::move(entry)); + return extension_matchers.back().second; + } + } + // compile before evicting, so a grammar that fails to compile does not evict a valid one + auto new_matcher = CompiledGrammar::Create(selected_extensions); + if (extension_matchers.size() >= MAX_CACHED_EXTENSION_MATCHERS) { + // connections that still use the evicted grammar keep their own reference to it + extension_matchers.erase(extension_matchers.begin()); + } + extension_matchers.emplace_back(std::move(extension_names), std::move(new_matcher)); + return extension_matchers.back().second; +} + shared_ptr ParserCache::GetPassthroughMatcher(const ClientContext &context) { lock_guard lock(passthrough_mutex); if (!passthrough_dialect) { diff --git a/src/duckdb/src/main/secret/secret_manager.cpp b/src/duckdb/src/main/secret/secret_manager.cpp index 5eb49e3b8..39ff78bd9 100644 --- a/src/duckdb/src/main/secret/secret_manager.cpp +++ b/src/duckdb/src/main/secret/secret_manager.cpp @@ -542,8 +542,8 @@ vector SecretManager::AllSecrets(CatalogTransaction transaction) { } // Add results from all backends to the result set - for (const auto &backend : secret_storages) { - auto backend_result = backend.second->AllSecrets(&transaction); + for (const auto &backend : GetSecretStorages()) { + auto backend_result = backend.get().AllSecrets(&transaction); for (const auto &it : backend_result) { result.push_back(it); } diff --git a/src/duckdb/src/main/settings/custom_settings.cpp b/src/duckdb/src/main/settings/custom_settings.cpp index cf9449843..3b63cefb3 100644 --- a/src/duckdb/src/main/settings/custom_settings.cpp +++ b/src/duckdb/src/main/settings/custom_settings.cpp @@ -619,17 +619,17 @@ void DisabledOptimizersSetting::SetGlobal(DatabaseInstance *db, DBConfig &config } disabled_optimizers.insert(OptimizerTypeFromString(param)); } - config.options.disabled_optimizers = std::move(disabled_optimizers); + config.SetDisabledOptimizers(std::move(disabled_optimizers)); } void DisabledOptimizersSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) { - config.options.disabled_optimizers = DBConfigOptions().disabled_optimizers; + config.SetDisabledOptimizers(DBConfigOptions().disabled_optimizers); } Value DisabledOptimizersSetting::GetSetting(const ClientContext &context) { auto &config = DBConfig::GetConfig(context); string result; - for (auto &optimizer : config.options.disabled_optimizers) { + for (auto &optimizer : config.GetDisabledOptimizers()) { if (!result.empty()) { result += ","; } @@ -1417,6 +1417,10 @@ void ProfilingRendererSettingsSetting::SetLocal(ClientContext &context, const Va unordered_map new_settings; for (auto &entry : MapValue::GetChildren(input)) { auto &key_value = StructValue::GetChildren(entry); + if (key_value[1].IsNull()) { + throw InvalidInputException("profiling_renderer_settings: the value of \"%s\" cannot be NULL", + key_value[0].ToString()); + } new_settings[StringUtil::Lower(key_value[0].ToString())] = key_value[1]; } // eagerly configure the renderer of the current profiler format to validate the setting values @@ -1975,7 +1979,8 @@ void ActiveGrammarExtensionsSetting::SetLocal(ClientContext &context, const Valu return; } - auto compiled_grammar = CompiledGrammar::Create(context, selected_extensions); + auto compiled_grammar = + DatabaseInstance::GetDatabase(context).GetParserCache().GetMatcher(context, selected_extensions); client_config.active_grammar_extensions = std::move(selected_extensions); client_config.cached_grammar = std::move(compiled_grammar); } diff --git a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp index 19a1871ce..b8b82fe8f 100644 --- a/src/duckdb/src/optimizer/common_subplan_optimizer.cpp +++ b/src/duckdb/src/optimizer/common_subplan_optimizer.cpp @@ -19,6 +19,54 @@ namespace duckdb { //===--------------------------------------------------------------------===// // Subplan Signature/Info //===--------------------------------------------------------------------===// +static vector GetExpandedColumnBindings(LogicalOperator &op) { + switch (op.type) { + case LogicalOperatorType::LOGICAL_FILTER: + case LogicalOperatorType::LOGICAL_ORDER_BY: + case LogicalOperatorType::LOGICAL_LIMIT: + case LogicalOperatorType::LOGICAL_TOP_N: + case LogicalOperatorType::LOGICAL_DISTINCT: + return GetExpandedColumnBindings(*op.children[0]); + case LogicalOperatorType::LOGICAL_WINDOW: + case LogicalOperatorType::LOGICAL_UNNEST: { + auto bindings = GetExpandedColumnBindings(*op.children[0]); + const auto table_index = op.GetTableIndex()[0]; + for (auto col_idx : ProjectionIndex::GetIndexes(op.expressions.size())) { + bindings.emplace_back(table_index, col_idx); + } + return bindings; + } + case LogicalOperatorType::LOGICAL_ANY_JOIN: + case LogicalOperatorType::LOGICAL_ASOF_JOIN: + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { + auto &join = op.Cast(); + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { + return left_bindings; + } + if (join.join_type == JoinType::MARK) { + left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); + return left_bindings; + } + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { + return right_bindings; + } + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: + case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { + auto left_bindings = GetExpandedColumnBindings(*op.children[0]); + auto right_bindings = GetExpandedColumnBindings(*op.children[1]); + left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); + return left_bindings; + } + default: + return op.GetColumnBindings(); + } +} + enum class ConversionType { TO_CANONICAL, RESTORE_ORIGINAL, @@ -100,7 +148,7 @@ class PlanSignatureTableIndexMap { // Store temporary mapping for (const auto &child_op : op.children) { - for (const auto &child_cb : child_op->GetColumnBindings()) { + for (const auto &child_cb : GetExpandedColumnBindings(*child_op)) { const auto &original = child_cb.table_index; auto it = to_canonical_table_index.find(original); if (it != to_canonical_table_index.end()) { @@ -1108,42 +1156,6 @@ class CommonSubplanFinder { return a.get(); } - vector GetExpandedColumnBindings(LogicalOperator &op) { - switch (op.type) { - case LogicalOperatorType::LOGICAL_FILTER: - case LogicalOperatorType::LOGICAL_ORDER_BY: - return GetExpandedColumnBindings(*op.children[0]); - case LogicalOperatorType::LOGICAL_ANY_JOIN: - case LogicalOperatorType::LOGICAL_ASOF_JOIN: - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: { - auto &join = op.Cast(); - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - if (join.join_type == JoinType::SEMI || join.join_type == JoinType::ANTI) { - return left_bindings; - } - if (join.join_type == JoinType::MARK) { - left_bindings.emplace_back(join.mark_index, ProjectionIndex(0)); - return left_bindings; - } - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - if (join.join_type == JoinType::RIGHT_SEMI || join.join_type == JoinType::RIGHT_ANTI) { - return right_bindings; - } - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - case LogicalOperatorType::LOGICAL_CROSS_PRODUCT: - case LogicalOperatorType::LOGICAL_POSITIONAL_JOIN: { - auto left_bindings = GetExpandedColumnBindings(*op.children[0]); - auto right_bindings = GetExpandedColumnBindings(*op.children[1]); - left_bindings.insert(left_bindings.end(), right_bindings.begin(), right_bindings.end()); - return left_bindings; - } - default: - return op.GetColumnBindings(); - } - } - arena_vector GetCanonicalBindings(const vector &original_bindings) { const auto &table_index_map = state.table_index_map.GetMap(); arena_vector canonical_bindings(state.allocator); diff --git a/src/duckdb/src/optimizer/filter_combiner.cpp b/src/duckdb/src/optimizer/filter_combiner.cpp index 696bad84d..3477a0082 100644 --- a/src/duckdb/src/optimizer/filter_combiner.cpp +++ b/src/duckdb/src/optimizer/filter_combiner.cpp @@ -799,7 +799,19 @@ static bool IsLessThan(ExpressionType type) { //! Margins: 2h for DST ambiguity (TIMESTAMP<->TIMESTAMPTZ), 24h for time-of-day loss, 1s for rounding loss. static int64_t GetTemporalCastMargin(LogicalTypeId source, LogicalTypeId target) { if (source == target) { - return 0; + switch (source) { + case LogicalTypeId::DATE: + case LogicalTypeId::TIMESTAMP: + case LogicalTypeId::TIMESTAMP_TZ: + case LogicalTypeId::TIMESTAMP_NS: + case LogicalTypeId::TIMESTAMP_TZ_NS: + case LogicalTypeId::TIMESTAMP_MS: + case LogicalTypeId::TIMESTAMP_SEC: + return 0; + default: + // non-temporal types with the same id can differ (e.g. UNION or DECIMAL types) + return -1; + } } switch (source) { case LogicalTypeId::TIMESTAMP: @@ -1135,7 +1147,6 @@ FilterResult FilterCombiner::AddBoundComparisonFilter(Expression &expr) { return ret; } else { // comparison between two non-scalars - // only handle comparisons for now if (expr.GetExpressionType() != ExpressionType::COMPARE_EQUAL) { return FilterResult::UNSUPPORTED; } @@ -1290,47 +1301,18 @@ FilterResult FilterCombiner::AddFilter(Expression &expr) { return FilterResult::UNSUPPORTED; } -/* - * Create and add new transitive filters from a two non-scalar filter such as j > i, j >= i, j < i, and j <= i - * It's missing to create another method to add transitive filters from scalar filters, e.g, i > 10 - */ +// Infer constant bounds from comparisons between non-scalar expressions. FilterResult FilterCombiner::AddTransitiveFilters(BoundFunctionExpression &comparison, bool is_root) { if (!IsGreaterThan(comparison.GetExpressionType()) && !IsLessThan(comparison.GetExpressionType())) { return FilterResult::UNSUPPORTED; } + // Derived bounds must not duplicate volatile or throwing expressions. + if (comparison.IsVolatile() || comparison.CanThrow()) { + return FilterResult::UNSUPPORTED; + } // get the LHS and RHS nodes auto &left_node = GetNode(*BoundComparisonExpression::LeftMutable(comparison)); - reference right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); - // In case with filters like CAST(i) = j and i = 5 we replace the COLUMN_REF i with the constant 5 - do { - if (right_node.get().GetExpressionType() != ExpressionType::OPERATOR_CAST) { - break; - } - auto &bound_cast_expr = right_node.get().Cast(); - if (BoundCastExpression::Child(bound_cast_expr).GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - break; - } - auto &col_ref = BoundCastExpression::Child(bound_cast_expr).Cast(); - for (auto &stored_exp : stored_expressions) { - const_reference expr = stored_exp.first; - if (expr.get().GetExpressionType() == ExpressionType::OPERATOR_CAST) { - expr = BoundCastExpression::Child(right_node.get().Cast()); - } - if (expr.get().GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - continue; - } - auto &st_col_ref = expr.get().Cast(); - if (st_col_ref.Binding() != col_ref.Binding()) { - continue; - } - if (bound_cast_expr.GetReturnType() != stored_exp.second->GetReturnType()) { - continue; - } - BoundCastExpression::ChildMutable(bound_cast_expr) = stored_exp.second->Copy(); - right_node = GetNode(*BoundCastExpression::ChildMutable(bound_cast_expr)); - break; - } - } while (false); + auto &right_node = GetNode(*BoundComparisonExpression::RightMutable(comparison)); if (left_node.Equals(right_node)) { return FilterResult::UNSUPPORTED; diff --git a/src/duckdb/src/optimizer/filter_pushdown.cpp b/src/duckdb/src/optimizer/filter_pushdown.cpp index b9484e797..d262b32a9 100644 --- a/src/duckdb/src/optimizer/filter_pushdown.cpp +++ b/src/duckdb/src/optimizer/filter_pushdown.cpp @@ -172,62 +172,34 @@ unique_ptr FilterPushdown::PushdownJoin(unique_ptrtype == LogicalOperatorType::LOGICAL_DELIM_JOIN); auto &join = op->Cast(); - const auto restore_projection_maps = join.HasProjectionMap(); - auto left_projection_map = join.left_projection_map; - auto right_projection_map = join.right_projection_map; - unordered_set left_bindings, right_bindings; LogicalJoin::GetTableReferences(*op->children[0], left_bindings); LogicalJoin::GetTableReferences(*op->children[1], right_bindings); - unique_ptr result; + // every branch keeps the join's projection maps on the operator it returns: the join itself when it survives, + // the re-created join in PushdownInnerJoin, or a filter carrying the map when a side replaces the join switch (join.join_type) { case JoinType::OUTER: - result = PushdownOuterJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownOuterJoin(std::move(op), left_bindings, right_bindings); case JoinType::INNER: // AsOf joins can't push anything into the RHS, so treat it as a left join if (op->type == LogicalOperatorType::LOGICAL_ASOF_JOIN) { - result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); } - result = PushdownInnerJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownInnerJoin(std::move(op), left_bindings, right_bindings); case JoinType::LEFT: - result = PushdownLeftJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownLeftJoin(std::move(op), left_bindings, right_bindings); case JoinType::MARK: - result = PushdownMarkJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownMarkJoin(std::move(op), left_bindings, right_bindings); case JoinType::SINGLE: - result = PushdownSingleJoin(std::move(op), left_bindings, right_bindings); - break; + return PushdownSingleJoin(std::move(op), left_bindings, right_bindings); case JoinType::SEMI: case JoinType::ANTI: - result = PushdownSemiAntiJoin(std::move(op)); - break; + return PushdownSemiAntiJoin(std::move(op)); default: // unsupported join type: stop pushing down return FinishPushdown(std::move(op)); } - - if (restore_projection_maps) { - switch (result->type) { - case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: - case LogicalOperatorType::LOGICAL_ASOF_JOIN: - case LogicalOperatorType::LOGICAL_ANY_JOIN: - case LogicalOperatorType::LOGICAL_DELIM_JOIN: { - // Pushing down filter through join didn't change operator type (e.g., LogicalEmptyResult), restore maps - auto &result_join = result->Cast(); - result_join.left_projection_map = std::move(left_projection_map); - result_join.right_projection_map = std::move(right_projection_map); - break; - } - default: - break; - } - } - return result; } FilterResult FilterPushdown::PushFilters() { for (auto &f : filters) { diff --git a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp index 2549eac84..51f2247d7 100644 --- a/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp +++ b/src/duckdb/src/optimizer/grouping_sets_optimizer.cpp @@ -50,6 +50,9 @@ struct GroupingSetLevel { } // namespace +//! The maximum number of grouping sets that are rewritten into separate aggregates +static constexpr idx_t MAX_REWRITE_GROUPING_SETS = 1024; + static bool CanRewriteAggregate(const BoundAggregateExpression &aggregate) { if (aggregate.IsDistinct() || aggregate.GetOrderBys()) { // DISTINCT / ORDER BY aggregates cannot be computed by combining states of a finer aggregation @@ -249,6 +252,10 @@ bool GroupingSetsOptimizer::TryRewriteGroupingSets(unique_ptr & can_cascade = false; } } + if (aggr.grouping_sets.size() > MAX_REWRITE_GROUPING_SETS) { + // the rewritten plan nests a materialized CTE per grouping set - leave large grouping sets to the aggregate + return false; + } vector levels; if (!can_cascade || !FindCascade(aggr.grouping_sets, levels)) { // Explicit branches support arbitrary aggregates and expose single grouping sets to subsequent rewrites. diff --git a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp index 98a8f2b33..1ed6428e9 100644 --- a/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp +++ b/src/duckdb/src/optimizer/join_order/join_order_optimizer.cpp @@ -8,7 +8,6 @@ #include "duckdb/optimizer/join_order/plan_enumerator.hpp" #include "duckdb/planner/expression/list.hpp" #include "duckdb/planner/operator/list.hpp" -#include "duckdb/main/settings.hpp" namespace duckdb { @@ -27,14 +26,12 @@ static optional CombineReorderableStats(const vector source; - for (auto &stats : relation_stats) { - source = stats.GetColumnStats(binding); - if (source) { - break; - } - } + auto source = lookup.Find(binding); if (!source) { return {}; } @@ -59,8 +56,7 @@ JoinOrderOptimizer JoinOrderOptimizer::CreateChildOptimizer() { unique_ptr JoinOrderOptimizer::Optimize(unique_ptr plan, optional_ptr stats) { - auto max_expression_depth = Settings::Get(query_graph_manager.context); - if (depth > max_expression_depth) { + if (depth > MAX_RECURSION_DEPTH) { // Very deep plans will eventually consume quite some stack space // Returning the current plan is always a valid choice return plan; diff --git a/src/duckdb/src/optimizer/join_order/relation_manager.cpp b/src/duckdb/src/optimizer/join_order/relation_manager.cpp index f963cb088..8b6139cae 100644 --- a/src/duckdb/src/optimizer/join_order/relation_manager.cpp +++ b/src/duckdb/src/optimizer/join_order/relation_manager.cpp @@ -362,14 +362,12 @@ static optional CombineNonReorderableStats(LogicalOperator &op, c auto ordinal_set_output = op.type == LogicalOperatorType::LOGICAL_UNION || op.type == LogicalOperatorType::LOGICAL_INTERSECT || op.type == LogicalOperatorType::LOGICAL_EXCEPT; + RelationColumnStatsLookup lookup; + for (auto &stats : child_stats) { + lookup.AddRelation(stats); + } for (idx_t column_idx = 0; column_idx < bindings.size(); column_idx++) { - optional_ptr source; - for (auto &stats : child_stats) { - source = stats.GetColumnStats(bindings[column_idx]); - if (source) { - break; - } - } + auto source = lookup.Find(bindings[column_idx]); if (!source && ordinal_set_output && !child_stats.empty() && column_idx < child_stats[0].columns.size()) { source = child_stats[0].columns[column_idx]; } diff --git a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp index 00825612b..ff8524f89 100644 --- a/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp +++ b/src/duckdb/src/optimizer/multi_stage_aggregate_rewriter.cpp @@ -1,6 +1,8 @@ #include "duckdb/optimizer/multi_stage_aggregate_rewriter.hpp" #include "duckdb/common/string_util.hpp" +#include "duckdb/function/aggregate/distributive_function_utils.hpp" +#include "duckdb/function/function_binder.hpp" #include "duckdb/optimizer/aggregate_rewrite_helper.hpp" #include "duckdb/optimizer/aggregate_rewrite.hpp" #include "duckdb/optimizer/optimizer.hpp" @@ -9,6 +11,7 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression/bound_constant_expression.hpp" +#include "duckdb/planner/expression_binder.hpp" #include "duckdb/planner/expression_iterator.hpp" #include "duckdb/planner/operator/logical_aggregate.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" @@ -27,12 +30,15 @@ MultiStageAggregateRewriter::MultiStageAggregateRewriter( namespace { struct DistinctAggregateSet { - explicit DistinctAggregateSet(idx_t source_index) : source_index(source_index) { + DistinctAggregateSet(idx_t source_index, vector> keys_p) + : source_index(source_index), keys(std::move(keys_p)) { aggregate_indices.push_back(source_index); } idx_t source_index; vector aggregate_indices; + //! Actual deduplication keys, including collations unless the binder already applied them. + vector> keys; vector> order_expressions; }; @@ -62,6 +68,24 @@ static optional_idx FindExpression(const vector> &express return optional_idx(); } +//! Expression equality ignores the return type, but a collation lives in the return type and decides which +//! argument values are duplicates of each other. Two distinct aggregates can only share a deduplication branch +//! if their arguments agree on that as well. +static bool ArgumentsEqualIncludingCollation(const BoundAggregateExpression &lhs, const BoundAggregateExpression &rhs) { + auto &left = lhs.GetChildren(); + auto &right = rhs.GetChildren(); + if (left.size() != right.size()) { + return false; + } + for (idx_t i = 0; i < left.size(); i++) { + if (!Expression::Equals(*left[i], *right[i]) || + !left[i]->GetReturnType().EqualsIncludingCollation(right[i]->GetReturnType())) { + return false; + } + } + return true; +} + static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateExpression &aggregate) { if (!aggregate.GetOrderBys()) { return; @@ -78,33 +102,34 @@ static void AddOrderExpressions(DistinctAggregateSet &set, const BoundAggregateE static unique_ptr CreateFinalAggregate(const BoundAggregateExpression &source, const DistinctAggregateSet &set, - TableIndex input_table, idx_t input_column_offset, - idx_t order_column_offset, + TableIndex input_table, idx_t order_column_offset, + const vector &value_columns, optional_idx filter_column_offset = optional_idx()) { auto result = unique_ptr_cast(source.Copy()); result->GetAggregateTypeMutable() = AggregateType::NON_DISTINCT; result->GetChildrenMutable().clear(); for (idx_t child_idx = 0; child_idx < source.GetChildren().size(); child_idx++) { auto &child = source.GetChildren()[child_idx]; - result->GetChildrenMutable().push_back(make_uniq( - child->GetReturnType(), ColumnBinding(input_table, ProjectionIndex(input_column_offset + child_idx)))); + auto &value_column = value_columns[child_idx]; + result->GetChildrenMutable().push_back( + make_uniq(child->GetReturnType(), value_column)); } if (source.GetOrderBys()) { result->GetOrderBysMutable() = make_uniq(); for (auto &order : source.GetOrderBys()->orders) { auto order_idx = FindExpression(source.GetChildren(), *order.expression); - idx_t column_offset; + ColumnBinding order_binding; if (order_idx.IsValid()) { - column_offset = input_column_offset + order_idx.GetIndex(); + // the ORDER BY is one of the arguments - sort on the value of that argument + order_binding = value_columns[order_idx.GetIndex()]; } else { order_idx = FindExpression(set.order_expressions, *order.expression); D_ASSERT(order_idx.IsValid()); - column_offset = order_column_offset + order_idx.GetIndex(); + order_binding = ColumnBinding(input_table, ProjectionIndex(order_column_offset + order_idx.GetIndex())); } result->GetOrderBysMutable()->orders.emplace_back( order.type, order.null_order, - make_uniq(order.expression->GetReturnType(), - ColumnBinding(input_table, ProjectionIndex(column_offset)))); + make_uniq(order.expression->GetReturnType(), order_binding)); } } else { result->GetOrderBysMutable().reset(); @@ -153,8 +178,30 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate for (auto &group : aggr.groups) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*group, input_replacements)); } - for (auto &child : source_aggregate.GetChildren()) { - distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); + auto distinct_group_index = optimizer.binder.GenerateTableIndex(); + auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); + + // Group on comparison keys, but pass original values to the final aggregate. When these differ, + // FIRST carries a representative, just as for collated GROUP BY. + vector> distinct_aggregates; + vector value_columns; + value_columns.reserve(source_aggregate.GetChildren().size()); + FunctionBinder function_binder(optimizer.GetContext()); + for (idx_t child_idx = 0; child_idx < source_aggregate.GetChildren().size(); child_idx++) { + auto &child = source_aggregate.GetChildren()[child_idx]; + auto &key = set.keys[child_idx]; + auto value_binding = ColumnBinding(distinct_group_index, ProjectionIndex(distinct_groups.size())); + if (!Expression::Equals(*child, *key)) { + vector> first_children; + first_children.push_back(AggregateRewriteHelper::CopyAndRebind(*child, input_replacements)); + auto first_aggregate = + function_binder.BindAggregateFunction(FirstFunctionGetter::GetFunction(child->GetReturnType()), + std::move(first_children), nullptr, AggregateType::NON_DISTINCT); + value_binding = ColumnBinding(distinct_aggregate_index, ProjectionIndex(distinct_aggregates.size())); + distinct_aggregates.push_back(std::move(first_aggregate)); + } + value_columns.push_back(value_binding); + distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*key, input_replacements)); } for (auto &order_expr : set.order_expressions) { distinct_groups.push_back(AggregateRewriteHelper::CopyAndRebind(*order_expr, input_replacements)); @@ -167,10 +214,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate AggregateRewriteHelper::CopyAndRebind(*source_aggregate.GetFilter(), input_replacements)); } - auto distinct_group_index = optimizer.binder.GenerateTableIndex(); - auto distinct_aggregate_index = optimizer.binder.GenerateTableIndex(); auto distinct = - make_uniq(distinct_group_index, distinct_aggregate_index, vector>()); + make_uniq(distinct_group_index, distinct_aggregate_index, std::move(distinct_aggregates)); distinct->groups = std::move(distinct_groups); distinct->children.push_back(std::move(input)); @@ -186,8 +231,8 @@ static BranchResult CreateDistinctBranch(Optimizer &optimizer, LogicalAggregate const auto order_column_offset = group_count + source_aggregate.GetChildren().size(); for (auto aggregate_idx : set.aggregate_indices) { auto &aggregate = aggr.expressions[aggregate_idx]->Cast(); - final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, group_count, - order_column_offset, filter_column_offset)); + final_aggregates.push_back(CreateFinalAggregate(aggregate, set, distinct_group_index, order_column_offset, + value_columns, filter_column_offset)); } auto final_group_index = optimizer.binder.GenerateTableIndex(); @@ -688,10 +733,20 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { regular_aggregates.push_back(aggregate_idx); continue; } + // Resolve keys before sharing branches: COUNT may already consume keys from the binder, while a + // value-returning aggregate with the same argument still needs its collation applied. + vector> keys; + for (auto &child : aggregate.GetChildren()) { + auto key = child->Copy(); + if (aggregate.Function().GetCollationHandling() != FunctionCollationHandling::PUSH_DISTINCT_COLLATIONS) { + ExpressionBinder::PushCollation(optimizer.GetContext(), key, key->GetReturnType()); + } + keys.push_back(std::move(key)); + } bool found_match = false; for (auto &set : distinct_sets) { auto &other = aggr.expressions[set.source_index]->Cast(); - if (Expression::ListEquals(aggregate.GetChildren(), other.GetChildren()) && + if (ArgumentsEqualIncludingCollation(aggregate, other) && Expression::ListEquals(keys, set.keys) && Expression::Equals(aggregate.GetFilter(), other.GetFilter())) { set.aggregate_indices.push_back(aggregate_idx); AddOrderExpressions(set, aggregate); @@ -700,7 +755,7 @@ bool MultiStageAggregateRewriter::TryRewrite(unique_ptr &op) { } } if (!found_match) { - distinct_sets.emplace_back(aggregate_idx); + distinct_sets.emplace_back(aggregate_idx, std::move(keys)); AddOrderExpressions(distinct_sets.back(), aggregate); } } diff --git a/src/duckdb/src/optimizer/optimizer.cpp b/src/duckdb/src/optimizer/optimizer.cpp index a068c5fba..e9ef3cb82 100644 --- a/src/duckdb/src/optimizer/optimizer.cpp +++ b/src/duckdb/src/optimizer/optimizer.cpp @@ -130,13 +130,11 @@ bool Optimizer::OptimizerDisabled(ClientContext &context_p, OptimizerType type) return true; } auto &config = DBConfig::GetConfig(context_p); - return config.options.disabled_optimizers.find(type) != config.options.disabled_optimizers.end(); + return config.IsOptimizerDisabled(type); } void Optimizer::RunOptimizer(OptimizerType type, const std::function &callback) { - if (context.IsInterrupted()) { - throw InterruptException(); - } + context.InterruptCheck(); if (OptimizerDisabled(type)) { // optimizer is marked as disabled: skip diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp index b8a626121..84893ecf6 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_cross_product.cpp @@ -1,13 +1,60 @@ #include "duckdb/optimizer/filter_pushdown.hpp" #include "duckdb/optimizer/in_clause_rewriter.hpp" +#include "duckdb/planner/expression/bound_between_expression.hpp" +#include "duckdb/planner/expression/bound_columnref_expression.hpp" +#include "duckdb/planner/expression/bound_comparison_expression.hpp" #include "duckdb/planner/operator/logical_comparison_join.hpp" #include "duckdb/planner/operator/logical_cross_product.hpp" #include "duckdb/planner/operator/logical_filter.hpp" +#include "duckdb/planner/operator/logical_empty_result.hpp" namespace duckdb { using Filter = FilterPushdown::Filter; +static bool HasTransitiveJoinBounds(const vector> &filters) { + unordered_set bound_tables; + for (auto &filter : filters) { + if (filter->bindings.size() != 1) { + continue; + } + auto &expr = *filter->filter; + bool has_bound = false; + if (BoundComparisonExpression::IsComparison(expr)) { + auto &comparison = expr.Cast(); + has_bound = BoundComparisonExpression::Left(comparison).IsFoldable() || + BoundComparisonExpression::Right(comparison).IsFoldable(); + } else if (expr.GetExpressionType() == ExpressionType::COMPARE_BETWEEN) { + auto &between = expr.Cast(); + has_bound = BoundBetweenExpression::LowerBound(between).IsFoldable() || + BoundBetweenExpression::UpperBound(between).IsFoldable(); + } + if (has_bound) { + bound_tables.insert(*filter->bindings.begin()); + } + } + if (bound_tables.empty()) { + return false; + } + for (auto &filter : filters) { + if (filter->bindings.size() < 2 || !BoundComparisonExpression::IsComparison(*filter->filter)) { + continue; + } + auto &comparison = filter->filter->Cast(); + auto type = comparison.GetExpressionType(); + if (type != ExpressionType::COMPARE_GREATERTHAN && type != ExpressionType::COMPARE_GREATERTHANOREQUALTO && + type != ExpressionType::COMPARE_LESSTHAN && type != ExpressionType::COMPARE_LESSTHANOREQUALTO) { + continue; + } + auto &right = BoundComparisonExpression::Right(comparison); + if (right.GetExpressionType() == ExpressionType::BOUND_COLUMN_REF && + bound_tables.count(right.Cast().Binding().table_index)) { + return true; + } + } + return false; +} + unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr op) { D_ASSERT(op->children.size() > 1); FilterPushdown left_pushdown(optimizer, convert_mark_joins, projection_mode); @@ -23,6 +70,13 @@ unique_ptr FilterPushdown::PushdownCrossProduct(unique_ptr left_bindings, right_bindings; if (!filters.empty()) { + // Recombine useful join bounds before distributing filters to the inputs. + if (HasTransitiveJoinBounds(filters)) { + if (PushFilters() == FilterResult::UNSATISFIABLE) { + return make_uniq(std::move(op)); + } + GenerateFilters(); + } // check to see into which side we should push the filters // first get the LHS and RHS bindings LogicalJoin::GetTableReferences(*op->children[0], left_bindings); diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp index f0acab67e..045497783 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_inner_join.cpp @@ -49,7 +49,21 @@ unique_ptr FilterPushdown::PushdownInnerJoin(unique_ptrtype) { + case LogicalOperatorType::LOGICAL_COMPARISON_JOIN: + case LogicalOperatorType::LOGICAL_ANY_JOIN: { + auto &result_join = result->Cast(); + result_join.left_projection_map = std::move(join.left_projection_map); + result_join.right_projection_map = std::move(join.right_projection_map); + break; + } + default: + break; + } + return result; } } // namespace duckdb diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp index 9d4ba02b7..21009431e 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_semi_anti_join.cpp @@ -39,8 +39,8 @@ unique_ptr FilterPushdown::PushdownSemiAntiJoin(unique_ptrchildren[0]); + // nothing to match against: every left row passes, keep the join's output layout + return LogicalJoin::PromoteJoinSide(std::move(op->children[0]), std::move(join.left_projection_map)); case JoinType::SEMI: return make_uniq(std::move(op)); default: diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp index 7426e56e5..fbc8738a7 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics.cpp @@ -42,6 +42,24 @@ bool RelationStats::MatchesBindings(const vector &bindings) const return true; } +RelationColumnStatsLookup::RelationColumnStatsLookup(const RelationStats &stats) { + AddRelation(stats); +} + +void RelationColumnStatsLookup::AddRelation(const RelationStats &stats) { + for (auto &column : stats.columns) { + columns.emplace(column.binding, column); + } +} + +optional_ptr RelationColumnStatsLookup::Find(ColumnBinding binding) const { + auto entry = columns.find(binding); + if (entry == columns.end()) { + return nullptr; + } + return entry->second.get(); +} + void RelationStats::Verify(const vector &bindings) const { D_ASSERT(!stats_initialized || MatchesBindings(bindings)); } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp index f32bab0e8..b4a983207 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_helper.cpp @@ -71,6 +71,7 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical result.stats_initialized = true; auto bindings = projection.GetColumnBindings(); D_ASSERT(bindings.size() == projection.expressions.size()); + RelationColumnStatsLookup child_columns(child_stats); for (idx_t expression_idx = 0; expression_idx < projection.expressions.size(); expression_idx++) { auto &expression = *projection.expressions[expression_idx]; auto expression_binding = GetChildColumnBinding(expression); @@ -78,7 +79,7 @@ optional RelationStatisticsHelper::ExtractProjectionStats(Logical if (expression_binding.expression_is_constant) { distinct_count = DistinctCount(MinValue(result.cardinality, 1), DistinctCountSource::EXACT); } else if (expression_binding.FoundColumnRef()) { - auto child_column = child_stats.GetColumnStats(expression_binding.child_binding); + auto child_column = child_columns.Find(expression_binding.child_binding); if (!child_column) { return {}; } @@ -108,6 +109,7 @@ idx_t RelationStatisticsHelper::EstimateDistinctCardinality(const vector RelationStatisticsHelper::ExtractAggregationStats(LogicalAggregate &aggregate, const RelationStats &child_stats) { + RelationColumnStatsLookup child_columns(child_stats); vector cardinality_counts; for (auto &grouping_set : aggregate.grouping_sets) { vector set_counts; @@ -116,7 +118,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica if (group.GetExpressionClass() != ExpressionClass::BOUND_COLUMN_REF) { continue; } - auto column = child_stats.GetColumnStats(group.Cast().Binding()); + auto column = child_columns.Find(group.Cast().Binding()); if (!column) { return {}; } @@ -139,7 +141,7 @@ optional RelationStatisticsHelper::ExtractAggregationStats(Logica auto &group = *aggregate.groups[group_idx]; DistinctCount distinct_count(result.cardinality, DistinctCountSource::CARDINALITY); if (group.GetExpressionClass() == ExpressionClass::BOUND_COLUMN_REF) { - auto child_column = child_stats.GetColumnStats(group.Cast().Binding()); + auto child_column = child_columns.Find(group.Cast().Binding()); if (!child_column) { return {}; } @@ -168,8 +170,9 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind result.cardinality = child_stats.cardinality; result.table_name = Identifier(window.GetName()); result.stats_initialized = true; + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : window.GetColumnBindings()) { - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == window.window_index) { @@ -183,10 +186,11 @@ optional RelationStatisticsHelper::ExtractWindowStats(LogicalWind return result; } -static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats) { +static optional GetDistinctTargetCount(Expression &target, const RelationStats &child_stats, + const RelationColumnStatsLookup &child_columns) { switch (target.GetExpressionClass()) { case ExpressionClass::BOUND_COLUMN_REF: { - auto column = child_stats.GetColumnStats(target.Cast().Binding()); + auto column = child_columns.Find(target.Cast().Binding()); return column ? optional(column->distinct_count) : optional(); } case ExpressionClass::BOUND_REF: { @@ -213,8 +217,9 @@ optional RelationStatisticsHelper::ExtractDistinctStats(LogicalDi distinct_counts.push_back(column.distinct_count); } } else { + RelationColumnStatsLookup child_columns(child_stats); for (auto &target : distinct.distinct_targets) { - auto count = GetDistinctTargetCount(*target, child_stats); + auto count = GetDistinctTargetCount(*target, child_stats, child_columns); if (!count) { return result; } @@ -250,14 +255,16 @@ optional RelationStatisticsHelper::ProjectOutputStats(const Relat result.filter_strength = stats.filter_strength; result.stats_initialized = true; result.table_name = stats.table_name; - for (auto &binding : op.GetColumnBindings()) { - auto column = stats.GetColumnStats(binding); + RelationColumnStatsLookup lookup(stats); + auto bindings = op.GetColumnBindings(); + for (auto &binding : bindings) { + auto column = lookup.Find(binding); if (!column) { return {}; } result.columns.push_back(*column); } - result.Verify(op.GetColumnBindings()); + result.Verify(bindings); return result; } diff --git a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp index f1ba72352..4ac81b70b 100644 --- a/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp +++ b/src/duckdb/src/optimizer/relation_statistics/relation_statistics_operator.cpp @@ -11,14 +11,12 @@ ProjectChildStats(LogicalOperator &op, const vector source; - for (auto &child : children) { - source = child.get().GetColumnStats(binding); - if (source) { - break; - } - } + auto source = child_columns.Find(binding); if (!source) { return {}; } @@ -47,11 +45,12 @@ static optional ExtractGetWithChildStats(LogicalGet &get, ClientC const RelationStats &child_stats) { auto result = RelationStatisticsHelper::ExtractGetStats(get, context); result.cardinality = child_stats.cardinality; + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : get.GetColumnBindings()) { if (binding.table_index == get.table_index) { continue; } - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (!child_column) { return {}; } @@ -67,8 +66,9 @@ static optional ExtractUnnestStats(LogicalOperator &op, const Rel result.cardinality = child_stats.cardinality; result.stats_initialized = true; result.table_name = Identifier(op.GetName()); + RelationColumnStatsLookup child_columns(child_stats); for (auto &binding : op.GetColumnBindings()) { - auto child_column = child_stats.GetColumnStats(binding); + auto child_column = child_columns.Find(binding); if (child_column) { result.columns.emplace_back(binding, child_column->distinct_count, child_column->name); } else if (binding.table_index == unnest.unnest_index) { @@ -97,8 +97,9 @@ static optional ExtractComparisonJoinStats(LogicalComparisonJoin mark_result.cardinality = cardinality; mark_result.stats_initialized = true; mark_result.table_name = Identifier(join.GetName()); + RelationColumnStatsLookup left_columns(child_stats[0].get()); for (auto &binding : join.GetColumnBindings()) { - auto column = child_stats[0].get().GetColumnStats(binding); + auto column = left_columns.Find(binding); if (column) { mark_result.columns.emplace_back(binding, column->distinct_count, column->name); } else if (binding.table_index == join.mark_index) { diff --git a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp index c0ab697ba..ef7363cf5 100644 --- a/src/duckdb/src/optimizer/remove_duplicate_groups.cpp +++ b/src/duckdb/src/optimizer/remove_duplicate_groups.cpp @@ -246,9 +246,9 @@ void RemoveDuplicateGroups::VisitAggregate(LogicalAggregate &aggr) { return; } - // This optimizer should run before statistics propagation, so this should be empty - // If it runs after, then group_stats should be updated too - D_ASSERT(aggr.group_stats.empty()); + // Earlier statistics (e.g. from the filter simplification) no longer match the groups - the statistics + // propagation that runs after this optimizer computes them again + aggr.group_stats.clear(); const idx_t num_original_groups = aggr.groups.size(); const idx_t num_aggregate_outputs = aggr.expressions.size(); diff --git a/src/duckdb/src/optimizer/remove_unused_columns.cpp b/src/duckdb/src/optimizer/remove_unused_columns.cpp index 8c9e597c5..35e31126c 100644 --- a/src/duckdb/src/optimizer/remove_unused_columns.cpp +++ b/src/duckdb/src/optimizer/remove_unused_columns.cpp @@ -230,10 +230,11 @@ idx_t BaseColumnPruner::ReplaceBinding(ColumnBinding current_binding, ColumnBind template void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex table_idx, bool replace) { D_ASSERT(mode == RemoveUnusedColumnsMode::APPLY); - idx_t offset = 0; idx_t new_col_idx = 0; + // entries that are referred to are moved to the front of the list, the rest is erased at the end + idx_t kept_count = 0; for (idx_t col_idx = 0; col_idx < list.size(); col_idx++) { - auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx + offset)); + auto current_binding = ColumnBinding(table_idx, ProjectionIndex(col_idx)); if (replace && !root.active_secure_views.empty()) { root.secure_view_replacements[current_binding].clear(); } @@ -243,16 +244,17 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab root.projection_map_replacements[current_binding].clear(); } // this entry is not referred to, erase it from the set of expressions - list.erase_at(col_idx); - offset++; - col_idx--; continue; } + if (kept_count != col_idx) { + list[kept_count] = std::move(list[col_idx]); + } + kept_count++; if (!replace) { continue; } bool should_replace = false; - if (col_idx + offset != new_col_idx) { + if (col_idx != new_col_idx) { should_replace = true; } if (!entry->second.child_columns.empty() && @@ -279,6 +281,7 @@ void RemoveUnusedColumns::ClearUnusedExpressions(vector &list, TableIndex tab new_col_idx++; } } + list.erase(list.begin() + NumericCast(kept_count), list.end()); } enum class ProjectionBindingResolution : uint8_t { SELECTED, REPLACEMENT }; @@ -1838,9 +1841,20 @@ static bool TryGetCastChild(unique_ptr &expr, optional_ptr *expression) { - //! Check if this is a struct extract wrapped in a cast + if ((*expression)->GetExpressionType() == ExpressionType::OPERATOR_TRY) { + const auto was_inside_try = inside_try; + inside_try = true; + LogicalOperatorVisitor::VisitExpression(expression); + inside_try = was_inside_try; + return; + } + + //! Check if this is a struct extract wrapped in a cast. TRY_CAST is already + //! skipped by TryGetCastChild. A throwing CAST under TRY must also stay put: + //! unused-columns would otherwise emit CAST(struct_extract) in a child + //! projection and leave TRY wrapping only the already-cast column (#25236). optional_ptr> cast_child; - if (TryGetCastChild(*expression, cast_child)) { + if (!inside_try && TryGetCastChild(*expression, cast_child)) { if (HandleExtractExpression(cast_child.get(), expression)) { // already handled return; diff --git a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp index ef00ba652..53de7616b 100644 --- a/src/duckdb/src/optimizer/rule/comparison_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/comparison_simplification.cpp @@ -121,6 +121,12 @@ static bool ConstantCastIsInvertible(ClientContext &context, BoundFunctionExpres auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); return roundtrip && *roundtrip == constant_value; } + if (target_type.id() == LogicalTypeId::DECIMAL && cast_expression.GetReturnType().id() == LogicalTypeId::DECIMAL) { + // a narrowing decimal cast of the constant is fine if the constant round-trips exactly + string error_message; + auto roundtrip = cast_constant.TryCastAs(context, cast_expression.GetReturnType(), &error_message, true); + return roundtrip && *roundtrip == constant_value; + } if (target_type.id() != LogicalTypeId::DATE || cast_expression.GetReturnType().id() != LogicalTypeId::TIMESTAMP) { return false; } diff --git a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp index 87f0b2fc3..787a721ba 100644 --- a/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp +++ b/src/duckdb/src/optimizer/rule/date_trunc_simplification.cpp @@ -13,6 +13,7 @@ #include "duckdb/optimizer/matcher/expression_matcher.hpp" #include "duckdb/optimizer/expression_rewriter.hpp" #include "duckdb/common/enums/date_part_specifier.hpp" +#include "duckdb/common/types/date.hpp" #include "duckdb/function/function.hpp" #include "duckdb/function/function_binder.hpp" @@ -37,6 +38,27 @@ DateTruncSimplificationRule::DateTruncSimplificationRule(ExpressionRewriter &rew root = std::move(op); } +static bool TruncatesYearsTowardsZero(const BoundConstantExpression &date_part) { + DatePartSpecifier part; + if (!TryGetDatePartSpecifier(StringValue::Get(date_part.GetValue()), part)) { + return true; + } + return part == DatePartSpecifier::DECADE || part == DatePartSpecifier::CENTURY || + part == DatePartSpecifier::MILLENNIUM; +} + +static bool IsAfterFirstYear(const Value &value) { + if (value.IsNull()) { + return true; + } + auto date_value = value.DefaultTryCastAs(LogicalType::DATE); + if (!date_value || date_value->IsNull()) { + return false; + } + // leave a margin for time zones + return Date::ExtractYear(date_value->GetValue()) > 1; +} + unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, vector> &bindings, bool &changes_made, bool is_root) { auto &expr = bindings[0].get().Cast(); @@ -73,6 +95,12 @@ unique_ptr DateTruncSimplificationRule::Apply(LogicalOperator &op, v return nullptr; } + // Truncating to decades, centuries or millennia does not round years before the common era towards the past + // (e.g. 150 BC truncates to 100 BC), so the rewrite below is only valid for constants after that + if (TruncatesYearsTowardsZero(date_part) && !IsAfterFirstYear(rhs.GetValue())) { + return nullptr; + } + // Check whether trunc(date_part, constant_rhs) = constant_rhs. const bool is_truncated = DateIsTruncated(date_part, rhs); diff --git a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp index 8d3eaa6b3..d937e40e9 100644 --- a/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp +++ b/src/duckdb/src/optimizer/statistics/operator/propagate_join.cpp @@ -13,21 +13,6 @@ namespace duckdb { -static unique_ptr PromoteJoinSide(unique_ptr child, - vector projection_map) { - if (projection_map.empty()) { - return child; - } - auto filter = make_uniq(); - filter->projection_map = std::move(projection_map); - filter->children.push_back(std::move(child)); - if (filter->children[0]->has_estimated_cardinality) { - filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); - } - filter->ResolveOperatorTypes(); - return std::move(filter); -} - bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: @@ -40,10 +25,10 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< // If the filter is always false or Null, the join output is exactly one of its sides: // promote that side together with its projection map so the output layout stays unchanged. case JoinType::RIGHT_ANTI: - node_ptr = PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[1]), std::move(join.right_projection_map)); return true; case JoinType::ANTI: - node_ptr = PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(join.children[0]), std::move(join.left_projection_map)); return true; case JoinType::LEFT: // anti/left outer join: replace right side with empty node @@ -61,13 +46,32 @@ bool StatisticsPropagator::HandleJoinNeverMatches(LogicalJoin &join, unique_ptr< } } +//! Appends the columns of a join side that the join emits to the projection map of its replacement +static void AddProjectedColumns(vector &result, const vector &projection_map, + idx_t column_count, idx_t offset) { + if (projection_map.empty()) { + for (idx_t column_idx = 0; column_idx < column_count; column_idx++) { + result.emplace_back(offset + column_idx); + } + return; + } + for (auto &column_idx : projection_map) { + result.emplace_back(offset + column_idx.GetIndex()); + } +} + bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr &node_ptr) { switch (join.join_type) { case JoinType::RIGHT_SEMI: case JoinType::SEMI: { + auto &emitted_projection_map = + join.join_type == JoinType::RIGHT_SEMI ? join.right_projection_map : join.left_projection_map; if (join.join_type == JoinType::RIGHT_SEMI) { std::swap(join.children[0], join.children[1]); } + // the cross product also emits the columns of the other side: project them out again + vector projection_map; + AddProjectedColumns(projection_map, emitted_projection_map, join.children[0]->GetColumnBindings().size(), 0); // when the right child has no data, return an empty set // cannot just return the left child because if the right child has no cardinality // then the whole result should be empty. @@ -78,14 +82,21 @@ bool StatisticsPropagator::HandleJoinAlwaysMatches(LogicalJoin &join, unique_ptr limit->AddChild(std::move(join.children[1])); auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(limit)); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = std::move(cross_product); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); return true; } case JoinType::INNER: { - // inner, replace with cross product + // inner, replace with cross product - keeping the columns that the join emits + vector projection_map; + if (!join.left_projection_map.empty() || !join.right_projection_map.empty()) { + auto left_count = join.children[0]->GetColumnBindings().size(); + AddProjectedColumns(projection_map, join.left_projection_map, left_count, 0); + AddProjectedColumns(projection_map, join.right_projection_map, join.children[1]->GetColumnBindings().size(), + left_count); + } auto cross_product = LogicalCrossProduct::Create(std::move(join.children[0]), std::move(join.children[1])); cross_product->SetEstimatedCardinality(join.estimated_cardinality); - node_ptr = std::move(cross_product); + node_ptr = LogicalJoin::PromoteJoinSide(std::move(cross_product), std::move(projection_map)); return true; } default: @@ -228,8 +239,12 @@ void StatisticsPropagator::MultiplyCardinalities(unique_ptr &sta return; } stats->estimated_cardinality = MaxValue(stats->estimated_cardinality, new_stats.estimated_cardinality); - auto new_max = Hugeint::Multiply(NumericCast(stats->max_cardinality), - NumericCast(new_stats.max_cardinality)); + hugeint_t new_max; + if (!Hugeint::TryMultiply(Hugeint::Convert(stats->max_cardinality), Hugeint::Convert(new_stats.max_cardinality), + new_max)) { + stats = nullptr; + return; + } if (new_max < NumericLimits::Maximum()) { int64_t result; if (!Hugeint::TryCast(new_max, result)) { diff --git a/src/duckdb/src/optimizer/topn_window_elimination.cpp b/src/duckdb/src/optimizer/topn_window_elimination.cpp index 7808bc49e..586787929 100644 --- a/src/duckdb/src/optimizer/topn_window_elimination.cpp +++ b/src/duckdb/src/optimizer/topn_window_elimination.cpp @@ -900,24 +900,10 @@ TopNWindowElimination::ExtractOptimizerParameters(const LogicalWindow &window, c params.partition_count = window_expr.Partitions().size(); params.order_type = window_expr.OrderBy()[0].type; - VisitExpression(&window_expr.OrderByMutable()[0].expression); - if (params.payload_type == TopNPayloadType::SINGLE_COLUMN && !aggregate_payload.empty()) { - VisitExpression(&aggregate_payload[0]); - } - for (const auto &column_ref : column_references) { - const auto &column_stats = stats->find(column_ref.first); - if (column_stats == stats->end() || column_stats->second->CanHaveNull()) { - params.can_be_null = true; - break; - } - } - // A compound ORDER BY expression (NULLIF, arithmetic, cast, ...) can produce NULLs even when its input - // columns are non-null. Only a direct column reference inherits the column's known nullability, so treat a - // non-column-reference as potentially null to keep the nulls-last aggregate variant. - if (window_expr.OrderBy()[0].expression->GetExpressionType() != ExpressionType::BOUND_COLUMN_REF) { - params.can_be_null = true; - } - column_references.clear(); + // The statistics of a column binding are those at the top of the plan: a filter above the window (e.g. on the + // result of a decorrelated LATERAL join) can have narrowed them to non-NULL although the input of the window + // contains NULLs. They cannot tell whether the window input can be NULL, so keep the NULL-preserving variants. + params.can_be_null = true; return params; } diff --git a/src/duckdb/src/parallel/pipeline_executor.cpp b/src/duckdb/src/parallel/pipeline_executor.cpp index 3f4cfbc3d..e61574823 100644 --- a/src/duckdb/src/parallel/pipeline_executor.cpp +++ b/src/duckdb/src/parallel/pipeline_executor.cpp @@ -62,7 +62,7 @@ PipelineExecutor::PipelineExecutor(ClientContext &context_p, Pipeline &pipeline_ auto ¤t_operator = pipeline.operators[i].get(); auto chunk = make_uniq(); - chunk->Initialize(BufferAllocator::Get(context.client), prev_operator.GetTypes()); + prev_operator.InitializeOutputChunk(BufferAllocator::Get(context.client), *chunk); intermediate_chunks.push_back(std::move(chunk)); auto op_state = current_operator.GetOperatorState(context); @@ -896,7 +896,7 @@ void PipelineExecutor::SampleProgress() { void PipelineExecutor::InitializeChunk(DataChunk &chunk) { auto &last_op = pipeline.operators.empty() ? *pipeline.source : pipeline.operators.back().get(); - chunk.Initialize(BufferAllocator::Get(context.client), last_op.GetTypes()); + last_op.InitializeOutputChunk(BufferAllocator::Get(context.client), chunk); } void PipelineExecutor::StartOperator(PhysicalOperator &op) { diff --git a/src/duckdb/src/parallel/scan_read_ahead.cpp b/src/duckdb/src/parallel/scan_read_ahead.cpp index 61bd67e25..a789974ba 100644 --- a/src/duckdb/src/parallel/scan_read_ahead.cpp +++ b/src/duckdb/src/parallel/scan_read_ahead.cpp @@ -46,12 +46,6 @@ bool ReadAheadJobCompletion::TryPark(TableFunctionInput &data_p) { return true; } -//! Throw when the query was interrupted, otherwise yield the thread -static void ScanReadAheadYield(ClientContext &context) { - context.InterruptCheck(); - TaskScheduler::YieldThread(); -} - void ReadAheadJobCompletion::WaitForIO() { shared_ptr task; while (pending_io_tasks.load() > 0) { @@ -70,8 +64,9 @@ void ReadAheadJobCompletion::WaitForIO() { class ReadAheadIOTask : public BaseExecutorTask { public: ReadAheadIOTask(TaskExecutor &executor, unique_ptr task_p, - shared_ptr completion_p) - : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)) { + shared_ptr completion_p, ScanReadAhead &read_ahead_p) + : BaseExecutorTask(executor), task(std::move(task_p)), completion(std::move(completion_p)), + read_ahead(read_ahead_p) { completion->AddIOTask(); } ~ReadAheadIOTask() override { @@ -80,13 +75,20 @@ class ReadAheadIOTask : public BaseExecutorTask { } void ExecuteTask() override { - // Does the actual IO - task->Execute(); + try { + // Does the actual IO + task->Execute(); + } catch (std::exception &ex) { + // recording the error through the read-ahead also wakes the threads waiting for progress + read_ahead.PushError(ErrorData(ex)); + } } private: unique_ptr task; shared_ptr completion; + //! Outlives the task, the read-ahead drains its executor before it is destroyed + ScanReadAhead &read_ahead; }; //! Async task that opens one file ahead of decoding and releases the pending-open count when destroyed @@ -199,13 +201,19 @@ unique_ptr ScanReadAhead::ClaimJob() { auto job = std::move(ready_queue.front()); ready_queue.pop_front(); ReleaseSlot(); + NotifyProgress(false); return job; } void ScanReadAhead::WaitForJob(ScanReadAheadJob &job) { job.SettleIO(); // the job's I/O has completed, release its budget charge - pending_io_bytes -= job.io_bytes; + const auto budget = backlog_budget.load(); + const auto previous = pending_io_bytes.fetch_sub(job.io_bytes); + if (previous >= budget && previous - job.io_bytes < budget) { + // the scan is no longer over budget, a waiting thread can produce + SignalProgress(false); + } job.io_bytes = 0; ThrowIfError(); } @@ -214,7 +222,9 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc const ProduceJobCallback &claim_and_schedule, unique_ptr &job) { D_ASSERT(!job); + optional yield_deadline; while (true) { + const auto epoch = ProgressEpoch(); // keep the queue full before claiming while (TryProduceJob(claim_and_schedule)) { } @@ -232,8 +242,18 @@ ScanReadAheadAcquire ScanReadAhead::AcquireJob(ClientContext &context, TableFunc } } if (!job) { - // another thread is between claiming an assignment and pushing its job, wait for it - ScanReadAheadYield(context); + // another thread is between claiming an assignment and pushing its job, a short claim is caught by + // yielding and a long one is waited for + const auto now = std::chrono::steady_clock::now(); + if (!yield_deadline) { + yield_deadline = now + std::chrono::microseconds(PRODUCER_YIELD_MICROS); + } + if (now < *yield_deadline) { + context.InterruptCheck(); + TaskScheduler::YieldThread(); + } else { + WaitForProgress(context, epoch); + } continue; } // park until the job's scheduled I/O completes, the last I/O task to finish wakes the scan task @@ -250,14 +270,22 @@ void ScanReadAhead::SetDone() { done = true; } +idx_t ScanReadAhead::ReservationDepth() const { + // over budget one job stays queued, claimed jobs hold the budget until their task resumes + return pending_io_bytes.load() >= backlog_budget.load() ? 1 : read_ahead_depth; +} + bool ScanReadAhead::TryReserveSlot() { // take the minimum job charge up front so concurrent producers cannot all pass the budget check together const bool over_budget = pending_io_bytes.fetch_add(MINIMUM_JOB_IO_CHARGE) >= backlog_budget.load(); - // over budget one job stays queued, claimed jobs hold the budget until their task resumes const idx_t depth = over_budget ? 1 : read_ahead_depth; if (active_jobs.fetch_add(1) >= depth) { --active_jobs; pending_io_bytes -= MINIMUM_JOB_IO_CHARGE; + if (active_jobs.load() < ReservationDepth()) { + // a concurrent attempt failed on this attempt's charge or slot, let it retry + SignalProgress(false); + } return false; } ++active_producers; @@ -272,7 +300,7 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorio_bytes += task->GetIOSize(); - read_tasks.push_back(make_uniq(*executor, std::move(task), completion)); + read_tasks.push_back(make_uniq(*executor, std::move(task), completion, *this)); } job->io_bytes = MaxValue(job->io_bytes, MINIMUM_JOB_IO_CHARGE); // the minimum charge was taken by TryReserveSlot, charge the remainder @@ -284,22 +312,27 @@ void ScanReadAhead::PushJob(unique_ptr job, vectorScheduleTasks(std::move(read_tasks)); lock_guard guard(lock); if (memory_governor) { - memory_governor->UpdateReservation(pending_io_bytes.load()); + // Reserve room for the next job before admission checks the budget. + memory_governor->UpdateReservation(pending_io_bytes.load() + MINIMUM_JOB_IO_CHARGE); backlog_budget = memory_governor->BackpressureBudget(); } // producers push concurrently, so admit jobs to the queue in batch-index order if (job->batch_index < next_batch_index || !pending_jobs.emplace(job->batch_index, std::move(job)).second) { throw InternalException("Read-ahead jobs must have unique batch indexes, assigned densely from 0"); } + idx_t admitted = 0; while (!pending_jobs.empty() && pending_jobs.begin()->first == next_batch_index) { ready_queue.push_back(std::move(pending_jobs.begin()->second)); pending_jobs.erase(pending_jobs.begin()); next_batch_index++; + admitted++; } + NotifyProgress(admitted > 1); } void ScanReadAhead::PushError(ErrorData error) { executor->PushError(std::move(error)); + SignalProgress(true); } void ScanReadAhead::ScheduleFileOpen(std::function open_fn, std::function cancel_fn) { @@ -331,4 +364,47 @@ void ScanReadAhead::ReleaseSlot() { active_jobs--; } +void ScanReadAhead::WakeWaiters(bool wake_all) { + if (wake_all) { + progress.notify_all(); + } else { + progress.notify_one(); + } +} + +void ScanReadAhead::NotifyProgress(bool wake_all) { + ++progress_epoch; + if (waiting_threads.load() > 0) { + WakeWaiters(wake_all); + } +} + +void ScanReadAhead::SignalProgress(bool wake_all) { + ++progress_epoch; + if (waiting_threads.load() == 0) { + return; + } + { + // a waiter that saw the old epoch is blocked in the wait once the lock is free + lock_guard guard(lock); + } + WakeWaiters(wake_all); +} + +void ScanReadAhead::WaitForProgress(ClientContext &context, idx_t epoch) { + context.BlockingInterruptCheck(); + { + unique_lock guard(lock); + ++waiting_threads; + // the timeout bounds the interrupt latency + progress.wait_for(guard, std::chrono::milliseconds(10), [&]() { return progress_epoch.load() != epoch; }); + --waiting_threads; + } + context.BlockingInterruptCheck(); +} + +idx_t ScanReadAhead::ProgressEpoch() const { + return progress_epoch.load(); +} + } // namespace duckdb diff --git a/src/duckdb/src/parser/peg/matcher_factory.cpp b/src/duckdb/src/parser/peg/matcher_factory.cpp index 77e1ca472..84f9b1d2d 100644 --- a/src/duckdb/src/parser/peg/matcher_factory.cpp +++ b/src/duckdb/src/parser/peg/matcher_factory.cpp @@ -220,6 +220,7 @@ Matcher &MatcherFactory::CreateRootMatcher(const string &root_rule) { // START GENERATED PACKRAT MEMOIZED RULES //===--------------------------------------------------------------------===// AddPackratMemoizedRule("Expression"); + AddPackratMemoizedRule("TableRef"); AddPackratMemoizedRule("Identifier"); AddPackratMemoizedRule("ColId"); AddPackratMemoizedRule("CatalogQualification"); diff --git a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp index c02196b46..9f8df5442 100644 --- a/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp +++ b/src/duckdb/src/parser/peg/tokenizer/base_tokenizer.cpp @@ -85,15 +85,6 @@ bool Tokenizer::CharacterIsInitialNumber(char c) { return c == '.'; } -static bool NumberHasDotOrExponent(const string &sql, idx_t start, idx_t end) { - for (idx_t i = start; i < end; i++) { - if (sql[i] == '.' || sql[i] == 'e' || sql[i] == 'E') { - return true; - } - } - return false; -} - bool Tokenizer::CharacterIsSpecialStringCharacter(char c) { if (c == 'N' || c == 'n') { return true; @@ -447,12 +438,13 @@ bool Tokenizer::TokenizeInputInternal(TokenizerBehavior &behavior) const { last_pos = i; break; case TokenizeState::NUMERIC: - // Check for "always allowed" numeric characters - a number has at most one dot, and none in its exponent - if (CharacterIsInitialNumber(c) && (c != '.' || !NumberHasDotOrExponent(sql, last_pos, i))) { + // Check for "always allowed" numeric characters + if (CharacterIsInitialNumber(c)) { break; // Continue tokenizing } - // Allow underscore only when immediately followed by a digit (no consecutive underscores) - if (c == '_' && i + 1 < sql.size() && CharacterIsInitialNumber(sql[i + 1])) { + // A digit separator must sit between two digits + if (c == '_' && StringUtil::CharacterIsDigit(sql[i - 1]) && i + 1 < sql.size() && + StringUtil::CharacterIsDigit(sql[i + 1])) { break; } diff --git a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp index cc0ac5f8f..abd92a91b 100644 --- a/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp +++ b/src/duckdb/src/parser/peg/transformer/peg_transformer_factory.cpp @@ -164,8 +164,6 @@ PEGTransformerFactory::PEGTransformerFactory(ParsedGrammar &grammar_p) : grammar grammar.SetExpressionDepth("Type", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("NestedType", ExpressionDepthKind::NESTING); grammar.SetExpressionDepth("LambdaArrowExpression", ExpressionDepthKind::CHAIN); - grammar.SetExpressionDepth("LogicalOrExpression", ExpressionDepthKind::CHAIN); - grammar.SetExpressionDepth("LogicalAndExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("NotExpression", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsExpressionContinuation", ExpressionDepthKind::CHAIN); grammar.SetExpressionDepth("IsDistinctFromExpression", ExpressionDepthKind::CHAIN); diff --git a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp index cbae0a00c..d71badeae 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp @@ -40,7 +40,9 @@ void SetCopyOptions(unique_ptr &info, vector &optio info->parsed_options[option.name] = std::move(row_func); } } else if (option.name == "HEADER" || option.name == "ESCAPE") { - if (option.children.empty()) { + if (option.expression) { + info->parsed_options[option.name] = std::move(option.expression); + } else if (option.children.empty()) { info->parsed_options[option.name] = nullptr; } else { info->parsed_options[option.name] = ConstantExpression::FromValue(option.children[0]); diff --git a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp index 991d7ade2..9c870fb7f 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_create_type.cpp @@ -1,5 +1,6 @@ #include "duckdb/common/vector/flat_vector.hpp" #include "duckdb/common/vector/string_vector.hpp" +#include "duckdb/parser/expression/type_expression.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" #include "duckdb/parser/parsed_data/create_type_info.hpp" @@ -35,7 +36,8 @@ PEGTransformerFactory::TransformEnumSelectType(PEGTransformer &transformer, unique_ptr PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transformer, - const optional> &string_literal) { + const optional> &string_literal, + const optional> &array_bounds) { auto result = make_uniq(); idx_t enum_count = string_literal ? string_literal->size() : 0; Vector enum_vector(LogicalType::VARCHAR, enum_count); @@ -46,6 +48,9 @@ PEGTransformerFactory::TransformEnumStringLiteralList(PEGTransformer &transforme } } result->type = LogicalType::ENUM(enum_vector, enum_count); + if (array_bounds && !array_bounds->empty()) { + result->type = TransformType(transformer, TypeExpression::FromLogicalType(result->type), array_bounds); + } return result; } diff --git a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp index 5fe0ec5c7..5ff313174 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_describe.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_describe.cpp @@ -213,6 +213,9 @@ DescribeTarget PEGTransformerFactory::TransformDescribeBaseTableName(PEGTransfor DescribeTarget PEGTransformerFactory::TransformDescribeStringLiteral(PEGTransformer &transformer, const string &string_literal) { + if (string_literal.empty()) { + throw ParserException("Expected a table name, but got an empty string"); + } DescribeTarget result; result.is_table_name = true; result.table_name = Identifier(string_literal); diff --git a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp index c0473c3f3..ecc4f4394 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_drop.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_drop.cpp @@ -48,13 +48,13 @@ bool PEGTransformerFactory::TransformFunctionTypeFunction(PEGTransformer &transf unique_ptr PEGTransformerFactory::TransformDropTableFunction(PEGTransformer &transformer, const CatalogType &comment_macro_table, const optional &if_exists, - const vector &table_function_name) { + const vector &table_function_name) { auto result = make_uniq(); auto info = make_uniq(); if (table_function_name.size() > 1) { throw NotImplementedException("Can only drop one object at a time"); } - info->SetQualifiedName(QualifiedName(table_function_name[0])); + info->SetQualifiedName(table_function_name[0]); info->type = comment_macro_table; info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; result->info = std::move(info); @@ -214,6 +214,9 @@ unique_ptr PEGTransformerFactory::TransformDropSecret(PEGTransfor info->if_not_found = if_exists ? OnEntryNotFound::RETURN_NULL : OnEntryNotFound::THROW_EXCEPTION; info->SetName(secret_name); if (drop_secret_storage) { + if (extra_drop_info->persist_mode == SecretPersistType::TEMPORARY) { + throw ParserException("Can not combine TEMPORARY with specifying a storage for drop secret"); + } extra_drop_info->secret_storage = drop_secret_storage->GetIdentifierName(); } info->extra_drop_info = std::move(extra_drop_info); diff --git a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp index 1a2a75c7b..65aff7f04 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_expression.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_expression.cpp @@ -239,6 +239,19 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( if (filter_clause) { filter_expr = std::move(*filter_clause); } + if (within_group_clause) { + if (distinct) { + throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); + } + if (!order_modifier->orders.empty()) { + throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); + } + order_modifier->orders = std::move(*within_group_clause); + if (order_modifier->orders.size() != 1) { + throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); + } + } + if (function_children.size() == 1 && ExpressionIsUnqualifiedStar(function_children[0].GetExpression()) && !distinct && order_modifier->orders.empty()) { // COUNT(*) gets converted into COUNT() @@ -289,20 +302,6 @@ unique_ptr PEGTransformerFactory::TransformFunctionExpression( throw ParserException("RESPECT/IGNORE NULLS is not supported for non-window functions"); } if (within_group_clause) { - auto order_by_clause = std::move(*within_group_clause); - if (distinct) { - throw ParserException("DISTINCT is not allowed in combination with WITHIN GROUP"); - } - if (!order_modifier->orders.empty()) { - throw ParserException("Cannot use multiple ORDER BY statements with WITHIN GROUP"); - } - if (!order_modifier) { - throw InternalException("ORDER modifier for WITHIN GROUP is not initialized"); - } - order_modifier->orders = std::move(order_by_clause); - if (order_modifier->orders.size() != 1) { - throw ParserException("Cannot use multiple ORDER BY clauses with WITHIN GROUP"); - } if (lowercase_name == "percentile_cont") { if (function_children.size() != 1) { throw ParserException("Wrong number of arguments for PERCENTILE_CONT"); @@ -603,11 +602,14 @@ static unique_ptr FoldConjunctionExpression(PEGTransformer &tr if (!tails) { return expr; } - auto depth_guard = transformer.StackCheck(tails->size()); + auto depth_guard = transformer.StackCheck(1); + vector> children; + children.reserve(tails->size() + 1); + children.push_back(std::move(expr)); for (auto &tail : *tails) { - expr = make_uniq(conjunction_type, std::move(expr), std::move(tail)); + children.push_back(std::move(tail)); } - return expr; + return make_uniq(conjunction_type, std::move(children)); } unique_ptr PEGTransformerFactory::TransformLogicalOrExpression( @@ -662,15 +664,10 @@ PEGTransformerFactory::TransformColDefAndExpressionTail(PEGTransformer &transfor return is_distinct_from_expression; } -unique_ptr -PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, optional> not_expression, - unique_ptr is_expression) { - auto expr = std::move(is_expression); - if (!not_expression) { - return expr; - } - auto not_depth_guard = transformer.StackCheck(not_expression->size()); - for (idx_t i = 0; i < not_expression->size(); i++) { +static unique_ptr ApplyNotKeywords(PEGTransformer &transformer, const vector ¬_keywords, + unique_ptr expr) { + auto not_depth_guard = transformer.StackCheck(not_keywords.size()); + for (idx_t i = 0; i < not_keywords.size(); i++) { vector> inner_list_children; inner_list_children.push_back(std::move(expr)); expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); @@ -678,6 +675,22 @@ PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer return expr; } +unique_ptr +PEGTransformerFactory::TransformLogicalNotExpression(PEGTransformer &transformer, + const optional> ¬_expression, + unique_ptr is_expression) { + if (!not_expression) { + return is_expression; + } + return ApplyNotKeywords(transformer, *not_expression, std::move(is_expression)); +} + +unique_ptr +PEGTransformerFactory::TransformNotComparisonOperand(PEGTransformer &transformer, const vector ¬_expression, + unique_ptr is_expression) { + return ApplyNotKeywords(transformer, not_expression, std::move(is_expression)); +} + vector PEGTransformerFactory::TransformNotExpression(PEGTransformer &transformer, const vector ¬_keyword) { return not_keyword; @@ -699,6 +712,13 @@ static unique_ptr ApplyIsTest(unique_ptr exp ExpressionClassToString(is_expr->GetExpressionClass())); } +static unique_ptr ApplyCastOperator(unique_ptr expr, + unique_ptr cast_operator) { + auto cast_expr = unique_ptr_cast(std::move(cast_operator)); + cast_expr->ChildMutable() = std::move(expr); + return std::move(cast_expr); +} + static unique_ptr ApplyIsDistinctFromTail(unique_ptr expr, IsDistinctFromTail tail) { return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); @@ -706,13 +726,7 @@ static unique_ptr ApplyIsDistinctFromTail(unique_ptr ApplyComparisonTail(unique_ptr expr, ComparisonExpressionTail tail) { - auto right_expr = std::move(tail.expression); - for (idx_t i = 0; i < tail.not_keywords.size(); i++) { - vector> inner_list_children; - inner_list_children.push_back(std::move(right_expr)); - right_expr = make_uniq(ExpressionType::OPERATOR_NOT, std::move(inner_list_children)); - } - return make_uniq(tail.comparison_type, std::move(expr), std::move(right_expr)); + return make_uniq(tail.comparison_type, std::move(expr), std::move(tail.expression)); } unique_ptr @@ -745,6 +759,9 @@ PEGTransformerFactory::TransformIsExpression(PEGTransformer &transformer, expr = TransformInfixOtherOperatorExpression(transformer, std::move(expr), std::move(other_tail)); break; } + case ExpressionTailType::CAST: + expr = ApplyCastOperator(std::move(expr), std::move(tail.cast)); + break; } previous_type = tail.type; } @@ -796,6 +813,14 @@ IsExpressionTail PEGTransformerFactory::TransformIsOtherOperatorTail(PEGTransfor return result; } +IsExpressionTail PEGTransformerFactory::TransformIsCastTail(PEGTransformer &transformer, + unique_ptr cast_operator) { + IsExpressionTail result; + result.type = ExpressionTailType::CAST; + result.cast = std::move(cast_operator); + return result; +} + unique_ptr PEGTransformerFactory::TransformIsLiteral(PEGTransformer &transformer, const bool &has_result, const Value &is_literal_value) { @@ -869,15 +894,13 @@ PEGTransformerFactory::TransformIsDistinctFromTail(PEGTransformer &transformer, return {is_distinct_from_op, std::move(comparison_expression)}; } -ComparisonExpressionTail PEGTransformerFactory::TransformComparisonExpressionTail( - PEGTransformer &transformer, const ExpressionType &comparison_operator, optional> not_expression, - unique_ptr between_in_like_expression) { +ComparisonExpressionTail +PEGTransformerFactory::TransformComparisonExpressionTail(PEGTransformer &transformer, + const ExpressionType &comparison_operator, + unique_ptr comparison_operand) { ComparisonExpressionTail result; result.comparison_type = comparison_operator; - if (not_expression) { - result.not_keywords = std::move(*not_expression); - } - result.expression = std::move(between_in_like_expression); + result.expression = std::move(comparison_operand); return result; } @@ -1122,7 +1145,12 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (!in_predicate && !between_like_op) { return expr; } - auto predicate_count = in_predicate ? in_predicate->size() : 0; + idx_t predicate_count = 0; + if (in_predicate) { + for (auto &predicate : *in_predicate) { + predicate_count += 1 + predicate.casts.size(); + } + } if (between_like_op) { predicate_count++; } @@ -1130,6 +1158,9 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres if (in_predicate) { for (auto &predicate : *in_predicate) { expr = ApplyBetweenInLikeOperator(std::move(expr), predicate); + for (auto &cast : predicate.casts) { + expr = ApplyCastOperator(std::move(expr), std::move(cast)); + } } } if (between_like_op) { @@ -1138,9 +1169,15 @@ unique_ptr PEGTransformerFactory::TransformBetweenInLikeExpres return expr; } -BetweenInLikeOperator PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, - unique_ptr in_clause) { - return {has_result, std::move(in_clause)}; +BetweenInLikeOperator +PEGTransformerFactory::TransformInPredicate(PEGTransformer &transformer, const bool &has_result, + unique_ptr in_clause, + optional>> cast_operator) { + BetweenInLikeOperator result {has_result, std::move(in_clause)}; + if (cast_operator) { + result.casts = std::move(*cast_operator); + } + return result; } BetweenInLikeOperator diff --git a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp index 84e0c6aa4..0b3eef8e5 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generated_trampoline.cpp @@ -1722,6 +1722,8 @@ static const TransformFrameOps IS_COMPARISON_TAIL_OPS = {"IsComparisonTail", static const TransformFrameOps IS_OTHER_OPERATOR_TAIL_OPS = { "IsOtherOperatorTail", &PEGTransformerFactory::InitializeIsOtherOperatorTailTrampoline, &PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline}; +static const TransformFrameOps IS_CAST_TAIL_OPS = {"IsCastTail", &PEGTransformerFactory::InitializeIsCastTailTrampoline, + &PEGTransformerFactory::FinalizeIsCastTailTrampoline}; static const TransformFrameOps IS_TEST_OPS = {"IsTest", &PEGTransformerFactory::InitializeIsTestTrampoline, &PEGTransformerFactory::FinalizeIsTestTrampoline}; static const TransformFrameOps IS_LITERAL_OPS = {"IsLiteral", &PEGTransformerFactory::InitializeIsLiteralTrampoline, @@ -1760,6 +1762,12 @@ static const TransformFrameOps COMPARISON_EXPRESSION_OPS = { static const TransformFrameOps COMPARISON_EXPRESSION_TAIL_OPS = { "ComparisonExpressionTail", &PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline, &PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline}; +static const TransformFrameOps COMPARISON_OPERAND_OPS = {"ComparisonOperand", + &PEGTransformerFactory::InitializeComparisonOperandTrampoline, + &PEGTransformerFactory::FinalizeComparisonOperandTrampoline}; +static const TransformFrameOps NOT_COMPARISON_OPERAND_OPS = { + "NotComparisonOperand", &PEGTransformerFactory::InitializeNotComparisonOperandTrampoline, + &PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline}; static const TransformFrameOps COMPARISON_OPERATOR_OPS = { "ComparisonOperator", &PEGTransformerFactory::InitializeComparisonOperatorTrampoline, &PEGTransformerFactory::FinalizeComparisonOperatorTrampoline}; @@ -3679,6 +3687,7 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctTail", &IS_DISTINCT_TAIL_OPS}, {"IsComparisonTail", &IS_COMPARISON_TAIL_OPS}, {"IsOtherOperatorTail", &IS_OTHER_OPERATOR_TAIL_OPS}, + {"IsCastTail", &IS_CAST_TAIL_OPS}, {"IsTest", &IS_TEST_OPS}, {"IsLiteral", &IS_LITERAL_OPS}, {"IsLiteralValue", &IS_LITERAL_VALUE_OPS}, @@ -3693,6 +3702,8 @@ const case_insensitive_map_t &PEGTransformerFactory:: {"IsDistinctFromOp", &IS_DISTINCT_FROM_OP_OPS}, {"ComparisonExpression", &COMPARISON_EXPRESSION_OPS}, {"ComparisonExpressionTail", &COMPARISON_EXPRESSION_TAIL_OPS}, + {"ComparisonOperand", &COMPARISON_OPERAND_OPS}, + {"NotComparisonOperand", &NOT_COMPARISON_OPERAND_OPS}, {"ComparisonOperator", &COMPARISON_OPERATOR_OPS}, {"OperatorEqual", &OPERATOR_EQUAL_OPS}, {"OperatorNotEqual", &OPERATOR_NOT_EQUAL_OPS}, @@ -11574,13 +11585,34 @@ PEGTransformerFactory::FinalizeEnumSelectTypeTrampoline(PEGTransformer &transfor void PEGTransformerFactory::InitializeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - process.ReserveChildSlots(0); + auto &list_pr = process.parse_result.Cast(); + auto &repeat_opt = list_pr.GetChild(2).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(1 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("ArrayBounds"), repeat_children[child_idx].get()}, 0 + child_idx); + } + } else { + process.ReserveChildSlots(1 - 1); + } } arena_ptr PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } optional> string_literal {}; auto &string_literal_opt = ExtractResultFromParens(list_pr.GetChild(1)).Cast(); if (string_literal_opt.HasResult()) { @@ -11592,7 +11624,15 @@ PEGTransformerFactory::FinalizeEnumStringLiteralListTrampoline(PEGTransformer &t } string_literal = std::move(string_literal_value); } - auto result = TransformEnumStringLiteralList(transformer, string_literal); + optional> array_bounds {}; + if (dynamic_child_count > 0) { + vector array_bounds_value; + for (idx_t i = 0; i < 0 + dynamic_child_count; i++) { + array_bounds_value.push_back(process.TakeResult(i)); + } + array_bounds = std::move(array_bounds_value); + } + auto result = TransformEnumStringLiteralList(transformer, string_literal, array_bounds); return transformer.MakeResult>(std::move(result)); } @@ -12409,7 +12449,13 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableTrampoli void PEGTransformerFactory::InitializeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(2); + auto list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); + auto dynamic_child_count = list_items.size(); + process.ReserveChildSlots(3 + dynamic_child_count - 1); + for (idx_t i = list_items.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("QualifiedTableFunction"), list_items[child_idx].get()}, 2 + child_idx); + } auto &if_exists_opt = list_pr.GetChild(1).Cast(); if (if_exists_opt.HasResult()) { process.PushChild({transformer.GetRule("IfExists"), if_exists_opt.GetResult()}, 1); @@ -12421,17 +12467,18 @@ arena_ptr PEGTransformerFactory::FinalizeDropTableFunctionTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + auto dynamic_list_items = ExtractParseResultsFromList(list_pr.GetChild(2)); + auto dynamic_child_count = dynamic_list_items.size(); auto comment_macro_table = process.TakeResult(0); optional if_exists {}; if (process.child_results[1]) { if_exists = process.TakeResult(1); } - vector table_function_name; - auto table_function_name_items = ExtractParseResultsFromList(list_pr.GetChild(2)); - for (auto &table_function_name_item : table_function_name_items) { - table_function_name.push_back(table_function_name_item.get().Cast().identifier); + vector qualified_table_function; + for (idx_t i = 2; i < 2 + dynamic_child_count; i++) { + qualified_table_function.push_back(process.TakeResult(i)); } - auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, table_function_name); + auto result = TransformDropTableFunction(transformer, comment_macro_table, if_exists, qualified_table_function); return transformer.MakeResult>(std::move(result)); } @@ -15807,7 +15854,7 @@ PEGTransformerFactory::FinalizeLogicalNotExpressionTrampoline(PEGTransformer &tr not_expression = process.TakeResult>(0); } auto is_expression = process.TakeResult>(1); - auto result = TransformLogicalNotExpression(transformer, std::move(not_expression), std::move(is_expression)); + auto result = TransformLogicalNotExpression(transformer, not_expression, std::move(is_expression)); return transformer.MakeResult>(std::move(result)); } @@ -15836,7 +15883,7 @@ PEGTransformerFactory::FinalizeNotExpressionTrampoline(PEGTransformer &transform not_keyword.push_back(process.TakeResult(i)); } auto result = TransformNotExpression(transformer, not_keyword); - return transformer.MakeResult>(std::move(result)); + return transformer.MakeResult>(result); } void PEGTransformerFactory::InitializeNotKeywordTrampoline(PEGTransformer &transformer, @@ -15999,6 +16046,20 @@ PEGTransformerFactory::FinalizeIsOtherOperatorTailTrampoline(PEGTransformer &tra return transformer.MakeResult(std::move(result)); } +void PEGTransformerFactory::InitializeIsCastTailTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(1); + process.PushChild({transformer.GetRule("CastOperator"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeIsCastTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { + auto cast_operator = process.TakeResult>(0); + auto result = TransformIsCastTail(transformer, std::move(cast_operator)); + return transformer.MakeResult(std::move(result)); +} + void PEGTransformerFactory::InitializeIsTestTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -16268,12 +16329,8 @@ PEGTransformerFactory::FinalizeComparisonExpressionTrampoline(PEGTransformer &tr void PEGTransformerFactory::InitializeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); - process.PushChild({transformer.GetRule("BetweenInLikeExpression"), list_pr.GetChild(2)}, 2); - auto ¬_expression_opt = list_pr.GetChild(1).Cast(); - if (not_expression_opt.HasResult()) { - process.PushChild({transformer.GetRule("NotExpression"), not_expression_opt.GetResult()}, 1); - } + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("ComparisonOperand"), list_pr.GetChild(1)}, 1); process.PushChild({transformer.GetRule("ComparisonOperator"), list_pr.GetChild(0)}, 0); } @@ -16281,16 +16338,49 @@ arena_ptr PEGTransformerFactory::FinalizeComparisonExpressionTailTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto comparison_operator = process.TakeResult(0); - optional> not_expression {}; - if (process.child_results[1]) { - not_expression = process.TakeResult>(1); - } - auto between_in_like_expression = process.TakeResult>(2); - auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(not_expression), - std::move(between_in_like_expression)); + auto comparison_operand = process.TakeResult>(1); + auto result = TransformComparisonExpressionTail(transformer, comparison_operator, std::move(comparison_operand)); return transformer.MakeResult(std::move(result)); } +void PEGTransformerFactory::InitializeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + auto &choice_pr = list_pr.Child(0); + auto &choice_result = choice_pr.GetResult(); + process.ReserveChildSlots(1); + auto child_rule = choice_result.GetRule(); + auto has_transform_process = child_rule && child_rule->transform_process; + if (!has_transform_process) { + throw InternalException("No transform process registered for rule '%s'", choice_result.Name()); + } + process.PushChild({*child_rule, choice_result}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto result = process.TakeResult>(0); + return transformer.MakeResult>(std::move(result)); +} + +void PEGTransformerFactory::InitializeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto &list_pr = process.parse_result.Cast(); + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("IsExpression"), list_pr.GetChild(1)}, 1); + process.PushChild({transformer.GetRule("NotExpression"), list_pr.GetChild(0)}, 0); +} + +arena_ptr +PEGTransformerFactory::FinalizeNotComparisonOperandTrampoline(PEGTransformer &transformer, + GeneratedTransformProcess &process) { + auto not_expression = process.TakeResult>(0); + auto is_expression = process.TakeResult>(1); + auto result = TransformNotComparisonOperand(transformer, not_expression, std::move(is_expression)); + return transformer.MakeResult>(std::move(result)); +} + void PEGTransformerFactory::InitializeComparisonOperatorTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); @@ -16446,18 +16536,46 @@ PEGTransformerFactory::FinalizeBetweenInLikeExpressionTrampoline(PEGTransformer void PEGTransformerFactory::InitializeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); + auto &repeat_opt = list_pr.GetChild(2).Cast(); + idx_t dynamic_child_count = 0; + if (repeat_opt.HasResult()) { + auto &repeat_pr = repeat_opt.GetResult().Cast(); + auto repeat_children = repeat_pr.GetChildren(); + dynamic_child_count = repeat_children.size(); + process.ReserveChildSlots(2 + dynamic_child_count - 1); + for (idx_t i = repeat_children.size(); i > 0; i--) { + auto child_idx = i - 1; + process.PushChild({transformer.GetRule("CastOperator"), repeat_children[child_idx].get()}, 1 + child_idx); + } + } else { + process.ReserveChildSlots(2 - 1); + } process.PushChild({transformer.GetRule("InClause"), list_pr.GetChild(1)}, 0); } arena_ptr PEGTransformerFactory::FinalizeInPredicateTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); + idx_t dynamic_child_count = 0; + auto &dynamic_repeat_opt = list_pr.GetChild(2).Cast(); + if (dynamic_repeat_opt.HasResult()) { + auto &dynamic_repeat_pr = dynamic_repeat_opt.GetResult().Cast(); + auto dynamic_repeat_children = dynamic_repeat_pr.GetChildren(); + dynamic_child_count = dynamic_repeat_children.size(); + } bool has_result {}; auto &has_result_opt = list_pr.GetChild(0).Cast(); has_result = has_result_opt.HasResult(); auto in_clause = process.TakeResult>(0); - auto result = TransformInPredicate(transformer, has_result, std::move(in_clause)); + optional>> cast_operator {}; + if (dynamic_child_count > 0) { + vector> cast_operator_value; + for (idx_t i = 1; i < 1 + dynamic_child_count; i++) { + cast_operator_value.push_back(process.TakeResult>(i)); + } + cast_operator = std::move(cast_operator_value); + } + auto result = TransformInPredicate(transformer, has_result, std::move(in_clause), std::move(cast_operator)); return transformer.MakeResult(std::move(result)); } @@ -21777,7 +21895,11 @@ PEGTransformerFactory::FinalizeTableAliasColonTrampoline(PEGTransformer &transfo void PEGTransformerFactory::InitializeValuesRefTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(3); + process.ReserveChildSlots(4); + auto &sample_clause_opt = list_pr.GetChild(3).Cast(); + if (sample_clause_opt.HasResult()) { + process.PushChild({transformer.GetRule("SampleClause"), sample_clause_opt.GetResult()}, 3); + } auto &table_alias_opt = list_pr.GetChild(2).Cast(); if (table_alias_opt.HasResult()) { process.PushChild({transformer.GetRule("TableAlias"), table_alias_opt.GetResult()}, 2); @@ -21800,7 +21922,12 @@ arena_ptr PEGTransformerFactory::FinalizeValuesRefTrampoli if (process.child_results[2]) { table_alias = process.TakeResult(2); } - auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias); + optional> sample_clause {}; + if (process.child_results[3]) { + sample_clause = process.TakeResult>(3); + } + auto result = TransformValuesRef(transformer, table_alias_colon, std::move(values_clause), table_alias, + std::move(sample_clause)); return transformer.MakeResult>(std::move(result)); } @@ -22488,15 +22615,17 @@ PEGTransformerFactory::FinalizeFunctionArgumentTrampoline(PEGTransformer &transf void PEGTransformerFactory::InitializeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { auto &list_pr = process.parse_result.Cast(); - process.ReserveChildSlots(1); - process.PushChild({transformer.GetRule("NamedParameter"), list_pr.GetChild(0)}, 0); + process.ReserveChildSlots(2); + process.PushChild({transformer.GetRule("Expression"), list_pr.GetChild(2)}, 1); + process.PushChild({transformer.GetRule("TypeFuncName"), list_pr.GetChild(0)}, 0); } arena_ptr PEGTransformerFactory::FinalizeNamedFunctionArgumentTrampoline(PEGTransformer &transformer, GeneratedTransformProcess &process) { - auto named_parameter = process.TakeResult(0); - auto result = TransformNamedFunctionArgument(transformer, std::move(named_parameter)); + auto type_func_name = process.TakeResult(0); + auto expression = process.TakeResult>(1); + auto result = TransformNamedFunctionArgument(transformer, type_func_name, std::move(expression)); return transformer.MakeResult(std::move(result)); } diff --git a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp index 3bc06b644..74a0b8c0e 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_generic_copy_option.cpp @@ -44,7 +44,8 @@ static void SetGenericCopyOptionExpression(GenericCopyOption ©_option, uniqu ExpressionClassToString(child->GetExpressionClass())); } } - } else if (expression->GetExpressionType() == ExpressionType::FUNCTION) { + } else if (expression->GetExpressionType() == ExpressionType::FUNCTION || + expression->GetExpressionType() == ExpressionType::VALUE_PARAMETER) { copy_option.expression = std::move(expression); } else if (expression->GetExpressionType() == ExpressionType::STAR) { copy_option.children.push_back(Value("*")); diff --git a/src/duckdb/src/parser/peg/transformer/transform_select.cpp b/src/duckdb/src/parser/peg/transformer/transform_select.cpp index 49debbefe..31a17c55c 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_select.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_select.cpp @@ -317,13 +317,10 @@ PEGTransformerFactory::FinalizeSimpleSelectTrampoline(PEGTransformer &transforme } FunctionArgument PEGTransformerFactory::TransformNamedFunctionArgument(PEGTransformer &transformer, - MacroParameter named_parameter) { - if (named_parameter.type != LogicalType::UNKNOWN) { - throw ParserException("Named argument \"%s\" of a function call cannot have a type", - named_parameter.name.GetIdentifierName()); - } - named_parameter.expression->SetAlias(named_parameter.name); - return FunctionArgument(named_parameter.name, std::move(named_parameter.expression)); + const Identifier &type_func_name, + unique_ptr expression) { + expression->SetAlias(type_func_name); + return FunctionArgument(type_func_name, std::move(expression)); } FunctionArgument PEGTransformerFactory::TransformPositionalFunctionArgument(PEGTransformer &transformer, @@ -774,6 +771,9 @@ unique_ptr PEGTransformerFactory::TransformTableFunctionLateralOpt( PEGTransformer &transformer, const optional &lateral, const QualifiedName &qualified_table_function, vector table_function_arguments, const optional &with_ordinality, const optional &table_alias, optional> sample_clause) { + if (lateral.value_or(false) && sample_clause) { + throw ParserException("TABLESAMPLE cannot be applied to a LATERAL table function"); + } auto result = make_uniq(); result->with_ordinality = @@ -931,6 +931,7 @@ vector PEGTransformerFactory::GroupByExpressionUnfolding(GroupByExp for (auto &child_expr : group_by_expr.children) { auto child_sets = GroupByExpressionUnfolding(child_expr, map, result); result_sets.insert(result_sets.end(), child_sets.begin(), child_sets.end()); + CheckGroupingSetMax(result_sets.size()); } } else if (group_by_expr.type == GroupByExpressionInfoType::CUBE || group_by_expr.type == GroupByExpressionInfoType::ROLLUP) { @@ -1360,6 +1361,9 @@ unique_ptr PEGTransformerFactory::TransformTableSubquery(PEGTransforme if (table_alias_colon && table_alias) { throw ParserException("Table reference %s cannot have two aliases", subquery_reference->ToString()); } + if (lateral.value_or(false) && sample_clause) { + throw ParserException("TABLESAMPLE cannot be applied to a LATERAL subquery"); + } if (table_alias_colon) { subquery_reference->alias = *table_alias_colon; } @@ -1406,7 +1410,8 @@ Identifier PEGTransformerFactory::TransformTableAliasColon(PEGTransformer &trans unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &transformer, const optional &table_alias_colon, unique_ptr values_clause, - const optional &table_alias) { + const optional &table_alias, + optional> sample_clause) { if (table_alias_colon && table_alias) { throw ParserException("Table reference cannot have two aliases"); } @@ -1418,6 +1423,9 @@ unique_ptr PEGTransformerFactory::TransformValuesRef(PEGTransformer &t subquery_ref->alias = table_alias->name; subquery_ref->column_name_alias = table_alias->column_name_alias; } + if (sample_clause) { + subquery_ref->sample = std::move(*sample_clause); + } return std::move(subquery_ref); } diff --git a/src/duckdb/src/planner/bind_context.cpp b/src/duckdb/src/planner/bind_context.cpp index a2d68d4d2..bf8423944 100644 --- a/src/duckdb/src/planner/bind_context.cpp +++ b/src/duckdb/src/planner/bind_context.cpp @@ -557,7 +557,7 @@ string CandidateColumnMessage(const vector &candidate_columns, const void BindContext::GenerateAllColumnExpressions(StarExpression &expr, vector> &new_select_list) { - if (bindings_list.empty()) { + if (bindings_list.empty() && expr.RelationName().empty()) { throw BinderException("* expression without FROM clause!"); } StarBindState star_state(new_select_list); @@ -625,6 +625,21 @@ void BindContext::GenerateAllColumnExpressions(StarExpression &expr, if (!binding) { binding = GetMatchingBinding(expr.RelationName(), expr); if (!binding) { + // Expand the nearest enclosing match into column references. Expression binding then + // resolves their correlation depth, just as it does for explicit qualified columns. + auto &scopes = binder.GetEnclosingScopes(); + for (idx_t i = scopes.size(); i > 0; i--) { + auto &scope_context = scopes[i - 1].get().GetBinder().bind_context; + ErrorData scope_error; + if (scope_context.GetBinding(expr.RelationName(), scope_error) || + scope_context.GetMatchingBinding(expr.RelationName(), expr)) { + scope_context.GenerateAllColumnExpressions(expr, new_select_list); + return; + } + } + if (bindings_list.empty()) { + throw BinderException("* expression without FROM clause!"); + } error.Throw(); } is_struct_ref = true; diff --git a/src/duckdb/src/planner/binder.cpp b/src/duckdb/src/planner/binder.cpp index bedc1860c..30aad5b4c 100644 --- a/src/duckdb/src/planner/binder.cpp +++ b/src/duckdb/src/planner/binder.cpp @@ -146,6 +146,11 @@ BoundStatement Binder::Bind(QueryNode &node) { } BoundStatement Binder::Bind(TableRef &ref) { + for (auto &column_alias : ref.column_name_alias) { + if (column_alias.empty()) { + throw BinderException(ref, "column alias cannot be empty"); + } + } BoundStatement result; switch (ref.type) { case TableReferenceType::BASE_TABLE: diff --git a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp index 9dfe3d128..89bf6a80b 100644 --- a/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_aggregate_expression.cpp @@ -127,6 +127,21 @@ BindResult BaseSelectBinder::BindAggregate(FunctionExpression &aggr, AggregateFu if (inside_aggregate) { throw BinderException(aggr, "aggregate function calls cannot be nested"); } + // an aggregate is computed over the rows of the query, not over the elements an enclosing lambda is applied to - + // so the parameters of the enclosing lambdas can not be referenced in the aggregate + struct InaccessibleLambdaBindings { + InaccessibleLambdaBindings(ExpressionBinder &binder_p, idx_t count) + : binder(binder_p), previous_count(binder.inaccessible_lambda_binding_count) { + binder.inaccessible_lambda_binding_count = count; + } + ~InaccessibleLambdaBindings() { + binder.inaccessible_lambda_binding_count = previous_count; + } + ExpressionBinder &binder; + idx_t previous_count; + }; + InaccessibleLambdaBindings inaccessible_lambdas(*this, lambda_bindings ? lambda_bindings->size() : 0); + // first bind the child of the aggregate expression (if any) this->bound_aggregate = true; unique_ptr bound_filter; diff --git a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp index 23945d2a6..6c57f4cd6 100644 --- a/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_columnref_expression.cpp @@ -60,6 +60,9 @@ void ExpressionBinder::QualifyColumnNames(ExpressionBinder &expression_binder, u BindResult ExpressionBinder::BindExpression(LambdaRefExpression &lambda_ref, idx_t depth) { D_ASSERT(lambda_bindings && lambda_ref.LambdaIndex() < lambda_bindings->size()); + if (lambda_ref.LambdaIndex() < inaccessible_lambda_binding_count) { + throw BinderException(lambda_ref, "aggregate functions cannot reference lambda parameters"); + } return (*lambda_bindings)[lambda_ref.LambdaIndex()].Bind(lambda_ref, depth); } diff --git a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp index 79bd7e184..37c6c38c1 100644 --- a/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_function_expression.cpp @@ -106,6 +106,25 @@ struct ListReduceRebindResult { bool override_has_accumulator_type = false; }; +//! Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen accumulator type +//! when decimals are involved +static void CastDecimalLambdaToAccumulatorType(ClientContext &context, BindResult &bind_lambda_result, + const LogicalType &accumulator_type) { + auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); + const auto return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); + if (return_type == accumulator_type || + (!TypeContainsDecimal(accumulator_type) && !TypeContainsDecimal(return_type))) { + return; + } + auto cast_expr = BoundCastExpression::AddCastToType(context, std::move(rebound_lambda_expr.LambdaExprMutable()), + accumulator_type); + if (!cast_expr) { + throw BinderException("Could not cast lambda return type %s to accumulator type %s", return_type.ToString(), + accumulator_type.ToString()); + } + rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); +} + ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t depth, const vector &function_child_types, bind_lambda_function_t bind_lambda_function, @@ -158,22 +177,7 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t bind_lambda_result.error.Throw(); } - auto &rebound_lambda_expr = bind_lambda_result.expression->Cast(); - // Avoid repeated rebinds for DECIMAL type widening by forcing the lambda return type to the chosen - // accumulator type when decimals are involved. - if (TypeContainsDecimal(accumulator_type) || - TypeContainsDecimal(rebound_lambda_expr.LambdaExpr()->GetReturnType())) { - if (rebound_lambda_expr.LambdaExpr()->GetReturnType() != accumulator_type) { - const auto old_return_type = rebound_lambda_expr.LambdaExpr()->GetReturnType(); - auto cast_expr = BoundCastExpression::AddCastToType( - context, std::move(rebound_lambda_expr.LambdaExprMutable()), accumulator_type); - if (!cast_expr) { - throw BinderException("Could not cast lambda return type %s to accumulator type %s", - old_return_type.ToString(), accumulator_type.ToString()); - } - rebound_lambda_expr.LambdaExprMutable() = std::move(cast_expr); - } - } + CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); result.did_rebind = true; result.capture_child_types = std::move(rebind_child_types); } else if (accumulator_type != list_child_type) { @@ -188,6 +192,7 @@ ListReduceRebindResult MaybeRebindListReduceLambda(ClientContext &context, idx_t if (bind_lambda_result.HasError()) { bind_lambda_result.error.Throw(); } + CastDecimalLambdaToAccumulatorType(context, bind_lambda_result, accumulator_type); result.did_rebind = true; result.override_accumulator_type_storage = accumulator_type; result.override_has_accumulator_type = true; @@ -461,7 +466,8 @@ BindResult ExpressionBinder::BindLambdaFunction(FunctionExpression &function, Sc auto &lambda_expr = args[lambda_expr_idx].GetExpressionMutable()->Cast(); unique_ptr lambda_expr_copy; - const bool is_list_reduce = func.name == "list_reduce"; + // the aliases of list_reduce (e.g. reduce) are separate catalog entries + const bool is_list_reduce = func.name == "list_reduce" || func.alias_of == "list_reduce"; if (is_list_reduce) { // We have to make this copy before binding in case we have to double-bind lambda_expr_copy = lambda_expr.Copy(); diff --git a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp index ca14f9277..5953ec8ba 100644 --- a/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_macro_expression.cpp @@ -7,6 +7,7 @@ #include "duckdb/common/exception/binder_exception.hpp" #include "duckdb/common/unique_ptr.hpp" #include "duckdb/function/scalar_macro_function.hpp" +#include "duckdb/main/client_context.hpp" #include "duckdb/parser/expression/conjunction_expression.hpp" #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/parser/expression/subquery_expression.hpp" @@ -204,6 +205,8 @@ void ExpressionBinder::UnfoldMacroExpression(FunctionExpression &function, Scala BindResult ExpressionBinder::BindMacro(FunctionExpression &function, ScalarMacroCatalogEntry ¯o_func, idx_t depth, unique_ptr &expr) { auto stack_checker = StackCheck(*expr, 3); + // expanding nested macros can take a long time, allow the query to be interrupted + context.InterruptCheck(); // unfold the macro expression UnfoldMacroExpression(function, macro_func, expr, depth); diff --git a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp index 77a0e953d..d44038f98 100644 --- a/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp +++ b/src/duckdb/src/planner/binder/expression/bind_window_expression.cpp @@ -226,7 +226,9 @@ BindResult BaseSelectBinder::BindWindowExpression(WindowExpression &window, idx_ // bind the aggregate FunctionBinder function_binder(binder); - auto window_bound_aggregate = function_binder.BindAggregateFunction(func, std::move(arguments), error, nullptr); + auto window_bound_aggregate = function_binder.BindAggregateFunction( + func, std::move(arguments), error, nullptr, + window.Distinct() ? AggregateType::DISTINCT : AggregateType::NON_DISTINCT); // No function found, throw an error if (!window_bound_aggregate) { error.AddQueryLocation(window); diff --git a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp index c0709ba68..d798f33ae 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_cte_node.cpp @@ -142,6 +142,9 @@ void CTEBindState::Bind(CTEBinding &binding) { // names are picked from the LHS, unless aliases are explicitly specified names = query.names; for (idx_t i = 0; i < aliases.size() && i < names.size(); i++) { + if (aliases[i].empty()) { + throw BinderException("column alias cannot be empty"); + } names[i] = aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp index 8bf83d31b..9ce269546 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_recursive_cte_node.cpp @@ -42,6 +42,9 @@ BoundStatement Binder::BindNode(RecursiveCTENode &statement) { // names are picked from the LHS, unless aliases are explicitly specified result.names = left.names; for (idx_t i = 0; i < statement.aliases.size() && i < result.names.size(); i++) { + if (statement.aliases[i].empty()) { + throw BinderException("column alias cannot be empty"); + } result.names[i] = statement.aliases[i]; } diff --git a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp index 27ce506ff..49843c812 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp @@ -725,9 +725,6 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from } bind_state.AddRegularColumn(); - if (can_group_by_all && is_window) { - throw BinderException("Cannot group on a window clause"); - } if (can_group_by_all && result.unnests.SelectList().size() > unnest_count) { throw BinderException("Cannot group on an UNNEST or UNLIST clause"); } @@ -735,6 +732,10 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from if (select_binder.BoundAggregates()) { throw BinderException("Cannot mix aggregates with non-aggregated columns!"); } + if (is_window) { + // a window over aggregates (e.g. sum(sum(x)) OVER ()) is fine, a window over a group is not + throw BinderException("Cannot group on a window clause"); + } // we are forcing aggregates, and the node has columns bound // this entry becomes a group group_by_all_indexes.push_back(i); diff --git a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp index 52387f016..5b4e01ca8 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_table_macro_node.cpp @@ -5,6 +5,7 @@ #include "duckdb/planner/expression_binder.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/common/limits.hpp" +#include "duckdb/main/client_context.hpp" #include "duckdb/main/config.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/parser/expression/comparison_expression.hpp" @@ -18,6 +19,8 @@ namespace duckdb { unique_ptr Binder::BindTableMacro(FunctionExpression &function, TableMacroCatalogEntry ¯o_func, idx_t depth) { + // expanding nested macros can take a long time, allow the query to be interrupted + context.InterruptCheck(); // validate the arguments and separate positional and default arguments vector> positional_arguments; InsertionOrderPreservingMap, Identifier, identifier_map_t> named_arguments; diff --git a/src/duckdb/src/planner/binder/statement/bind_attach.cpp b/src/duckdb/src/planner/binder/statement/bind_attach.cpp index 4a19a052b..81718c46e 100644 --- a/src/duckdb/src/planner/binder/statement/bind_attach.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_attach.cpp @@ -30,7 +30,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { if (stmt.info->parsed_path) { TableFunctionBinder path_binder(*this, context, "Attach", "Attach path"); auto bound_path = path_binder.Bind(stmt.info->parsed_path); - auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path); + auto path_val = ExpressionExecutor::EvaluateScalar(context, *bound_path, true); if (path_val.IsNull()) { throw BinderException("ATTACH path expression must not evaluate to NULL"); } @@ -43,7 +43,7 @@ BoundStatement Binder::Bind(AttachStatement &stmt) { unordered_map kv_options; for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for ATTACH option \"" + entry.first + "\""); } diff --git a/src/duckdb/src/planner/binder/statement/bind_connect.cpp b/src/duckdb/src/planner/binder/statement/bind_connect.cpp index 59f485d09..23823cbba 100644 --- a/src/duckdb/src/planner/binder/statement/bind_connect.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_connect.cpp @@ -19,7 +19,7 @@ BoundStatement Binder::Bind(ConnectStatement &stmt) { TableFunctionBinder option_binder(*this, context, "Connect", "Connect parameter"); for (auto &entry : stmt.info->parsed_options) { auto bound_expr = option_binder.Bind(entry.second); - auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr); + auto val = ExpressionExecutor::EvaluateScalar(context, *bound_expr, true); if (val.IsNull()) { throw BinderException("NULL is not supported as a valid option for CONNECT option \"" + entry.first + "\""); diff --git a/src/duckdb/src/planner/binder/statement/bind_copy.cpp b/src/duckdb/src/planner/binder/statement/bind_copy.cpp index 756aa4d9e..6a8d67aee 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy.cpp @@ -782,7 +782,7 @@ void Binder::BindCopyFormat(CopyInfo &info) { if (info.file_path_expression) { auto inputs = BindCopyOption(context, *this, "filename", info.file_path_expression, conversion_policy); if (inputs.size() != 1 || inputs[0].type().id() != LogicalTypeId::VARCHAR) { - throw InternalException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); + throw BinderException("Unsupported parameter type for filename: expected e.g. TARGET 'file.parquet'"); } if (!info.file_path.empty()) { throw InternalException("Both a file path and a file path expression were provided for COPY - only one of " diff --git a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp index 7596bfa3c..af07d6889 100644 --- a/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_copy_database.cpp @@ -51,14 +51,13 @@ unique_ptr Binder::BindCopyDatabaseSchema(Catalog &from_databas unique_ptr Binder::BindCopyDatabaseData(Catalog &source_catalog, const Identifier &target_database_name) { - auto source_schemas = source_catalog.GetSchemas(context); - - // We can just use ExtractEntries here because the order doesn't matter - ExportEntries entries; - PhysicalExport::ExtractEntries(context, source_schemas, entries); + auto entries = PhysicalExport::GetNaiveExportOrder(context, source_catalog); vector> insert_nodes; - for (auto &table_ref : entries.tables) { + for (auto &table_ref : entries) { + if (table_ref.get().type != CatalogType::TABLE_ENTRY) { + continue; + } auto &table = table_ref.get().Cast(); // generate the insert statement InsertStatement insert_stmt; diff --git a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp index 25be3c9ac..13c4247b3 100644 --- a/src/duckdb/src/planner/binder/statement/bind_create_table.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_create_table.cpp @@ -43,6 +43,11 @@ static void VerifyCompressionType(ClientContext &context, optional_ptrCast(); for (auto &col : base.columns.Logical()) { auto compression_type = col.CompressionType(); + if (col.Generated() && compression_type != CompressionType::COMPRESSION_AUTO) { + throw BinderException("Can't set a compression type on generated column %s, generated columns are " + "not stored", + col.Name()); + } auto compression_availability_result = CompressionTypeIsAvailable(compression_type, storage_manager); if (!compression_availability_result.IsAvailable()) { if (compression_availability_result.IsDeprecated()) { diff --git a/src/duckdb/src/planner/binder/statement/bind_export.cpp b/src/duckdb/src/planner/binder/statement/bind_export.cpp index 81f22f66f..7869d95b1 100644 --- a/src/duckdb/src/planner/binder/statement/bind_export.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_export.cpp @@ -46,10 +46,11 @@ string SanitizeExportIdentifier(const Identifier &str) { return result; } -bool ReferencedTableIsOrdered(const Identifier &referenced_table, catalog_entry_vector_t &ordered) { +bool ReferencedTableIsOrdered(const TableCatalogEntry &referencing_table, const Identifier &referenced_table, + catalog_entry_vector_t &ordered) { for (auto &entry : ordered) { auto &table_entry = entry.get().Cast(); - if (table_entry.name == referenced_table) { + if (&table_entry.ParentSchema() == &referencing_table.ParentSchema() && table_entry.name == referenced_table) { // The referenced table is already ordered return true; } @@ -79,7 +80,7 @@ void ScanForeignKeyTable(catalog_entry_vector_t &ordered, catalog_entry_vector_t // This table references a table, don't move it yet move_to_ordered = false; break; - } else if (!ReferencedTableIsOrdered(fk.info.table, ordered)) { + } else if (!ReferencedTableIsOrdered(table_entry, fk.info.table, ordered)) { // The table that it references isn't ordered yet move_to_ordered = false; break; diff --git a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp index bb986b527..0bbee6fd6 100644 --- a/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_external_resource.cpp @@ -15,7 +15,7 @@ namespace duckdb { Value Binder::BindExternalResourceValue(unique_ptr &expr) { TableFunctionBinder binder(*this, context, "External resource", "External resource parameter"); auto bound = binder.Bind(expr); - return ExpressionExecutor::EvaluateScalar(context, *bound); + return ExpressionExecutor::EvaluateScalar(context, *bound, true); } void Binder::BindExternalResourceParams(case_insensitive_map_t> &parsed_params, diff --git a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp index c053c9189..20b47314c 100644 --- a/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp +++ b/src/duckdb/src/planner/binder/statement/bind_merge_into.cpp @@ -186,6 +186,39 @@ void RewriteMergeBindings(unique_ptr &expr, const vector FindMergeJoin(LogicalOperator &op, LogicalOperator &source, + LogicalOperator &target, idx_t &source_idx) { + if (op.children.size() == 2) { + for (idx_t child_idx = 0; child_idx < 2; child_idx++) { + if (ContainsOperator(*op.children[child_idx], source) && + ContainsOperator(*op.children[1 - child_idx], target)) { + source_idx = child_idx; + return op; + } + } + } + for (auto &child : op.children) { + auto result = FindMergeJoin(*child, source, target, source_idx); + if (result) { + return result; + } + } + return nullptr; +} + void RewriteMergeBindings(LogicalOperator &op, const vector &source_bindings, TableIndex new_table_index) { LogicalOperatorVisitor::EnumerateExpressions( @@ -317,6 +350,7 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { } else { join.type = JoinType::INNER; } + auto &source_op = *source_binding.plan; join.left = make_uniq(std::move(source_binding), std::move(source_binder)); join.right = make_uniq(std::move(bound_table), std::move(target_binder)); if (node.join_condition) { @@ -327,16 +361,14 @@ BoundStatement Binder::BindNode(MergeQueryNode &node) { auto bound_join_node = Bind(join); auto root = std::move(bound_join_node.plan); - auto join_ref = reference(*root); - while (join_ref.get().children.size() == 1) { - join_ref = *join_ref.get().children[0]; - } - if (join_ref.get().children.size() != 2) { - throw NotImplementedException("Expected a join after binding a join operator - but got a %s", - join_ref.get().type); + idx_t source_idx = 0; + auto merge_join = FindMergeJoin(*root, source_op, get, source_idx); + if (!merge_join) { + throw NotImplementedException("Expected a join after binding a join operator - but got a %s", root->type); } + auto join_ref = reference(*merge_join); // CreatePlan can turn a RIGHT join into a LEFT join - in that case the children are reversed - bool inverted = join.type == JoinType::RIGHT && join_ref.get().Cast().join_type == JoinType::LEFT; + bool inverted = source_idx == 1; auto &source = join_ref.get().children[inverted ? 1 : 0]; // bind WHEN_MATCHED merge actions (can contain references to both source and target) diff --git a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp index 5fef48a72..46ecedf6b 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_basetableref.cpp @@ -62,8 +62,8 @@ BoundStatement Binder::BindWithReplacementScan(ClientContext &context, BaseTable // Then the database-wide ones, including the built-in file scans if (!replacement_function) { - for (const auto &scan : DBConfig::GetConfig(context).replacement_scans) { - if (auto result = scan.function(context, input, scan.data.get())) { + for (const auto &scan : DBConfig::GetConfig(context).replacement_scans.Snapshot()) { + if (auto result = scan->function(context, input, scan->data.get())) { replacement_function = std::move(result); break; } @@ -304,6 +304,8 @@ BoundStatement Binder::Bind(BaseTableRef &ref) { case CatalogType::VIEW_ENTRY: { // the node is a view: get the query that the view represents auto &view_catalog_entry = table_or_view->Cast(); + // expanding nested views can take a long time, allow the query to be interrupted + context.InterruptCheck(); // We need to use a new binder for the view that doesn't reference any CTEs // defined for this binder so there are no collisions between the CTEs defined // for the view and for the current query diff --git a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp index 031e644aa..453117558 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_pivot.cpp @@ -271,11 +271,35 @@ static unique_ptr PivotInitialAggregate(ClientContext &context, Pivo return subquery_stage1; } +//! The key of a pivot value: the length of the value, followed by the value - or "N" for NULL values. +//! Prefixing the length makes the concatenated keys of multiple pivot values unambiguous. +static string PivotValueKey(const Value &value) { + if (value.IsNull()) { + return "N"; + } + auto str = value.ToString(); + return to_string(str.size()) + ":" + str; +} + unique_ptr ConstructPivotExpression(unique_ptr pivot_expr) { + // coalesce(strlen(pivot::VARCHAR)::VARCHAR || ':' || pivot::VARCHAR, 'N'), see PivotValueKey auto cast = make_uniq(LogicalType::VARCHAR, std::move(pivot_expr)); + vector> strlen_children; + strlen_children.push_back(cast->Copy()); + auto length = make_uniq(LogicalType::VARCHAR, + make_uniq("strlen", std::move(strlen_children))); + vector> prefix_children; + prefix_children.push_back(std::move(length)); + prefix_children.push_back(ConstantExpression::String(":")); + auto prefix = make_uniq("||", std::move(prefix_children)); + vector> key_children; + key_children.push_back(std::move(prefix)); + key_children.push_back(std::move(cast)); + auto key = make_uniq("||", std::move(key_children)); + vector> coalesce_children; - coalesce_children.push_back(std::move(cast)); - coalesce_children.push_back(ConstantExpression::String("NULL")); + coalesce_children.push_back(std::move(key)); + coalesce_children.push_back(ConstantExpression::String("N")); auto coalesce = make_uniq(ExpressionType::OPERATOR_COALESCE, std::move(coalesce_children)); return std::move(coalesce); } @@ -447,8 +471,9 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { } if (unique_names.size() != aggregates.size()) { - throw InternalException("Pivot aggregate mismatch: %llu unique names, %llu aggregates", unique_names.size(), - aggregates.size()); + // identical aggregates with different names are bound to a single aggregate + throw BinderException("PIVOT: identical aggregates must have the same name - use a single aggregate, or " + "make the aggregates differ"); } identifier_map_t name_to_position; @@ -489,13 +514,11 @@ BoundStatement Binder::BindBoundPivot(PivotRef &ref) { auto name = pivot_value.name + aggr_name; string pivot_str; - for (auto &value : pivot_value.values) { - auto str = value.ToString(); - if (pivot_str.empty()) { - pivot_str = std::move(str); - } else { - pivot_str += "_" + str; + for (idx_t value_idx = 0; value_idx < pivot_value.values.size(); value_idx++) { + if (value_idx > 0) { + pivot_str += "_"; } + pivot_str += PivotValueKey(pivot_value.values[value_idx]); } result.bound_pivot.pivot_values.push_back(std::move(pivot_str)); names.push_back(Identifier(std::move(name))); diff --git a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp index 697da9c54..de12d298e 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_showref.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_showref.cpp @@ -275,6 +275,9 @@ static void WarnShowDescribesQuery(ClientContext &context) { BoundStatement Binder::BindShow(ShowRef &ref) { BoundStatement result; if (ref.GetTableName().empty()) { + if (!ref.query) { + throw BinderException("SHOW requires a setting name, a table name or a query"); + } // "SHOW (SELECT ...)": describe the columns of the query. Deprecated in favor of DESCRIBE, and not affected // by show_behavior (a parenthesized query is neither a setting nor a named table). WarnShowDescribesQuery(context); diff --git a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp index 30f39a605..0332bf655 100644 --- a/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp +++ b/src/duckdb/src/planner/binder/tableref/bind_table_function.cpp @@ -36,6 +36,15 @@ static TableFunctionBindType GetTableFunctionBindType(TableFunctionCatalogEntry } } if (all_scalar) { + for (idx_t function_idx = 0; function_idx < table_function.functions.Size(); function_idx++) { + const auto &function = *table_function.functions.GetFunctionByOffset(function_idx); + for (auto ¶m : function.GetSignature().GetParameters()) { + if (param.GetType().id() == LogicalTypeId::TABLE) { + throw BinderException("Table function %s requires a subquery as its TABLE parameter", + table_function.name); + } + } + } return TableFunctionBindType::STANDARD_TABLE_FUNCTION; } // if we have non-scalar parameters - we need to look at the function definition to decide how to bind diff --git a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp index f98bd5d44..07437da74 100644 --- a/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_aggregate_expression.cpp @@ -1,4 +1,5 @@ #include "duckdb/planner/expression/bound_aggregate_expression.hpp" + #include "duckdb/parser/expression/function_expression.hpp" #include "duckdb/catalog/catalog_entry/aggregate_function_catalog_entry.hpp" @@ -91,6 +92,11 @@ bool BoundAggregateExpression::Equals(const BaseExpression &other_p) const { if (!Expression::Equals(*children[i], *other.children[i])) { return false; } + // expression equality ignores the return type, but a collation lives in the return type and decides + // which argument values compare equal - two aggregates that disagree on it are not the same aggregate + if (!children[i]->GetReturnType().EqualsIncludingCollation(other.children[i]->GetReturnType())) { + return false; + } } if (state_export_mode != other.state_export_mode) { return false; diff --git a/src/duckdb/src/planner/expression/bound_cast_expression.cpp b/src/duckdb/src/planner/expression/bound_cast_expression.cpp index 896f4c36b..26d925cb2 100644 --- a/src/duckdb/src/planner/expression/bound_cast_expression.cpp +++ b/src/duckdb/src/planner/expression/bound_cast_expression.cpp @@ -109,6 +109,48 @@ unique_ptr BoundCastExpression::AddArrayCastToList(ClientContext &co return BoundCastExpression::AddCastToType(context, std::move(expr), LogicalType::LIST(child_type)); } +//! Whether a cast between nested types is invertible: the structure has to stay the same, and all child casts have to +//! be invertible +static bool NestedCastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { + if (source_type.id() != target_type.id()) { + return false; + } + switch (source_type.id()) { + case LogicalTypeId::LIST: + return BoundCastExpression::CastIsInvertible(ListType::GetChildType(source_type), + ListType::GetChildType(target_type)); + case LogicalTypeId::ARRAY: + if (ArrayType::IsAnySize(source_type) || ArrayType::IsAnySize(target_type) || + ArrayType::GetSize(source_type) != ArrayType::GetSize(target_type)) { + return false; + } + return BoundCastExpression::CastIsInvertible(ArrayType::GetChildType(source_type), + ArrayType::GetChildType(target_type)); + case LogicalTypeId::MAP: + return BoundCastExpression::CastIsInvertible(MapType::KeyType(source_type), MapType::KeyType(target_type)) && + BoundCastExpression::CastIsInvertible(MapType::ValueType(source_type), MapType::ValueType(target_type)); + case LogicalTypeId::STRUCT: + case LogicalTypeId::TUPLE: { + // adding or removing fields is not invertible + auto &source_children = StructType::GetChildTypes(source_type); + auto &target_children = StructType::GetChildTypes(target_type); + if (source_children.size() != target_children.size()) { + return false; + } + for (idx_t child_idx = 0; child_idx < source_children.size(); child_idx++) { + if (source_children[child_idx].first != target_children[child_idx].first || + !BoundCastExpression::CastIsInvertible(source_children[child_idx].second, + target_children[child_idx].second)) { + return false; + } + } + return true; + } + default: + return false; + } +} + bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const LogicalType &target_type) { D_ASSERT(source_type.IsValid() && target_type.IsValid()); if (source_type.id() == LogicalTypeId::BOOLEAN || target_type.id() == LogicalTypeId::BOOLEAN) { @@ -123,6 +165,9 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (source_type.id() == LogicalTypeId::VARIANT || target_type.id() == LogicalTypeId::VARIANT) { return false; } + if (source_type.IsNested() || target_type.IsNested()) { + return NestedCastIsInvertible(source_type, target_type); + } if (source_type.id() == LogicalTypeId::DECIMAL || target_type.id() == LogicalTypeId::DECIMAL) { uint8_t source_width, target_width; uint8_t source_scale, target_scale; @@ -137,7 +182,22 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const if (target_scale < source_scale) { return false; } - return true; + if (target_type.IsUnsigned()) { + // decimals can be negative + return false; + } + // the target needs to fit all digits before the decimal point + // integral types do not fit all numbers of their decimal width, e.g. SMALLINT (width 5) does not fit 99999 + auto source_digits = source_width - source_scale; + auto target_digits = target_width - target_scale; + if (source_type.id() == LogicalTypeId::HUGEINT || source_type.id() == LogicalTypeId::UHUGEINT) { + // (u)hugeint values can have up to 39 digits + source_digits++; + } + if (target_type.id() != LogicalTypeId::DECIMAL) { + target_digits--; + } + return source_digits <= target_digits; } switch (source_type.id()) { case LogicalTypeId::TIMESTAMP: @@ -169,6 +229,12 @@ bool BoundCastExpression::CastIsInvertible(const LogicalType &source_type, const break; } break; + case LogicalTypeId::TIME_NS: + // a cast to microseconds rounds the nanoseconds + if (target_type.id() == LogicalTypeId::TIME) { + return false; + } + break; case LogicalTypeId::VARCHAR: case LogicalTypeId::BIT: case LogicalTypeId::TIME_TZ: diff --git a/src/duckdb/src/planner/expression_binder/check_binder.cpp b/src/duckdb/src/planner/expression_binder/check_binder.cpp index be5ceace5..519488add 100644 --- a/src/duckdb/src/planner/expression_binder/check_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/check_binder.cpp @@ -35,8 +35,9 @@ BindResult ExpressionBinder::BindQualifiedColumnName(ColumnRefExpression &colref if (colref.ColumnNames()[0] == table_name) { struct_start++; } - auto result = make_uniq_base(colref.ColumnNames().back()); - for (idx_t i = struct_start; i + 1 < colref.ColumnNames().size(); i++) { + // the first name (after the table name) is the column, the remaining names are struct fields + auto result = make_uniq_base(colref.ColumnNames()[struct_start]); + for (idx_t i = struct_start + 1; i < colref.ColumnNames().size(); i++) { result = CreateStructExtract(std::move(result), colref.ColumnNames()[i]); } return BindExpression(result, 0); diff --git a/src/duckdb/src/planner/expression_binder/index_binder.cpp b/src/duckdb/src/planner/expression_binder/index_binder.cpp index f5c2341f0..19b38df7f 100644 --- a/src/duckdb/src/planner/expression_binder/index_binder.cpp +++ b/src/duckdb/src/planner/expression_binder/index_binder.cpp @@ -1,9 +1,11 @@ #include "duckdb/planner/expression_binder/index_binder.hpp" #include "duckdb/catalog/catalog.hpp" +#include "duckdb/planner/binder.hpp" #include "duckdb/parser/parsed_data/create_index_info.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/planner/expression/bound_columnref_expression.hpp" +#include "duckdb/planner/binder.hpp" #include "duckdb/planner/column_binding.hpp" #include "duckdb/execution/index/bound_index.hpp" #include "duckdb/execution/index/unbound_index.hpp" @@ -80,19 +82,34 @@ unique_ptr IndexBinder::BindCreateIndex(ClientContext &context, // Add the dependencies. auto &dependencies = create_index_info->dependencies; auto &catalog = table_entry.ParentCatalog(); - SetCatalogLookupCallback([&dependencies, &catalog](CatalogEntry &entry) { + SetCatalogLookupCallback([&dependencies, &catalog, &table_entry](CatalogEntry &entry) { if (&catalog != &entry.ParentCatalog()) { return; } - // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table - dependencies.AddDependency(entry, DependencyDependentFlags()); + if (&entry == &table_entry) { + // indexes do not require CASCADE to be dropped, they are simply always dropped along with the table + dependencies.AddDependency(entry, DependencyDependentFlags()); + return; + } + // other entries the index expressions use (e.g. macros) cannot be dropped, replaced or altered while the index + // exists (note that blocking alone is the legacy value that is read as non-blocking for indexes) + dependencies.AddDependency(entry, DependencyDependentFlags().SetBlocking().SetAlterBlocking()); }); // Bind the index expressions. + // The expressions are bound again when the index is loaded, so they cannot depend on the session or the query + // they are bound in (e.g. now(), getvariable()) - such functions are folded to constants while binding. + auto &properties = binder.GetStatementProperties(); + const auto statement_requires_rebind = properties.always_require_rebind; vector> expressions; for (auto &expr : create_index_info->expressions) { + properties.always_require_rebind = false; expressions.push_back(Bind(expr)); + if (properties.always_require_rebind) { + throw BinderException("Index keys cannot contain expressions with side effects."); + } } + properties.always_require_rebind = statement_requires_rebind; auto &get = plan->Cast(); InitCreateIndexInfo(get, *create_index_info); diff --git a/src/duckdb/src/planner/filter/dynamic_filter.cpp b/src/duckdb/src/planner/filter/dynamic_filter.cpp index a95f643a1..4e235b845 100644 --- a/src/duckdb/src/planner/filter/dynamic_filter.cpp +++ b/src/duckdb/src/planner/filter/dynamic_filter.cpp @@ -24,13 +24,13 @@ void DynamicFilterData::SetValue(Value val) { if (val.IsNull()) { return; } - lock_guard l(lock); + annotated_lock_guard l(lock); constant = std::move(val); initialized = true; } void DynamicFilterData::Reset() { - lock_guard l(lock); + annotated_lock_guard l(lock); initialized = false; } diff --git a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp index 13e56d61d..4b0a0cbf8 100644 --- a/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp +++ b/src/duckdb/src/planner/filter/table_filter_dynamic_function.cpp @@ -21,6 +21,7 @@ DynamicFilterData::DynamicFilterData(ExpressionType comparison_type_p, Value con } unique_ptr DynamicFilterData::ToExpression(const Expression &column) const { + annotated_shared_lock guard(lock); return BoundComparisonExpression::Create(comparison_type, column.Copy(), make_uniq(constant)); } @@ -120,7 +121,7 @@ static idx_t DynamicFilterSelect(DataChunk &args, ExpressionState &state, option ExpressionType comparison_type; Value constant; { - lock_guard lock(func_data.filter_data->lock); + annotated_shared_lock lock(func_data.filter_data->lock); comparison_type = func_data.filter_data->comparison_type; constant = func_data.filter_data->constant; } @@ -150,7 +151,7 @@ FilterPropagateResult DynamicFilterScalarFun::FilterPrune(const FunctionStatisti if (!data.filter_data) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } - lock_guard lock(data.filter_data->lock); + annotated_shared_lock lock(data.filter_data->lock); if (!data.filter_data->initialized) { return FilterPropagateResult::NO_PRUNING_POSSIBLE; } diff --git a/src/duckdb/src/planner/operator/logical_join.cpp b/src/duckdb/src/planner/operator/logical_join.cpp index c083f190f..ab5ec92f9 100644 --- a/src/duckdb/src/planner/operator/logical_join.cpp +++ b/src/duckdb/src/planner/operator/logical_join.cpp @@ -2,6 +2,7 @@ #include "duckdb/planner/expression/bound_columnref_expression.hpp" #include "duckdb/planner/expression_iterator.hpp" +#include "duckdb/planner/operator/logical_filter.hpp" #include "duckdb/main/config.hpp" namespace duckdb { @@ -83,6 +84,21 @@ void LogicalJoin::GetExpressionBindings(const Expression &root_expr, unordered_s }); } +unique_ptr LogicalJoin::PromoteJoinSide(unique_ptr child, + vector projection_map) { + if (projection_map.empty()) { + return child; + } + auto filter = make_uniq(); + filter->projection_map = std::move(projection_map); + filter->children.push_back(std::move(child)); + if (filter->children[0]->has_estimated_cardinality) { + filter->SetEstimatedCardinality(filter->children[0]->estimated_cardinality); + } + filter->ResolveOperatorTypes(); + return std::move(filter); +} + void LogicalJoin::MoveJoinState(LogicalJoin &source, LogicalJoin &target) { D_ASSERT(source.join_type == target.join_type); target.mark_index = source.mark_index; diff --git a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp index 46c597971..339cfba43 100644 --- a/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp +++ b/src/duckdb/src/planner/sql_export/bound_expression_sql_exporter.cpp @@ -435,13 +435,25 @@ BoundExpressionSQLExportState::ExportComparison(const BoundFunctionExpression &e D_ASSERT(!expression.BindInfo()); D_ASSERT(ChildrenAreConsistentWithArguments(expression.GetChildren(), expression.Function().GetArguments())); D_ASSERT(expression.GetReturnType() == expression.Function().GetReturnType()); - D_ASSERT(expression.GetChildren()[0]->GetReturnType() == expression.GetChildren()[1]->GetReturnType()); + auto &left_type = expression.GetChildren()[0]->GetReturnType(); + auto &right_type = expression.GetChildren()[1]->GetReturnType(); + // the regex range filter compares VARCHAR values with BLOB bounds, as the bounds need not be valid UTF-8 + const bool varchar_blob = (left_type.id() == LogicalTypeId::VARCHAR && right_type.id() == LogicalTypeId::BLOB) || + (left_type.id() == LogicalTypeId::BLOB && right_type.id() == LogicalTypeId::VARCHAR); + D_ASSERT(left_type == right_type || varchar_blob); vector> children; vector issues; ExportChildren(expression.GetChildren(), path, children, issues); if (!issues.empty()) { return BoundExpressionSQLExportResult::Failure(std::move(issues)); } + if (varchar_blob) { + // encode reinterprets the VARCHAR bytes as a BLOB, so the bytes are compared as before + auto &varchar_child = left_type.id() == LogicalTypeId::VARCHAR ? children[0] : children[1]; + vector> arguments; + arguments.push_back(std::move(varchar_child)); + varchar_child = SQLExportHelpers::SystemFunction("encode", std::move(arguments)); + } return BoundExpressionSQLExportResult::Success(make_uniq( expression.GetExpressionType(), std::move(children[0]), std::move(children[1]))); } diff --git a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp index f3e359ac7..21298cdce 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_composition.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_composition.cpp @@ -331,6 +331,9 @@ bool LogicalPlanSQLExportHelpers::ComposeOrder(LogicalOperator &op, LogicalPlanS AddSubstitutions(state, child, *source); auto expressions = CollectExpressions(op); auto &modifier = select.modifiers[0]->Cast(); + if (modifier.orders.size() != expressions.size()) { + return false; + } vector> orders; for (idx_t i = 0; i < expressions.size(); i++) { auto &expression = *SemanticInput(expressions[i].get()); diff --git a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp index afcd594b6..b17415c21 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_functions.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_functions.cpp @@ -332,9 +332,13 @@ BoundExpressionSQLExportState::BuildAggregateCall(const BoundAggregateExpression auto order_bys = make_uniq(); if (expression.GetOrderBys()) { for (auto &order : expression.GetOrderBys()->orders) { - order_bys->orders.emplace_back(order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), - std::move(children[child_index++]))); + auto &child = children[child_index++]; + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } + order_bys->orders.emplace_back( + order.type, order.null_order, + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } } result->FilterMutable() = std::move(filter); diff --git a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp index 2806aa549..f0a40204a 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_relations.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_relations.cpp @@ -233,6 +233,9 @@ ExportOrderModifier(LogicalOperator &op, LogicalPlanSQLExportContext &context, c if (expression.HasError()) { return Result::Failure(expression); } + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *expression.GetValue())) { + continue; + } modifier->orders.emplace_back( order.type, order.null_order, SQLExportHelpers::OrderExpression(SemanticExpressionType(*order.expression, expression_context), @@ -262,6 +265,10 @@ static LogicalPlanSQLExportResult ExportOrderedRelation(LogicalOperator &op, Log if (modifier.HasError()) { return LogicalPlanSQLExportResult::Failure(modifier); } + if (modifier.GetValue()->orders.empty()) { + LogicalPlanSQLExportHelpers::SetChildScope(*select, std::move(child.GetValue()), plain); + return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); + } select->modifiers.push_back(std::move(modifier.GetValue())); if (!plain && LogicalPlanSQLExportHelpers::ComposeOrder(op, child.GetValue(), *select, expression_context, path)) { return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); @@ -469,7 +476,9 @@ LogicalPlanSQLExportResult LogicalDistinct::ToSQL(LogicalPlanSQLExportContext &c if (order.HasError()) { return LogicalPlanSQLExportResult::Failure(order); } - select->modifiers.push_back(std::move(order.GetValue())); + if (!order.GetValue()->orders.empty()) { + select->modifiers.push_back(std::move(order.GetValue())); + } } select->from_table = LogicalPlanSQLExportHelpers::CreateSubquery(std::move(child.GetValue())); return LogicalPlanSQLExportResult::Success({std::move(select), std::move(fields.GetValue())}); diff --git a/src/duckdb/src/planner/sql_export/sql_export_window.cpp b/src/duckdb/src/planner/sql_export/sql_export_window.cpp index 9f329bb12..64dab0866 100644 --- a/src/duckdb/src/planner/sql_export/sql_export_window.cpp +++ b/src/duckdb/src/planner/sql_export/sql_export_window.cpp @@ -82,6 +82,19 @@ static optional_ptr RangeOffset(const BoundWindowExpression &e return &offset; } +static bool IsOrderDependentBoundary(WindowBoundary boundary) { + switch (boundary) { + case WindowBoundary::EXPR_PRECEDING_RANGE: + case WindowBoundary::EXPR_FOLLOWING_RANGE: + case WindowBoundary::CURRENT_ROW_GROUPS: + case WindowBoundary::EXPR_PRECEDING_GROUPS: + case WindowBoundary::EXPR_FOLLOWING_GROUPS: + return true; + default: + return false; + } +} + static WindowSQLFrame ReconstructWindowFrame(const BoundWindowExpression &expression) { WindowSQLFrame frame; if (expression.OrderBy().size() == 1) { @@ -178,10 +191,17 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression for (idx_t i = 0; i < expression.Partitions().size(); i++) { window->PartitionsMutable().push_back(std::move(children[ordinal++])); } + // RANGE offsets and GROUPS frames are defined in terms of the ORDER BY + const bool frame_needs_order = + IsOrderDependentBoundary(expression.WindowStart()) || IsOrderDependentBoundary(expression.WindowEnd()); for (auto &order : expression.OrderBy()) { + auto &child = children[ordinal++]; + if (!frame_needs_order && SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } window->OrderByMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } auto &named_arguments = function.GetNamedArguments(); auto positional_count = function.GetPositionalArgumentCount(); @@ -204,9 +224,13 @@ BoundExpressionSQLExportState::ExportWindowFunction(const BoundWindowExpression window->EndExprMutable() = std::move(children[ordinal++]); } for (auto &order : expression.ArgOrders()) { + auto &child = children[ordinal++]; + if (SQLExportHelpers::IsConstantOrderKey(*order.expression, *child)) { + continue; + } window->ArgOrdersMutable().emplace_back( order.type, order.null_order, - SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(children[ordinal++]))); + SQLExportHelpers::OrderExpression(order.expression->GetReturnType(), std::move(child))); } window->IgnoreNullsMutable() = expression.IgnoreNulls(); window->HasIgnoreNullsMutable() = expression.IgnoreNulls(); diff --git a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp index 5e198a4b1..8c9dd8106 100644 --- a/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp +++ b/src/duckdb/src/planner/subquery/delim_join_cte_rewriter.cpp @@ -2631,9 +2631,8 @@ static bool CanRestrictCorrelationDomain(LogicalOperator &op, unordered_set(binder.context) && - config.options.disabled_optimizers.find(OptimizerType::DELIMINATOR) == config.options.disabled_optimizers.end(); + cte_deliminator_enabled = Settings::Get(binder.context) && + !config.IsOptimizerDisabled(OptimizerType::DELIMINATOR); } BindingReplacementGraph DelimJoinCTERewriter::MaterializeDelimJoinAsCTE(unique_ptr &plan, diff --git a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp index 457740d4c..69038c5d7 100644 --- a/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp +++ b/src/duckdb/src/planner/subquery/flatten_dependent_join.cpp @@ -1107,12 +1107,22 @@ FlattenDependentJoins::UnnestingState FlattenDependentJoins::PushDownJoin(unique } if (join_type == JoinType::MARK) { - if (!left_requires_domain && right_requires_domain) { + if (right_requires_domain) { auto right_state = PushDownChild(plan, propagate_null_values, state, false, 1); auto left_state = PushDownChild(plan, propagate_null_values, right_state.bindings, false, 0); - AddCorrelatedJoinConditions(plan->Cast(), left_state.bindings, right_state.bindings); + auto &comparison_join = plan->Cast(); + auto conditions = std::move(comparison_join.conditions); + comparison_join.conditions.clear(); + AddCorrelatedJoinConditions(comparison_join, left_state.bindings, right_state.bindings); + // Keep the correlation keys before the IN predicate for per-group NULL and empty-set handling. + comparison_join.mark_types.insert(comparison_join.mark_types.begin(), delim_types.begin(), + delim_types.end()); + for (auto &condition : conditions) { + comparison_join.conditions.push_back(std::move(condition)); + } + RewriteCorrelatedBindings(*plan, left_state.bindings); left_state.replacement_graph.Merge(right_state.replacement_graph); return left_state; } diff --git a/src/duckdb/src/storage/block_allocator.cpp b/src/duckdb/src/storage/block_allocator.cpp index d7088a345..60b19d2d7 100644 --- a/src/duckdb/src/storage/block_allocator.cpp +++ b/src/duckdb/src/storage/block_allocator.cpp @@ -329,7 +329,7 @@ data_ptr_t BlockAllocator::AllocateData(const idx_t size) const { } void BlockAllocator::FreeData(const data_ptr_t pointer, const idx_t size) const { - deallocated_since_flush.fetch_add(size, std::memory_order_relaxed); + estimated_unflushed_memory.fetch_add(size, std::memory_order_relaxed); if (!IsActive() || !IsInPool(pointer)) { return allocator.FreeData(pointer, size); } @@ -368,7 +368,7 @@ void BlockAllocator::ThreadFlush(bool allocator_background_threads, idx_t thresh } void BlockAllocator::FlushAll(const optional_idx extra_memory) const { - deallocated_since_flush.store(0, std::memory_order_relaxed); + estimated_unflushed_memory.store(0, std::memory_order_relaxed); if (IsActive() && IsEnabled() && extra_memory.IsValid()) { FreeInternal(extra_memory.GetIndex()); } @@ -377,22 +377,35 @@ void BlockAllocator::FlushAll(const optional_idx extra_memory) const { } } -idx_t BlockAllocator::GetDeallocatedSinceFlush() const { - return deallocated_since_flush.load(std::memory_order_relaxed); +idx_t BlockAllocator::GetEstimatedUnflushedMemory() const { + return estimated_unflushed_memory.load(std::memory_order_relaxed); } -bool BlockAllocator::TryFlushDeallocated(const idx_t threshold) const { +bool BlockAllocator::TryFlushDeallocated(const idx_t threshold, const idx_t memory_limit, + const idx_t used_memory) const { if (!SupportsFlush()) { return false; } - auto deallocated = deallocated_since_flush.load(std::memory_order_relaxed); + auto deallocated = estimated_unflushed_memory.load(std::memory_order_relaxed); if (deallocated < threshold) { return false; } - // the thread that claims the accumulated amount performs the flush - if (!deallocated_since_flush.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { + // the thread that claims the accumulated amount checks whether a flush is needed + if (!estimated_unflushed_memory.compare_exchange_strong(deallocated, 0, std::memory_order_relaxed)) { return false; } + if (!IsActive()) { + const auto mapped_memory = allocator.GetMappedMemory(); + if (mapped_memory.IsValid()) { + // Freed pages cannot occupy more than the allocator's entire mapped footprint. + const auto unflushed = MinValue(deallocated, mapped_memory.GetIndex()); + if (used_memory + unflushed <= memory_limit) { + // Keep the upper bound in subsequent pressure checks, adding any concurrent frees. + estimated_unflushed_memory.fetch_add(unflushed, std::memory_order_relaxed); + return false; + } + } + } FlushAll(deallocated); return true; } diff --git a/src/duckdb/src/storage/buffer/buffer_pool.cpp b/src/duckdb/src/storage/buffer/buffer_pool.cpp index dc5e68a38..6106abb0a 100644 --- a/src/duckdb/src/storage/buffer/buffer_pool.cpp +++ b/src/duckdb/src/storage/buffer/buffer_pool.cpp @@ -586,7 +586,7 @@ void BufferPool::FlushAllocator(const optional_idx extra_memory) { void BufferPool::FlushOnBulkDeallocation() { const auto threshold = GetBulkDeallocationFlushThreshold(); - const auto deallocated = block_allocator.GetDeallocatedSinceFlush(); + const auto deallocated = block_allocator.GetEstimatedUnflushedMemory(); if (deallocated < threshold) { return; } @@ -595,7 +595,7 @@ void BufferPool::FlushOnBulkDeallocation() { if (used_memory + deallocated <= maximum_memory.load(std::memory_order_relaxed)) { return; } - block_allocator.TryFlushDeallocated(threshold); + block_allocator.TryFlushDeallocated(threshold, maximum_memory.load(std::memory_order_relaxed), used_memory); } vector BufferPool::GetEvictionQueueInfo() const { diff --git a/src/duckdb/src/storage/checkpoint_manager.cpp b/src/duckdb/src/storage/checkpoint_manager.cpp index 05574c6ce..08f6f4ede 100644 --- a/src/duckdb/src/storage/checkpoint_manager.cpp +++ b/src/duckdb/src/storage/checkpoint_manager.cpp @@ -613,7 +613,12 @@ void CheckpointWriter::WriteSequence(SequenceCatalogEntry &seq, Serializer &seri void CheckpointReader::ReadSequence(CatalogTransaction transaction, Deserializer &deserializer) { auto info = ReadCreateInfo(deserializer, CatalogType::SEQUENCE_ENTRY, "sequence"); auto &sequence_info = info->Cast(); - catalog.CreateSequence(transaction, sequence_info); + auto entry = catalog.CreateSequence(transaction, sequence_info); + auto &storage_manager = catalog.GetAttached().GetStorageManager(); + if (entry && storage_manager.HasStorageVersion() && + StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { + entry->Cast().DeriveLastValue(); + } } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst.cpp b/src/duckdb/src/storage/compression/dict_fsst.cpp index 0d9cd73f3..71d024ea1 100644 --- a/src/duckdb/src/storage/compression/dict_fsst.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst.cpp @@ -160,17 +160,15 @@ void DictFSSTCompressionStorage::StringFetchRow(ColumnSegment &segment, ColumnFe // Select //===--------------------------------------------------------------------===// void DictFSSTSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { auto &scan_state = state.scan_state->Cast(); - if (scan_state.mode == DictFSSTMode::FSST_ONLY) { - // for FSST only - auto start = state.GetPositionInSegment(); - scan_state.Select(result, start, sel, sel_count); + if (scan_state.mode != DictFSSTMode::FSST_ONLY && scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + // scan + slice, so that we can emit a dictionary vector + DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); + result.Slice(sel, sel_count); return; } - // fallback: scan + slice - DictFSSTCompressionStorage::StringScan(segment, state, vector_count, result); - result.Slice(sel, sel_count); + scan_state.Select(result, result_offset, state.GetPositionInSegment(), sel, sel_count); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp index a6e555a82..d07d8bbcb 100644 --- a/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp +++ b/src/duckdb/src/storage/compression/dict_fsst/decompression.cpp @@ -157,19 +157,25 @@ const SelectionVector &CompressedStringScanState::GetSelVec(idx_t start, idx_t s } void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_offset, idx_t start, idx_t scan_count) { - // Create a decompression buffer of sufficient size if we don't already have one. + Select(result, result_offset, start, *FlatVector::IncrementalSelectionVector(), scan_count); + result.Verify(); +} + +void CompressedStringScanState::Select(Vector &result, idx_t result_offset, idx_t start, const SelectionVector &sel, + idx_t sel_count) { + const auto scan_count = sel_count == 0 ? 0 : sel.get_index(sel_count - 1) + 1; auto &selvec = GetSelVec(start, scan_count); //! (index 0 is reserved for NULL, which we don't have in this mode) const idx_t start_offset = mode == DictFSSTMode::FSST_ONLY ? start + 1 : 0; - auto result_data = FlatVector::Writer(result, scan_count, result_offset); + auto result_data = FlatVector::Writer(result, sel_count, result_offset); if (dictionary) { // We have prepared the full dictionary, we can reference these strings directly auto dictionary_values = FlatVector::GetData(dictionary->data); - for (idx_t i = 0; i < scan_count; i++) { + for (idx_t i = 0; i < sel_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(i + start_offset); + auto string_number = selvec.get_index(start_offset + sel.get_index(i)); if (string_number == 0) { result_data.WriteNull(); continue; @@ -177,9 +183,9 @@ void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_of result_data.WriteStringRef(dictionary_values[string_number]); } } else { - for (idx_t i = 0; i < scan_count; i++) { + for (idx_t i = 0; i < sel_count; i++) { // Lookup dict offset in index buffer - auto string_number = selvec.get_index(start_offset + i); + auto string_number = selvec.get_index(start_offset + sel.get_index(i)); if (string_number == 0) { result_data.WriteNull(); continue; @@ -193,25 +199,6 @@ void CompressedStringScanState::ScanToFlatVector(Vector &result, idx_t result_of result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); } } - result.Verify(); -} - -void CompressedStringScanState::Select(Vector &result, idx_t start, const SelectionVector &sel, idx_t sel_count) { - D_ASSERT(!dictionary); - D_ASSERT(mode == DictFSSTMode::FSST_ONLY); - idx_t start_offset = start + 1; - auto result_data = FlatVector::Writer(result, sel_count); - for (idx_t i = 0; i < sel_count; i++) { - // Lookup dict offset in index buffer - auto string_number = start_offset + sel.get_index(i); - if (decompress_position > string_number) { - throw InternalException("DICT_FSST: not performing a sequential scan?"); - } - for (; decompress_position < string_number; decompress_position++) { - decompress_offset += string_lengths[decompress_position]; - } - result_data.WriteStringRef(FetchStringFromDict(result, decompress_offset, string_number)); - } } bool CompressedStringScanState::AllowDictionaryScan(idx_t scan_count) { diff --git a/src/duckdb/src/storage/compression/fsst.cpp b/src/duckdb/src/storage/compression/fsst.cpp index b30090418..c6c724e5a 100644 --- a/src/duckdb/src/storage/compression/fsst.cpp +++ b/src/duckdb/src/storage/compression/fsst.cpp @@ -55,7 +55,7 @@ struct FSSTStorage { static void StringFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row_id, Vector &result, idx_t result_idx); static void Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type); static void SetDictionary(ColumnSegment &segment, BufferHandle &handle, StringDictionaryContainer container); static StringDictionaryContainer GetDictionary(ColumnSegment &segment, BufferHandle &handle); @@ -768,7 +768,7 @@ void FSSTStorage::StringScan(ColumnSegment &segment, ColumnScanState &state, idx // Select //===--------------------------------------------------------------------===// void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { auto &scan_state = state.scan_state->Cast(); auto start = state.GetPositionInSegment(); D_ASSERT(start <= segment.count.load()); @@ -783,7 +783,7 @@ void FSSTStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t v for (idx_t i = 0; i < sel_count; i++) { idx_t index = sel.get_index(i); D_ASSERT(index < vector_count); - result_data[i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); + result_data[result_offset + i] = scan_state.DecompressString(strings.GetCompressedString(index), str_allocator); } } diff --git a/src/duckdb/src/storage/compression/numeric_constant.cpp b/src/duckdb/src/storage/compression/numeric_constant.cpp index 94d751c24..fa9da5fb2 100644 --- a/src/duckdb/src/storage/compression/numeric_constant.cpp +++ b/src/duckdb/src/storage/compression/numeric_constant.cpp @@ -251,14 +251,23 @@ void ConstantFetchRow(ColumnSegment &segment, ColumnFetchState &state, row_t row // Select //===--------------------------------------------------------------------===// void ConstantSelectValidity(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { - ConstantScanFunctionValidity(segment, state, sel_count, result); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType scan_type) { + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + ConstantScanFunctionValidity(segment, state, sel_count, result); + return; + } + ConstantScanPartialValidity(segment, state, sel_count, result, result_offset); } template void ConstantSelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { - ConstantScanFunction(segment, state, vector_count, result); + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + ConstantScanFunction(segment, state, vector_count, result); + return; + } + ConstantScanPartial(segment, state, sel_count, result, result_offset); } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/storage/compression/rle.cpp b/src/duckdb/src/storage/compression/rle.cpp index e5e0df6e2..bbdbbc0b0 100644 --- a/src/duckdb/src/storage/compression/rle.cpp +++ b/src/duckdb/src/storage/compression/rle.cpp @@ -519,19 +519,21 @@ void RLEScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_count, V //===--------------------------------------------------------------------===// template void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { auto &scan_state = state.scan_state->Cast>(); // If we are scanning an entire Vector and it contains only a single run we don't need to select at all auto current_run = scan_state.ValidateAndGetCurrentRun(); - if (scan_state.position_in_entry < current_run.length && + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR && scan_state.position_in_entry < current_run.length && CanEmitConstantVector(scan_state.position_in_entry, current_run.length, vector_count)) { RLEScanConstant(scan_state, current_run, vector_count, result); return; } - auto result_data = FlatVector::Writer(result, sel_count); + auto result_data = FlatVector::Writer(result, sel_count, result_offset); + // Keep the current run until the selection advances past it. + idx_t run_end = current_run.length - scan_state.position_in_entry; idx_t prev_idx = 0; for (idx_t i = 0; i < sel_count; i++) { auto next_idx = sel.get_index(i); @@ -539,14 +541,16 @@ void RLESelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_coun throw InternalException("Error in RLESelect - selection vector indices are not ordered"); } D_ASSERT(next_idx < vector_count); - // skip forward to the next index - scan_state.SkipInternal(next_idx - prev_idx); - // read the element - result_data.WriteValue(scan_state.ValidateAndGetCurrentRun().value); - // move the next to the prev + while (next_idx >= run_end) { + scan_state.ForwardToNextRun(); + current_run = scan_state.ValidateAndGetCurrentRun(); + run_end += current_run.length; + } + result_data.WriteValue(current_run.value); prev_idx = next_idx; } - // skip the tail + // Restore the position at the last selected row before skipping the tail. + scan_state.position_in_entry = current_run.length - (run_end - prev_idx); scan_state.SkipInternal(vector_count - prev_idx); } diff --git a/src/duckdb/src/storage/compression/string_uncompressed.cpp b/src/duckdb/src/storage/compression/string_uncompressed.cpp index 7348b0934..9abdb7f15 100644 --- a/src/duckdb/src/storage/compression/string_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/string_uncompressed.cpp @@ -158,7 +158,8 @@ void UncompressedStringStorage::StringScan(ColumnSegment &segment, ColumnScanSta // Select //===--------------------------------------------------------------------===// void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, - Vector &result, const SelectionVector &sel, idx_t sel_count) { + Vector &result, const SelectionVector &sel, idx_t sel_count, idx_t result_offset, + ScanVectorType) { // clear any previously locked buffers and get the primary buffer handle auto &scan_state = state.scan_state->Cast(); auto layout = StringSegmentLayout::Read(scan_state.handle, segment); @@ -174,7 +175,7 @@ void UncompressedStringStorage::Select(ColumnSegment &segment, ColumnScanState & auto selection_index = sel.get_index(i); D_ASSERT(selection_index < vector_count); auto entry = strings.GetEntry(selection_index); - result_data[i] = FetchStringFromEntry(state.context, segment, result, entry); + result_data[result_offset + i] = FetchStringFromEntry(state.context, segment, result, entry); } } diff --git a/src/duckdb/src/storage/compression/validity_uncompressed.cpp b/src/duckdb/src/storage/compression/validity_uncompressed.cpp index a9778897b..6ab8fbe5b 100644 --- a/src/duckdb/src/storage/compression/validity_uncompressed.cpp +++ b/src/duckdb/src/storage/compression/validity_uncompressed.cpp @@ -520,7 +520,7 @@ void ValidityScan(ColumnSegment &segment, ColumnScanState &state, idx_t scan_cou // Select //===--------------------------------------------------------------------===// void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector_count, Vector &result, - const SelectionVector &sel, idx_t sel_count) { + const SelectionVector &sel, idx_t sel_count, idx_t result_offset, ScanVectorType) { auto start = state.GetPositionInSegment(); if (sel_count == 0) { return; @@ -528,18 +528,21 @@ void ValiditySelect(ColumnSegment &segment, ColumnScanState &state, idx_t vector D_ASSERT(start <= segment.count && vector_count <= segment.count - start); D_ASSERT(sel_count <= vector_count); - result.Flatten(); + if (result_offset == 0) { + result.Flatten(); + } auto &scan_state = state.scan_state->Cast(); + auto input_data = ValidityScanData(scan_state.reader, start, vector_count); + auto input_start = start % ValidityMask::BITS_PER_VALUE; auto &result_mask = FlatVector::ValidityMutable(result); for (idx_t i = 0; i < sel_count; i++) { auto selected_index = sel.get_index(i); D_ASSERT(selected_index < vector_count); - auto source_idx = start + selected_index; - auto entry_offset = source_idx / ValidityMask::BITS_PER_VALUE * sizeof(validity_t); - auto entry = scan_state.reader.Get(entry_offset); + auto source_idx = input_start + selected_index; + auto entry = input_data[source_idx / ValidityMask::BITS_PER_VALUE]; if (!ValidityMask::RowIsValid(entry, source_idx % ValidityMask::BITS_PER_VALUE)) { - result_mask.SetInvalid(i); + result_mask.SetInvalid(result_offset + i); } } } @@ -638,12 +641,14 @@ void ValidityRevertAppend(ColumnSegment &segment, idx_t new_count) { //===--------------------------------------------------------------------===// CompressionFunction ValidityUncompressed::GetFunction(PhysicalType data_type) { D_ASSERT(data_type == PhysicalType::BIT); - return CompressionFunction(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, + CompressionFunction result(CompressionType::COMPRESSION_UNCOMPRESSED, data_type, ValidityInitAnalyze, ValidityAnalyze, ValidityFinalAnalyze, UncompressedFunctions::InitCompression, UncompressedFunctions::Compress, UncompressedFunctions::FinalizeCompress, ValidityInitScan, ValidityScan, ValidityScanPartial, ValidityFetchRow, UncompressedFunctions::EmptySkip, ValidityInitSegment, ValidityInitAppend, ValidityAppend, ValidityFinalizeAppend, ValidityRevertAppend); + result.select = ValiditySelect; + return result; } } // namespace duckdb diff --git a/src/duckdb/src/storage/data_table.cpp b/src/duckdb/src/storage/data_table.cpp index a7884c327..2f9cec660 100644 --- a/src/duckdb/src/storage/data_table.cpp +++ b/src/duckdb/src/storage/data_table.cpp @@ -902,6 +902,8 @@ void DataTable::InitializeLocalStorage(LocalAppendState &state, DuckTableEntry & GetTableName(), TableModification()); } + // The transaction-local indexes are created with the local storage, and only for indexes that are bound + info->BindIndexes(context); auto &local_storage = LocalStorage::Get(context, db); local_storage.InitializeStorage(state, *this, table); state.constraint_state = InitializeConstraintState(table, bound_constraints); diff --git a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp index 5d9dbd3cb..3c9b26937 100644 --- a/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp +++ b/src/duckdb/src/storage/external_file_cache/caching_file_system.cpp @@ -616,6 +616,12 @@ bool CachingFileHandle::TryGetNetworkThroughput(NetworkThroughputEstimate &resul void CachingFileHandle::ReadAndRecord(QueryContext context, data_ptr_t buffer, idx_t nr_bytes, idx_t location) { auto handle = GetFileHandle(); + if (handle->file_system.IsLocalFileSystem()) { + // Pre-fault local read buffers before the kernel fills them. + for (idx_t offset = 0; offset < nr_bytes; offset += 4096) { + buffer[offset] = 0; + } + } const auto read_start = steady_clock::now(); handle->Read(context, buffer, nr_bytes, location); auto cache_valid_until = handle->file_system.GetCacheValidUntil(*handle); diff --git a/src/duckdb/src/storage/statistics/numeric_stats.cpp b/src/duckdb/src/storage/statistics/numeric_stats.cpp index ab56e0446..14640c6a1 100644 --- a/src/duckdb/src/storage/statistics/numeric_stats.cpp +++ b/src/duckdb/src/storage/statistics/numeric_stats.cpp @@ -31,6 +31,25 @@ BaseStatistics NumericStats::CreateUnknown(LogicalType type) { return result; } +//! The type whose limits are used for the min/max of empty statistics +static LogicalType GetEmptyStatsLimitType(const LogicalType &type) { + if (type.id() != LogicalTypeId::ENUM) { + return type; + } + // the dictionary of an ENUM can be empty or have a single entry: use the limits of the index type instead, so + // that the min is larger than the max until a value is added + switch (type.InternalType()) { + case PhysicalType::UINT8: + return LogicalType::UTINYINT; + case PhysicalType::UINT16: + return LogicalType::USMALLINT; + case PhysicalType::UINT32: + return LogicalType::UINTEGER; + default: + throw InternalException("Invalid Internal Type for ENUMs"); + } +} + BaseStatistics NumericStats::CreateEmpty(LogicalType type) { BaseStatistics result(std::move(type)); result.InitializeEmpty(); @@ -41,11 +60,13 @@ BaseStatistics NumericStats::CreateEmpty(LogicalType type) { case PhysicalType::DOUBLE: InitializeEmptyFloatingPointStats(result); break; - default: - SetMin(result, Value::MaximumValue(result.GetType())); - SetMax(result, Value::MinimumValue(result.GetType())); + default: { + auto limit_type = GetEmptyStatsLimitType(result.GetType()); + SetMin(result, Value::MaximumValue(limit_type)); + SetMax(result, Value::MinimumValue(limit_type)); break; } + } return result; } diff --git a/src/duckdb/src/storage/statistics/variant_stats.cpp b/src/duckdb/src/storage/statistics/variant_stats.cpp index ddc90feaf..5440fb2d3 100644 --- a/src/duckdb/src/storage/statistics/variant_stats.cpp +++ b/src/duckdb/src/storage/statistics/variant_stats.cpp @@ -696,8 +696,8 @@ bool VariantStats::MergeShredding(const BaseStatistics &stats, const BaseStatist return true; } else { D_ASSERT(!stats_typed_value_type.IsNested()); - if (stats_typed_value_type.id() != other_typed_value_type.id()) { - //! other is not the same type, can't merge + if (stats_typed_value_type != other_typed_value_type) { + //! other is not the same type (e.g. a DECIMAL of a different width or scale), can't merge return false; } auto new_typed_stats = stats_typed_value.Copy(); diff --git a/src/duckdb/src/storage/table/column_data.cpp b/src/duckdb/src/storage/table/column_data.cpp index 66f3d82e0..c820ee4f8 100644 --- a/src/duckdb/src/storage/table/column_data.cpp +++ b/src/duckdb/src/storage/table/column_data.cpp @@ -262,24 +262,71 @@ idx_t ColumnData::ScanVector(ColumnScanState &state, Vector &result, idx_t remai } void ColumnData::SelectVector(ColumnScanState &state, Vector &result, idx_t target_count, const SelectionVector &sel, - idx_t sel_count) { + idx_t sel_count, ScanVectorType scan_type) { BeginScanVectorInternal(state); - auto ¤t = state.current->GetNode(); - if (state.current->GetRowStart() + current.count - state.offset_in_column < target_count) { - throw InternalException("ColumnData::SelectVector should be able to fetch everything from one segment"); - } - if (state.scan_options && state.scan_options->force_fetch_row) { - auto start = state.GetPositionInSegment(); - for (idx_t i = 0; i < sel_count; i++) { - auto source_idx = sel.get_index(i); - ColumnFetchState fetch_state; - current.FetchRow(fetch_state, UnsafeNumericCast(start + source_idx), result, i); + // scratch space to rebase the selection vector to the start of a segment scan + // this owns its data, as compression functions can retain the selection vector (e.g. by slicing the result) + SelectionVector rebased_sel; + idx_t sel_offset = 0; + idx_t scanned_count = 0; + // the position up to which the scan state of the current segment has been advanced + // segments without any selected rows are not scanned, so they must be skipped in the next scan + idx_t segment_internal_index = state.offset_in_column; + while (scanned_count < target_count) { + auto ¤t = state.current->GetNode(); + auto current_start = state.current->GetRowStart(); + idx_t scan_count = + MinValue(target_count - scanned_count, current_start + current.count - state.offset_in_column); + segment_internal_index = state.offset_in_column; + // gather the selected rows that fall within this segment + idx_t segment_sel_count = 0; + while (sel_offset + segment_sel_count < sel_count && + sel.get_index(sel_offset + segment_sel_count) < scanned_count + scan_count) { + segment_sel_count++; + } + if (segment_sel_count > 0) { + // the indices must be relative to the start of the segment scan - for the first segment this is + // already the case, so we can forward the selection vector of the caller directly + reference segment_sel(sel); + if (scanned_count > 0) { + if (!rebased_sel.IsSet()) { + rebased_sel.Initialize(); + } + for (idx_t i = 0; i < segment_sel_count; i++) { + rebased_sel.set_index(i, sel.get_index(sel_offset + i) - scanned_count); + } + segment_sel = rebased_sel; + } + if (state.scan_options && state.scan_options->force_fetch_row) { + for (idx_t i = 0; i < segment_sel_count; i++) { + ColumnFetchState fetch_state; + auto source_idx = state.offset_in_column + segment_sel.get().get_index(i) - current_start; + current.FetchRow(fetch_state, UnsafeNumericCast(source_idx), result, sel_offset + i); + } + } else { + current.Select(state, scan_count, result, segment_sel.get(), segment_sel_count, sel_offset, scan_type); + } + segment_internal_index = state.offset_in_column + scan_count; + } + sel_offset += segment_sel_count; + scanned_count += scan_count; + state.offset_in_column += scan_count; + if (scanned_count < target_count) { + auto next = data.GetNextSegment(*state.current); + if (!next) { + break; + } + state.previous_states.emplace_back(std::move(state.scan_state)); + state.current = next; + state.current->GetNode().InitializeScan(state); + state.segment_checked = false; } - } else { - current.Select(state, target_count, result, sel, sel_count); } - state.offset_in_column += target_count; - state.internal_index = state.offset_in_column; + if (scan_type == ScanVectorType::SCAN_FLAT_VECTOR) { + // not every compression function sets the size of the result + FlatVector::SetSize(result, count_t(sel_count)); + } + state.internal_index = segment_internal_index; } void ColumnData::FilterVector(ColumnScanState &state, Vector &result, idx_t target_count, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/column_segment.cpp b/src/duckdb/src/storage/table/column_segment.cpp index 36f1f2f1b..641f4deef 100644 --- a/src/duckdb/src/storage/table/column_segment.cpp +++ b/src/duckdb/src/storage/table/column_segment.cpp @@ -151,11 +151,16 @@ void ColumnSegment::Scan(ColumnScanState &state, idx_t scan_count, Vector &resul } void ColumnSegment::Select(ColumnScanState &state, idx_t scan_count, Vector &result, const SelectionVector &sel, - idx_t sel_count) { + idx_t sel_count, idx_t result_offset, ScanVectorType scan_type) { if (!function.get().select) { throw InternalException("ColumnSegment::Select not implemented for this compression method"); } - function.get().select(*this, state, scan_count, result, sel, sel_count); + if (scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR) { + D_ASSERT(result_offset == 0); + } else { + D_ASSERT(result.GetVectorType() == VectorType::FLAT_VECTOR); + } + function.get().select(*this, state, scan_count, result, sel, sel_count, result_offset, scan_type); } void ColumnSegment::Filter(ColumnScanState &state, idx_t scan_count, Vector &result, SelectionVector &sel, diff --git a/src/duckdb/src/storage/table/standard_column_data.cpp b/src/duckdb/src/storage/table/standard_column_data.cpp index 734db61b9..ac2fa3ed2 100644 --- a/src/duckdb/src/storage/table/standard_column_data.cpp +++ b/src/duckdb/src/storage/table/standard_column_data.cpp @@ -113,14 +113,13 @@ void StandardColumnData::Select(TransactionData transaction, idx_t vector_index, bool validity_has_select = validity_compression && validity_compression->select; auto target_count = GetVectorCount(vector_index); auto scan_type = GetVectorScanType(state, target_count, result); - bool scan_entire_vector = scan_type == ScanVectorType::SCAN_ENTIRE_VECTOR; - if (!has_select || !validity_has_select || !scan_entire_vector) { - // we are not scanning an entire vector - this can have several causes (updates, etc) + if (!has_select || !validity_has_select || HasUpdates() || validity->HasUpdates()) { + // the updates need to be merged into the entire vector, so we cannot select ColumnData::Select(transaction, vector_index, state, result, sel, sel_count); return; } - SelectVector(state, result, target_count, sel, sel_count); - validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count); + SelectVector(state, result, target_count, sel, sel_count, scan_type); + validity->SelectVector(state.child_states[0], result, target_count, sel, sel_count, scan_type); } void StandardColumnData::InitializeAppend(ColumnAppendState &state) { diff --git a/src/duckdb/src/storage/wal_replay.cpp b/src/duckdb/src/storage/wal_replay.cpp index 54611faa2..fa1b476ca 100644 --- a/src/duckdb/src/storage/wal_replay.cpp +++ b/src/duckdb/src/storage/wal_replay.cpp @@ -1176,6 +1176,11 @@ void WriteAheadLogDeserializer::ReplaySequenceValue() { // fetch the sequence from the catalog auto &seq = catalog.GetEntry(context, ReplayEntryName(catalog, entry.qualified_name)); seq.ReplayValue(entry.usage_count, entry.counter, entry.last_value); + auto &storage_manager = db.GetStorageManager(); + if (storage_manager.HasStorageVersion() && + StorageManager::IsPriorToVersion(StorageVersion::V2_0_0, storage_manager.GetStorageVersion())) { + seq.DeriveLastValue(); + } } //===--------------------------------------------------------------------===// diff --git a/src/duckdb/src/transaction/meta_transaction.cpp b/src/duckdb/src/transaction/meta_transaction.cpp index b3cf2b9e2..d74a02b47 100644 --- a/src/duckdb/src/transaction/meta_transaction.cpp +++ b/src/duckdb/src/transaction/meta_transaction.cpp @@ -245,9 +245,10 @@ AttachedDatabase &MetaTransaction::UseDatabase(shared_ptr &dat if (entry == referenced_databases.end()) { auto used_entry = used_databases.emplace(db_ref.GetName(), db_ref); if (!used_entry.second) { - // return used_entry.first->second.get(); - throw InternalException( - "Database name %s was already used by a different database for this meta transaction", + // e.g. a database was detached and another one attached under the same name in this transaction + throw TransactionException( + "Database name %s was already used by a different database in this transaction - commit or roll back " + "the transaction before using the name for another database", db_ref.GetName()); } referenced_databases.emplace(reference(db_ref), database); diff --git a/src/duckdb/third_party/httplib/httplib.hpp b/src/duckdb/third_party/httplib/httplib.hpp index ae0a35db6..5675d7611 100644 --- a/src/duckdb/third_party/httplib/httplib.hpp +++ b/src/duckdb/third_party/httplib/httplib.hpp @@ -194,14 +194,6 @@ #define CPPHTTPLIB_RECV_FLAGS 0 #endif -#ifndef CPPHTTPLIB_SEND_FLAGS -#ifdef MSG_NOSIGNAL -#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL -#else -#define CPPHTTPLIB_SEND_FLAGS 0 -#endif -#endif - #ifndef CPPHTTPLIB_LISTEN_BACKLOG #define CPPHTTPLIB_LISTEN_BACKLOG 128 #endif @@ -329,6 +321,14 @@ using socket_t = int; #endif #endif //_WIN32 +#ifndef CPPHTTPLIB_SEND_FLAGS +#ifdef MSG_NOSIGNAL +#define CPPHTTPLIB_SEND_FLAGS MSG_NOSIGNAL +#else +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif +#endif + #if defined(__APPLE__) #include #endif diff --git a/src/duckdb/third_party/ska_sort/ska_sort.hpp b/src/duckdb/third_party/ska_sort/ska_sort.hpp index 3296a8c1b..5347ea7d7 100644 --- a/src/duckdb/third_party/ska_sort/ska_sort.hpp +++ b/src/duckdb/third_party/ska_sort/ska_sort.hpp @@ -1070,25 +1070,39 @@ struct TypeAt<0, First, More...> // { // }; -template -inline void StdSortFallback(It begin, It end, ExtractKey & extract_key) -{ - // LNK note that we use the full comparison (not just extracted key) here - static const auto comp = [&](const typename std::remove_reference::type & l, const typename std::remove_reference::type & r){ return l < r; }; - static const auto fallback = [&](const It &fb_begin, const It &fb_end) { - duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); - }; - duckdb_vergesort::vergesort(begin, end, comp, fallback); +// LNK note that we use the full comparison (not just extracted key) here +template +struct FullComparison +{ + typedef typename std::remove_reference())>::type value_type; + bool operator()(const value_type & l, const value_type & r) const + { + return l < r; + } +}; + +// Comparison sort of a radix bucket whose extracted keys are all equal: vergesort over the full comparison, with +// the sorted runs it finds merged by the caller's merge policy (see duckdb_vergesort::inplace_merge_policy), so +// that the merge scratch memory of the nested sort is the one the caller controls +template +inline void StdSortFallback(It begin, It end, ExtractKey &, const Merge & merge) +{ + FullComparison comp; + const auto fallback = [&](const It &fb_begin, const It &fb_end) { + duckdb_pdqsort::pdqsort_branchless(fb_begin, fb_end, comp); + }; + duckdb_vergesort::vergesort(begin, end, comp, fallback, merge); } template -inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey & extract_key) +inline bool StdSortIfLessThanThreshold(It begin, It end, size_t num_elements, ExtractKey &) { if (num_elements <= 1) return true; if (num_elements >= StdSortThreshold) return false; - StdSortFallback(begin, end, extract_key); + // below vergesort's minimum size (128), where it would hand the whole range to pdqsort without merging anything + duckdb_pdqsort::pdqsort_branchless(begin, end, FullComparison()); return true; } @@ -1422,8 +1436,8 @@ struct SortStarter> template struct SortStarter { - template - static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, void * next_sort_data = nullptr) + template + static void sort(It begin, It end, size_t num_elements, ExtractKey & extract_key, const Merge & merge) { if (StdSortIfLessThanThreshold(begin, end, num_elements, extract_key)) return; @@ -1433,22 +1447,26 @@ struct SortStarter // next_sort = nullptr; // LNK: different templating for DuckDB + // The sort data pointer that the radix passes carry along is the merge policy for the comparison sort of the + // buckets that the radix passes cannot order (the sub keys of DuckDB's sort keys never look at it) void (*next_sort)(It, It, size_t, ExtractKey &, void *) = nullptr; + void * next_sort_data = nullptr; if (extract_key.requires_next_sort) { - next_sort = [](It b, It e, size_t, ExtractKey& ek, void*) { - StdSortFallback(b, e, ek); + next_sort = [](It b, It e, size_t, ExtractKey& ek, void* merge_policy) { + StdSortFallback(b, e, ek, *static_cast(merge_policy)); }; + next_sort_data = const_cast(static_cast(&merge)); } InplaceSorter::sort(begin, end, num_elements, extract_key, next_sort, next_sort_data); } }; -template -void inplace_radix_sort(It begin, It end, ExtractKey & extract_key) +template +void inplace_radix_sort(It begin, It end, ExtractKey & extract_key, const Merge & merge) { typedef SubKey SubKeyType; - SortStarter::sort(begin, end, end - begin, extract_key); + SortStarter::sort(begin, end, end - begin, extract_key, merge); } struct IdentityFunctor @@ -1461,10 +1479,18 @@ struct IdentityFunctor }; } +// Sorts [begin, end) by the radix of extract_key; buckets whose extracted keys are equal (when extract_key requires +// a next sort) are sorted by the full comparison with vergesort, whose run merges go through the given merge policy +template +static void ska_sort(It begin, It end, ExtractKey && extract_key, const Merge & merge) +{ + detail::inplace_radix_sort<128, 1024>(begin, end, extract_key, merge); +} + template static void ska_sort(It begin, It end, ExtractKey && extract_key) { - detail::inplace_radix_sort<128, 1024>(begin, end, extract_key); + ska_sort(begin, end, extract_key, duckdb_vergesort::inplace_merge_policy()); } template diff --git a/src/duckdb/third_party/vergesort/vergesort.h b/src/duckdb/third_party/vergesort/vergesort.h index 1d7275318..e1322da65 100644 --- a/src/duckdb/third_party/vergesort/vergesort.h +++ b/src/duckdb/third_party/vergesort/vergesort.h @@ -47,6 +47,19 @@ namespace duckdb_vergesort { + // Merge policy used for the final pairwise run merges: a callable merge(first, middle, last, compare) that + // merges the sorted ranges [first, middle) and [middle, last). The default is std::inplace_merge, which takes + // its temporary buffer from operator new; callers that want to control that memory pass their own policy. + struct inplace_merge_policy + { + template + void operator()(RandomAccessIterator first, RandomAccessIterator middle, + RandomAccessIterator last, Compare compare) const + { + std::inplace_merge(first, middle, last, compare); + } + }; + namespace detail { // In-place merge where [first, middle1), [middle1, middle2) @@ -178,9 +191,9 @@ namespace duckdb_vergesort } } - template + template void vergesort(RandomAccessIterator first, RandomAccessIterator last, - Compare compare, std::random_access_iterator_tag, Fallback fallback) + Compare compare, std::random_access_iterator_tag, Fallback fallback, Merge merge) { typedef typename std::iterator_traits::difference_type difference_type; difference_type dist = last - first; @@ -321,7 +334,7 @@ namespace duckdb_vergesort for (typename std::list::iterator it = runs.begin() ; it != runs.end() && it != detail::prev(runs.end()) ; ++it) { - std::inplace_merge(begin, *it, *detail::next(it), compare); + merge(begin, *it, *detail::next(it), compare); // Remove the middle iterator and advance it = runs.erase(it); @@ -331,12 +344,19 @@ namespace duckdb_vergesort } } + template + void vergesort(BidirectionalIterator first, BidirectionalIterator last, + Compare compare, Fallback fallback, Merge merge) + { + typedef typename std::iterator_traits::iterator_category category; + detail::vergesort(first, last, compare, category(), fallback, merge); + } + template void vergesort(BidirectionalIterator first, BidirectionalIterator last, Compare compare, Fallback fallback) { - typedef typename std::iterator_traits::iterator_category category; - detail::vergesort(first, last, compare, category(), fallback); + vergesort(first, last, compare, fallback, inplace_merge_policy()); } template From a49342f12c87088b79f8b51ae1491a16e29772de Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Thu, 15 Oct 2026 22:13:35 +0000 Subject: [PATCH 06/13] Update vendored DuckDB sources to c457670e673 --- src/duckdb/generated_extension_loader_package_build.cpp | 8 ++++---- src/duckdb/src/function/table/version/pragma_version.cpp | 6 +++--- .../src/planner/binder/query_node/bind_select_node.cpp | 7 +++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index 39677e583..d62fdd99c 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48178"; + descriptor->api_version = "v2.0.0-alpha48141"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48178"; + descriptor->api_version = "v2.0.0-alpha48141"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48178"; + descriptor->api_version = "v2.0.0-alpha48141"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48178"; + descriptor->api_version = "v2.0.0-alpha48141"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 22461be92..9020fe4a9 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48178" +#define DUCKDB_PATCH_VERSION "0-alpha48141" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48178" +#define DUCKDB_VERSION "v2.0.0-alpha48141" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "57d34370c8" +#define DUCKDB_SOURCE_ID "c457670e67" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp index 49843c812..27ce506ff 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp @@ -725,6 +725,9 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from } bind_state.AddRegularColumn(); + if (can_group_by_all && is_window) { + throw BinderException("Cannot group on a window clause"); + } if (can_group_by_all && result.unnests.SelectList().size() > unnest_count) { throw BinderException("Cannot group on an UNNEST or UNLIST clause"); } @@ -732,10 +735,6 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from if (select_binder.BoundAggregates()) { throw BinderException("Cannot mix aggregates with non-aggregated columns!"); } - if (is_window) { - // a window over aggregates (e.g. sum(sum(x)) OVER ()) is fine, a window over a group is not - throw BinderException("Cannot group on a window clause"); - } // we are forcing aggregates, and the node has columns bound // this entry becomes a group group_by_all_indexes.push_back(i); From 2b17f5c1f9cfc918bc11a51bc0c000f77e33e5dd Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Fri, 16 Oct 2026 02:27:43 +0000 Subject: [PATCH 07/13] Update vendored DuckDB sources to 9a976423496 --- ...nerated_extension_loader_package_build.cpp | 8 ++--- .../function/table/version/pragma_version.cpp | 6 ++-- .../function/variant/variant_shredding.cpp | 6 ++-- .../include/duckdb/main/extension_entries.hpp | 3 +- .../duckdb/main/secret/default_secrets.hpp | 3 ++ .../src/include/duckdb/main/secret/secret.hpp | 11 +++--- .../include/duckdb/parser/parser_options.hpp | 2 ++ src/duckdb/src/main/client_context.cpp | 1 + .../src/main/secret/default_secrets.cpp | 8 +++-- src/duckdb/src/main/secret/secret.cpp | 35 +++++++++++++++++++ src/duckdb/src/main/secret/secret_manager.cpp | 13 +++++-- .../pushdown/pushdown_projection.cpp | 24 +++++++++---- .../parser/peg/transformer/transform_copy.cpp | 9 +++-- .../peg/transformer/transform_pragma.cpp | 7 ++++ .../binder/query_node/bind_select_node.cpp | 7 ++-- 15 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index d62fdd99c..b7fd05777 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48141"; + descriptor->api_version = "v2.0.0-alpha48444"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48141"; + descriptor->api_version = "v2.0.0-alpha48444"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48141"; + descriptor->api_version = "v2.0.0-alpha48444"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48141"; + descriptor->api_version = "v2.0.0-alpha48444"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 9020fe4a9..68a2c3236 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48141" +#define DUCKDB_PATCH_VERSION "0-alpha48444" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48141" +#define DUCKDB_VERSION "v2.0.0-alpha48444" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "c457670e67" +#define DUCKDB_SOURCE_ID "9a97642349" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/variant/variant_shredding.cpp b/src/duckdb/src/function/variant/variant_shredding.cpp index ea748f388..f3f511ff7 100644 --- a/src/duckdb/src/function/variant/variant_shredding.cpp +++ b/src/duckdb/src/function/variant/variant_shredding.cpp @@ -343,10 +343,10 @@ bool VariantShreddingState::ValueIsShredded(UnifiedVariantVectorData &variant, i return false; } if (type_id == VariantLogicalType::DECIMAL) { - auto physical_type = type.InternalType(); + // the shredded column stores the unscaled value, so it only holds decimals of its scale that fit its width auto decimal_data = VariantUtils::DecodeDecimalData(variant, row, values_index); - auto decimal_physical_type = decimal_data.GetPhysicalType(); - return physical_type == decimal_physical_type; + return type.InternalType() == decimal_data.GetPhysicalType() && + decimal_data.scale == DecimalType::GetScale(type) && decimal_data.width <= DecimalType::GetWidth(type); } return true; } diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index 0671844d9..53bbe9c7b 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -735,6 +735,7 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"quack_check_token", "quack", CatalogType::SCALAR_FUNCTION_ENTRY}, {"quack_clear_cache", "quack", CatalogType::TABLE_FUNCTION_ENTRY}, {"quack_connection_id", "quack", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"quack_generate_keys", "quack", CatalogType::TABLE_FUNCTION_ENTRY}, {"quack_identify", "quack", CatalogType::TABLE_FUNCTION_ENTRY}, {"quack_nop_authorization", "quack", CatalogType::SCALAR_FUNCTION_ENTRY}, {"quack_query", "quack", CatalogType::TABLE_FUNCTION_ENTRY}, @@ -1414,7 +1415,6 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"quack_default_client_id", "quack"}, {"quack_default_heartbeat_timeout", "quack"}, {"quack_enable_reconnects", "quack"}, - {"quack_fetch_producer_buffer_bytes", "quack"}, {"quack_fetch_read_ahead", "quack"}, {"quack_loaded_at_us", "quack"}, {"quack_prepare_inline_rows", "quack"}, @@ -1422,6 +1422,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"quack_result_ttl", "quack"}, {"quack_server_keep_alive_timeout", "quack"}, {"quack_server_max_connections", "quack"}, + {"quack_server_stream_buffer_bytes", "quack"}, {"quack_target_batch_bytes", "quack"}, {"rtree_index_scan_min_rows", "spatial"}, {"rtree_index_scan_ratio", "spatial"}, diff --git a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp index 99a81cbb6..010b1afe9 100644 --- a/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/default_secrets.hpp @@ -9,6 +9,7 @@ #pragma once #include "duckdb/common/common.hpp" +#include "duckdb/common/identifier.hpp" namespace duckdb { class DatabaseInstance; @@ -25,6 +26,8 @@ struct CreateHTTPSecretFunctions { static vector GetDefaultSecretTypes(); //! Get the default secret functions static vector GetDefaultSecretFunctions(); + //! Current sensitive keys for both new and persisted HTTP secrets. + static identifier_set_t GetRedactedKeys(); protected: //! HTTP secret CONFIG provider diff --git a/src/duckdb/src/include/duckdb/main/secret/secret.hpp b/src/duckdb/src/include/duckdb/main/secret/secret.hpp index 6183a6e5c..7299301da 100644 --- a/src/duckdb/src/include/duckdb/main/secret/secret.hpp +++ b/src/duckdb/src/include/duckdb/main/secret/secret.hpp @@ -217,13 +217,7 @@ class KeyValueSecret : public BaseSecret { Deserialize(Deserializer &deserializer, BaseSecret base_secret) { // NOLINT(performance-unnecessary-value-param): callback signature. auto result = make_uniq(base_secret); - Value secret_map_value; - deserializer.ReadProperty(201, "secret_map", secret_map_value); - - for (const auto &entry : ListValue::GetChildren(secret_map_value)) { - auto kv_struct = StructValue::GetChildren(entry); - result->secret_map[Identifier(kv_struct[0].ToString())] = kv_struct[1]; - } + ReadSecretMap(deserializer, result->secret_map); Value redact_set_value; deserializer.ReadProperty(202, "redact_keys", redact_set_value); @@ -261,6 +255,9 @@ class KeyValueSecret : public BaseSecret { identifier_tree_t secret_map; //! keys that are sensitive and should be redacted identifier_set_t redact_keys; + +private: + static void ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result); }; // Helper class to fetch secret parameters in a cascading way. The idea being that in many cases there is a direct diff --git a/src/duckdb/src/include/duckdb/parser/parser_options.hpp b/src/duckdb/src/include/duckdb/parser/parser_options.hpp index c36fc63fc..65bb77f87 100644 --- a/src/duckdb/src/include/duckdb/parser/parser_options.hpp +++ b/src/duckdb/src/include/duckdb/parser/parser_options.hpp @@ -38,6 +38,8 @@ struct ParserOptions { optional_ptr extensions; AllowParserOverride parser_override_setting = AllowParserOverride::DEFAULT_OVERRIDE; shared_ptr compiled_grammar; + //! The client context the statement is parsed for (not set when parsing without a client context) + optional_ptr context; }; } // namespace duckdb diff --git a/src/duckdb/src/main/client_context.cpp b/src/duckdb/src/main/client_context.cpp index 62ae13551..1631051fd 100644 --- a/src/duckdb/src/main/client_context.cpp +++ b/src/duckdb/src/main/client_context.cpp @@ -1478,6 +1478,7 @@ ParserOptions ClientContext::GetParserOptions() { options.extensions = DBConfig::GetConfig(*this).GetCallbackManager(); options.parser_override_setting = Settings::Get(*this); options.compiled_grammar = CompiledGrammar::Get(*this); + options.context = *this; return options; } diff --git a/src/duckdb/src/main/secret/default_secrets.cpp b/src/duckdb/src/main/secret/default_secrets.cpp index f0dce5f5d..4fa756253 100644 --- a/src/duckdb/src/main/secret/default_secrets.cpp +++ b/src/duckdb/src/main/secret/default_secrets.cpp @@ -3,6 +3,10 @@ namespace duckdb { +identifier_set_t CreateHTTPSecretFunctions::GetRedactedKeys() { + return {"http_proxy_password", "bearer_token", "extra_http_headers"}; +} + vector CreateHTTPSecretFunctions::GetDefaultSecretTypes() { vector result; @@ -89,7 +93,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromEnv(Client secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = {"http_proxy_password", "bearer_token"}; + secret->redact_keys = GetRedactedKeys(); return std::move(secret); } @@ -107,7 +111,7 @@ unique_ptr CreateHTTPSecretFunctions::CreateHTTPSecretFromConfig(Cli secret->TrySetValue("bearer_token", input); //! Set redact keys - secret->redact_keys = {"http_proxy_password", "bearer_token"}; + secret->redact_keys = GetRedactedKeys(); return std::move(secret); } diff --git a/src/duckdb/src/main/secret/secret.cpp b/src/duckdb/src/main/secret/secret.cpp index cf6bf6538..32e5cc921 100644 --- a/src/duckdb/src/main/secret/secret.cpp +++ b/src/duckdb/src/main/secret/secret.cpp @@ -101,6 +101,41 @@ void KeyValueSecret::Serialize(Serializer &serializer) const { serializer.WriteProperty(202, "redact_keys", list); } +void KeyValueSecret::ReadSecretMap(Deserializer &deserializer, identifier_tree_t &result) { + deserializer.ReadObject(201, "secret_map", [&](Deserializer &map) { + auto stored_type = map.ReadProperty(100, "type"); + // Before DuckDB 1.1.3, secrets declared VARCHAR values even when storing other types. + if (stored_type.id() != LogicalTypeId::MAP || !stored_type.HasParameters() || + MapType::KeyType(stored_type) != LogicalType::VARCHAR) { + throw SerializationException("Secret map must be a MAP with VARCHAR keys"); + } + if (map.ReadProperty(101, "is_null")) { + throw SerializationException("Secret map cannot be NULL"); + } + + map.Set(ListType::GetChildType(stored_type)); + map.ReadObject(102, "value", [&](Deserializer &obj) { + obj.ReadList(100, "children", [&](Deserializer::List &list, idx_t i) { + // Before DuckDB 1.2.0, entries carried their actual value types, even when the map declared VARCHAR. + // Deserialize them separately so their payloads still undergo normal Value validation. + auto entry = list.ReadElement(); + if (entry.IsNull() || entry.type().id() != LogicalTypeId::STRUCT) { + throw SerializationException("Secret map entry must be a non-NULL struct"); + } + const auto &children = StructValue::GetChildren(entry); + if (children.size() != 2) { + throw SerializationException("Secret map entry must have two fields"); + } + if (children[0].IsNull() || children[0].type() != LogicalType::VARCHAR) { + throw SerializationException("Secret map key must be a non-NULL VARCHAR"); + } + result[Identifier(StringValue::Get(children[0]))] = children[1]; + }); + }); + map.Unset(); + }); +} + Value KeyValueSecret::TryGetValue(const Identifier &key, bool error_on_missing) const { auto lookup = secret_map.find(key); if (lookup == secret_map.end()) { diff --git a/src/duckdb/src/main/secret/secret_manager.cpp b/src/duckdb/src/main/secret/secret_manager.cpp index 39ff78bd9..16ca921b5 100644 --- a/src/duckdb/src/main/secret/secret_manager.cpp +++ b/src/duckdb/src/main/secret/secret_manager.cpp @@ -113,8 +113,17 @@ unique_ptr SecretManager::DeserializeSecret(Deserializer &deserializ switch (serialization_type) { // This allows us to skip looking up the secret type for deserialization altogether - case SecretSerializationType::KEY_VALUE_SECRET: - return KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); + case SecretSerializationType::KEY_VALUE_SECRET: { + auto result = + KeyValueSecret::Deserialize(deserializer, BaseSecret(scope, type, provider, name)); + if (type == "http") { + // Preserve stored keys and apply current HTTP redaction rules to older secrets. + const auto keys = CreateHTTPSecretFunctions::GetRedactedKeys(); + auto &secret = result->Cast(); + secret.redact_keys.insert(keys.begin(), keys.end()); + } + return result; + } // Continues below: we need to do a type lookup to find the secret deserialize method case SecretSerializationType::CUSTOM: break; diff --git a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp index bbe7685d2..4c375253f 100644 --- a/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp +++ b/src/duckdb/src/optimizer/pushdown/pushdown_projection.cpp @@ -157,18 +157,30 @@ unique_ptr FilterPushdown::PushdownProjection(unique_ptr> remain_expressions; vector> split_expressions; + auto stays_on_top = [&](const Filter &f) { + return IsVolatile(proj, *f.filter) || (f.filter->CanThrow() && !f.has_barrier); + }; + auto splits_off = [&](const Filter &f) { + return projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && + ReferencesComputedExpression(proj, *f.filter); + }; + // barred expressions may only cross the projection if every other filter does: a filter kept on top of the + // projection, or split off below it, would otherwise be evaluated after them + bool hold_back_barriers = false; + for (auto &filter : filters) { + if (!filter->has_barrier && (stays_on_top(*filter) || splits_off(*filter))) { + hold_back_barriers = true; + } + } for (auto &filter : filters) { auto &f = *filter; D_ASSERT(f.bindings.size() <= 1); - bool is_volatile = IsVolatile(proj, *f.filter); - bool preserve_computed_expression = projection_mode == ProjectionMode::PRESERVE_COMPUTED_EXPRESSIONS && - ReferencesComputedExpression(proj, *f.filter); - if (is_volatile || (f.filter->CanThrow() && !f.has_barrier)) { + if (stays_on_top(f) || (f.has_barrier && hold_back_barriers)) { // Volatile and throwing expressions cannot move across the projection. Expressions carrying a barrier // are the exception: a projection does not remove any rows, so pushing them through it does not - // change which rows they are evaluated on. + // change which rows they are evaluated on - unless another filter stays behind, see above. remain_expressions.push_back(std::move(f.filter)); - } else if (preserve_computed_expression) { + } else if (splits_off(f)) { // Compute filter-dependent projection expressions once, below the filter. split_expressions.push_back(std::move(f.filter)); } else { diff --git a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp index d71badeae..af20b2ff8 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_copy.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_copy.cpp @@ -185,8 +185,13 @@ unique_ptr PEGTransformerFactory::TransformCopyFileNameStringL unique_ptr PEGTransformerFactory::TransformCopyFileNameIdentifier(PEGTransformer &transformer, const Identifier &identifier) { - string file_name = identifier == "stdout" ? "/dev/stdout" : identifier.GetIdentifierName(); - return ConstantExpression::String(file_name); + if (identifier == "stdin") { + return ConstantExpression::String("/dev/stdin"); + } + if (identifier == "stdout") { + return ConstantExpression::String("/dev/stdout"); + } + return ConstantExpression::String(identifier.GetIdentifierName()); } unique_ptr diff --git a/src/duckdb/src/parser/peg/transformer/transform_pragma.cpp b/src/duckdb/src/parser/peg/transformer/transform_pragma.cpp index cd4860cb6..3aeb2e5b6 100644 --- a/src/duckdb/src/parser/peg/transformer/transform_pragma.cpp +++ b/src/duckdb/src/parser/peg/transformer/transform_pragma.cpp @@ -1,3 +1,4 @@ +#include "duckdb/logging/logger.hpp" #include "duckdb/parser/statement/pragma_statement.hpp" #include "duckdb/parser/expression/columnref_expression.hpp" #include "duckdb/parser/peg/transformer/peg_transformer.hpp" @@ -39,6 +40,12 @@ PEGTransformerFactory::TransformPragmaAssign(PEGTransformer &transformer, const if (sqlite_compat_pragmas.find(info.name) != sqlite_compat_pragmas.end()) { return std::move(result); } + if (transformer.options.context) { + DUCKDB_LOG_WARNING(*transformer.options.context, + "Using \"PRAGMA %s=...\" to set a setting is deprecated and will be removed in a future " + "release, use \"SET %s=...\" instead.", + info.name.GetIdentifierName(), info.name.GetIdentifierName()); + } auto set_statement = make_uniq(info.name, std::move(info.parameters[0]), SetScope::AUTOMATIC); return std::move(set_statement); } diff --git a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp index 27ce506ff..49843c812 100644 --- a/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp +++ b/src/duckdb/src/planner/binder/query_node/bind_select_node.cpp @@ -725,9 +725,6 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from } bind_state.AddRegularColumn(); - if (can_group_by_all && is_window) { - throw BinderException("Cannot group on a window clause"); - } if (can_group_by_all && result.unnests.SelectList().size() > unnest_count) { throw BinderException("Cannot group on an UNNEST or UNLIST clause"); } @@ -735,6 +732,10 @@ BoundStatement Binder::BindSelectNode(SelectNode &statement, BoundStatement from if (select_binder.BoundAggregates()) { throw BinderException("Cannot mix aggregates with non-aggregated columns!"); } + if (is_window) { + // a window over aggregates (e.g. sum(sum(x)) OVER ()) is fine, a window over a group is not + throw BinderException("Cannot group on a window clause"); + } // we are forcing aggregates, and the node has columns bound // this entry becomes a group group_by_all_indexes.push_back(i); From edda2e76db68206a1c16a3d93330125d2c984e34 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Fri, 16 Oct 2026 10:44:37 +0000 Subject: [PATCH 08/13] Update vendored DuckDB sources to 4d5b840be15 --- ...nerated_extension_loader_package_build.cpp | 8 ++--- src/duckdb/src/catalog/catalog.cpp | 3 ++ .../table/system/pragma_database_size.cpp | 2 +- .../function/table/version/pragma_version.cpp | 6 ++-- .../function/window/window_value_function.cpp | 30 +++++++++++++++---- .../include/duckdb/main/extension_entries.hpp | 3 ++ 6 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index b7fd05777..2548ed59d 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48444"; + descriptor->api_version = "v2.0.0-alpha48520"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48444"; + descriptor->api_version = "v2.0.0-alpha48520"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48444"; + descriptor->api_version = "v2.0.0-alpha48520"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48444"; + descriptor->api_version = "v2.0.0-alpha48520"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/catalog/catalog.cpp b/src/duckdb/src/catalog/catalog.cpp index e286ec9e8..99bb2d221 100644 --- a/src/duckdb/src/catalog/catalog.cpp +++ b/src/duckdb/src/catalog/catalog.cpp @@ -898,6 +898,9 @@ CatalogException Catalog::CreateMissingEntryException(CatalogEntryRetriever &ret if (unseen_schemas.size() >= max_schema_count) { break; } + if (database->GetVisibility() == AttachVisibility::HIDDEN) { + continue; + } auto &catalog = database->GetCatalog(); auto current_schemas = catalog.GetSchemas(context); for (auto ¤t_schema : current_schemas) { diff --git a/src/duckdb/src/function/table/system/pragma_database_size.cpp b/src/duckdb/src/function/table/system/pragma_database_size.cpp index 2b1a67498..50873f329 100644 --- a/src/duckdb/src/function/table/system/pragma_database_size.cpp +++ b/src/duckdb/src/function/table/system/pragma_database_size.cpp @@ -82,7 +82,7 @@ void PragmaDatabaseSizeFunction(ClientContext &context, TableFunctionInput &data for (; data.index < data.databases.size() && row < STANDARD_VECTOR_SIZE; data.index++) { auto &db = *data.databases[data.index]; - if (db.IsSystem() || db.IsTemporary()) { + if (db.IsSystem() || db.IsTemporary() || db.GetVisibility() == AttachVisibility::HIDDEN) { continue; } auto ds = db.GetCatalog().GetDatabaseSize(context); diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 68a2c3236..4e1f3b98d 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48444" +#define DUCKDB_PATCH_VERSION "0-alpha48520" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48444" +#define DUCKDB_VERSION "v2.0.0-alpha48520" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "9a97642349" +#define DUCKDB_SOURCE_ID "4d5b840be1" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/function/window/window_value_function.cpp b/src/duckdb/src/function/window/window_value_function.cpp index 6d9e66ecc..0879b458a 100644 --- a/src/duckdb/src/function/window/window_value_function.cpp +++ b/src/duckdb/src/function/window/window_value_function.cpp @@ -681,7 +681,19 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c frame = FrameBounds(frame_begin[i], MaxValue(frame_end[i], frame_begin[i])); const auto own_row = glstate.row_tree->Rank(frame.start, frame.end, row_idx) - 1; // (2) adjust the row number by adding or subtracting an offset - const auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(own_row), offset); + auto val_idx = LeadLagIndex(wexpr.GetExpressionType(), NumericCast(own_row), offset); + const auto is_lead = (wexpr.GetExpressionType() == ExpressionType::WINDOW_LEAD); + if (row_idx < frame.start || frame.end <= row_idx) { + // An own row outside the frame ranks just before own_row instead of at it, + // so a zero shift is the own row itself and a forward shift lands one row earlier. + if (!offset) { + cursor.CopyCell(0, row_idx, result, i); + continue; + } + if (is_lead == (offset > 0)) { + --val_idx; + } + } const auto frame_width = NumericCast(frame.end - frame.start); if (val_idx >= 0 && val_idx < frame_width) { // (3) find the row at that offset @@ -739,13 +751,21 @@ void WindowLeadLagExecutor::GetData(ExecutionContext &context, DataChunk &eval_c idx_t delta = 0; if (val_idx < (int64_t)row_idx) { - // Count backwards + // Count backwards, starting from the last row at or before the own row delta = idx_t(row_idx - idx_t(val_idx)); - val_idx = int64_t(WindowBoundariesState::FindPrevStart(*ignore_nulls, partition_begin[i], row_idx, delta)); + const auto lo = partition_begin[i]; + const auto hi = MinValue(row_idx, partition_end[i]); + if (lo < hi) { + val_idx = int64_t(WindowBoundariesState::FindPrevStart(*ignore_nulls, lo, hi, delta)); + } } else if (val_idx > (int64_t)row_idx) { + // Count forwards, starting from the first row at or after the own row delta = idx_t(idx_t(val_idx) - row_idx); - val_idx = - int64_t(WindowBoundariesState::FindNextStart(*ignore_nulls, row_idx + 1, partition_end[i], delta)); + const auto lo = MaxValue(row_idx + 1, partition_begin[i]); + const auto hi = partition_end[i]; + if (lo < hi) { + val_idx = int64_t(WindowBoundariesState::FindNextStart(*ignore_nulls, lo, hi, delta)); + } } // else offset is zero, so don't move. diff --git a/src/duckdb/src/include/duckdb/main/extension_entries.hpp b/src/duckdb/src/include/duckdb/main/extension_entries.hpp index 53bbe9c7b..91b920f7b 100644 --- a/src/duckdb/src/include/duckdb/main/extension_entries.hpp +++ b/src/duckdb/src/include/duckdb/main/extension_entries.hpp @@ -403,6 +403,7 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"iceberg_bucket", "iceberg", CatalogType::SCALAR_FUNCTION_ENTRY}, {"iceberg_column_stats", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_load_table_response", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, + {"iceberg_manifests", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_metadata", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_partition_stats", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_rewrite_data_files", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, @@ -415,6 +416,7 @@ static constexpr ExtensionFunctionEntry EXTENSION_FUNCTIONS[] = { {"iceberg_table_properties", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_to_ducklake", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"iceberg_truncate", "iceberg", CatalogType::SCALAR_FUNCTION_ENTRY}, + {"iceberg_view_metadata", "iceberg", CatalogType::TABLE_FUNCTION_ENTRY}, {"icu_calendar_names", "icu", CatalogType::TABLE_FUNCTION_ENTRY}, {"icu_collate_aa", "icu", CatalogType::SCALAR_FUNCTION_ENTRY}, {"icu_collate_af", "icu", CatalogType::SCALAR_FUNCTION_ENTRY}, @@ -1351,6 +1353,7 @@ static constexpr ExtensionEntry EXTENSION_SETTINGS[] = { {"iceberg_default_format_version", "iceberg"}, {"iceberg_equality_delete_fast_filter", "iceberg"}, {"iceberg_logging_post_body_truncate_limit", "iceberg"}, + {"iceberg_metadata_log_clock_skew_ms", "iceberg"}, {"iceberg_test_force_token_expiry", "iceberg"}, {"iceberg_unsafe_skip_puffin_verification", "iceberg"}, {"iceberg_use_metadata_log", "iceberg"}, From b53f0ba5c08a52e4084dc91323c144059adc5332 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Fri, 16 Oct 2026 16:07:29 +0000 Subject: [PATCH 09/13] Update vendored DuckDB sources to ffe0b8b7a22 --- src/duckdb/generated_extension_loader_package_build.cpp | 8 ++++---- src/duckdb/src/function/table/version/pragma_version.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index 2548ed59d..a75e8bd16 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48520"; + descriptor->api_version = "v2.0.0-alpha48624"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48520"; + descriptor->api_version = "v2.0.0-alpha48624"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48520"; + descriptor->api_version = "v2.0.0-alpha48624"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48520"; + descriptor->api_version = "v2.0.0-alpha48624"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 4e1f3b98d..eeebee3f6 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48520" +#define DUCKDB_PATCH_VERSION "0-alpha48624" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48520" +#define DUCKDB_VERSION "v2.0.0-alpha48624" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "4d5b840be1" +#define DUCKDB_SOURCE_ID "ffe0b8b7a2" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" From 931a0d5c04f5101a19d3c9515054cad873d57fee Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Fri, 16 Oct 2026 18:13:28 +0000 Subject: [PATCH 10/13] Update vendored DuckDB sources to f2ba7850dd6 --- src/duckdb/generated_extension_loader_package_build.cpp | 8 ++++---- src/duckdb/src/function/table/version/pragma_version.cpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index a75e8bd16..7913dbdd1 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48624"; + descriptor->api_version = "v2.0.0-alpha48630"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48624"; + descriptor->api_version = "v2.0.0-alpha48630"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48624"; + descriptor->api_version = "v2.0.0-alpha48630"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48624"; + descriptor->api_version = "v2.0.0-alpha48630"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index eeebee3f6..07b1e2fcc 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48624" +#define DUCKDB_PATCH_VERSION "0-alpha48630" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48624" +#define DUCKDB_VERSION "v2.0.0-alpha48630" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "ffe0b8b7a2" +#define DUCKDB_SOURCE_ID "f2ba7850dd" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" From 21dc6b5931e3614fe1482dff50325d6b00176a2b Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Sat, 17 Oct 2026 03:02:12 +0000 Subject: [PATCH 11/13] Update vendored DuckDB sources to f8f3f4dcd95 --- ...nerated_extension_loader_package_build.cpp | 8 ++++---- src/duckdb/src/common/column_index.cpp | 4 ++++ .../function/table/version/pragma_version.cpp | 6 +++--- .../src/optimizer/remove_unused_columns.cpp | 20 +++++++++++++++++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index 7913dbdd1..e23210138 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48630"; + descriptor->api_version = "v2.0.0-alpha48730"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48630"; + descriptor->api_version = "v2.0.0-alpha48730"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48630"; + descriptor->api_version = "v2.0.0-alpha48730"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48630"; + descriptor->api_version = "v2.0.0-alpha48730"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/common/column_index.cpp b/src/duckdb/src/common/column_index.cpp index 976aeb397..0ab05e969 100644 --- a/src/duckdb/src/common/column_index.cpp +++ b/src/duckdb/src/common/column_index.cpp @@ -107,6 +107,10 @@ bool ColumnIndex::IsChildPathOf(const ColumnIndex &path) const { return false; } } + if (a.get().type != b.get().type) { + // A cast parent cannot replace a path through the uncast value. + return false; + } const bool a_has_children = a.get().HasChildren(); const bool b_has_children = b.get().HasChildren(); if (!a_has_children && !b_has_children) { diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index 07b1e2fcc..a6a31c722 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48630" +#define DUCKDB_PATCH_VERSION "0-alpha48730" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,10 +8,10 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48630" +#define DUCKDB_VERSION "v2.0.0-alpha48730" #endif #ifndef DUCKDB_SOURCE_ID -#define DUCKDB_SOURCE_ID "f2ba7850dd" +#define DUCKDB_SOURCE_ID "f8f3f4dcd9" #endif #include "duckdb/function/table/system_functions.hpp" #include "duckdb/main/database.hpp" diff --git a/src/duckdb/src/optimizer/remove_unused_columns.cpp b/src/duckdb/src/optimizer/remove_unused_columns.cpp index 35e31126c..6b665cdb6 100644 --- a/src/duckdb/src/optimizer/remove_unused_columns.cpp +++ b/src/duckdb/src/optimizer/remove_unused_columns.cpp @@ -1,6 +1,7 @@ #include "duckdb/optimizer/remove_unused_columns.hpp" #include "duckdb/common/assert.hpp" +#include "duckdb/common/exception/binder_exception.hpp" #include "duckdb/common/pair.hpp" #include "duckdb/function/aggregate/distributive_functions.hpp" #include "duckdb/function/function_binder.hpp" @@ -1118,6 +1119,11 @@ static unique_ptr ConstructStructExtractFromPath(ClientContext &cont reference type_iter(struct_type); reference path_iter(path); while (true) { + if (!path_iter.get().HasPrimaryIndex()) { + // TODO: Reconstruct named field steps with variant_extract to support mixed STRUCT/VARIANT paths. + throw BinderException("Extracting VARIANT field \"%s\" through this projection is not supported", + path_iter.get().GetFieldName()); + } auto child_index = path_iter.get().GetPrimaryIndex(); auto &child_types = StructType::GetChildTypes(type_iter.get()); D_ASSERT(child_index < child_types.size()); @@ -1197,6 +1203,16 @@ void RemoveUnusedColumns::RewriteExpressions(LogicalProjection &proj, idx_t expr proj.expressions = std::move(expressions); } +template +static bool OnlyStructFieldPaths(const T &paths) { + for (auto &path : paths) { + if (!path.HasPrimaryIndex() || !OnlyStructFieldPaths(path.GetChildIndexes())) { + return false; + } + } + return true; +} + void RemoveUnusedColumns::CheckPushdownExtract(LogicalOperator &op) { D_ASSERT(mode == RemoveUnusedColumnsMode::APPLY); switch (op.type) { @@ -1264,6 +1280,10 @@ void RemoveUnusedColumns::CheckPushdownExtract(LogicalOperator &op) { //! Extract pull up only supported for STRUCT currently continue; } + if (!OnlyStructFieldPaths(col.unique_paths)) { + //! A VARIANT field step cannot be rebuilt as a struct extract above the projection + continue; + } if (col.supports_pushdown_extract == PushdownExtractSupport::DISABLED) { //! Already explicitly disabled, don't need to check continue; From 1fa0bdbf30e837a1acdff365f395778147a005a5 Mon Sep 17 00:00:00 2001 From: DuckLabs GitHub Bot Date: Sun, 18 Oct 2026 02:14:33 +0000 Subject: [PATCH 12/13] Update vendored DuckDB sources to f8f3f4dcd95 --- src/duckdb/generated_extension_loader_package_build.cpp | 8 ++++---- src/duckdb/src/function/table/version/pragma_version.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/duckdb/generated_extension_loader_package_build.cpp b/src/duckdb/generated_extension_loader_package_build.cpp index e23210138..e647b4f1e 100644 --- a/src/duckdb/generated_extension_loader_package_build.cpp +++ b/src/duckdb/generated_extension_loader_package_build.cpp @@ -45,7 +45,7 @@ int32_t duckdb_extension_core_functions_describe(duckdb_extension_descriptor *de descriptor->version = 1; descriptor->name = "core_functions"; descriptor->extension_version = EXT_VERSION_CORE_FUNCTIONS; - descriptor->api_version = "v2.0.0-alpha48730"; + descriptor->api_version = "v2.0.0-alpha48780"; descriptor->entry_cpp = (void (*)(void))core_functions_duckdb_cpp_init; return 0; } @@ -78,7 +78,7 @@ int32_t duckdb_extension_parquet_describe(duckdb_extension_descriptor *descripto descriptor->version = 1; descriptor->name = "parquet"; descriptor->extension_version = EXT_VERSION_PARQUET; - descriptor->api_version = "v2.0.0-alpha48730"; + descriptor->api_version = "v2.0.0-alpha48780"; descriptor->entry_cpp = (void (*)(void))parquet_duckdb_cpp_init; return 0; } @@ -111,7 +111,7 @@ int32_t duckdb_extension_icu_describe(duckdb_extension_descriptor *descriptor) { descriptor->version = 1; descriptor->name = "icu"; descriptor->extension_version = EXT_VERSION_ICU; - descriptor->api_version = "v2.0.0-alpha48730"; + descriptor->api_version = "v2.0.0-alpha48780"; descriptor->entry_cpp = (void (*)(void))icu_duckdb_cpp_init; return 0; } @@ -144,7 +144,7 @@ int32_t duckdb_extension_json_describe(duckdb_extension_descriptor *descriptor) descriptor->version = 1; descriptor->name = "json"; descriptor->extension_version = EXT_VERSION_JSON; - descriptor->api_version = "v2.0.0-alpha48730"; + descriptor->api_version = "v2.0.0-alpha48780"; descriptor->entry_cpp = (void (*)(void))json_duckdb_cpp_init; return 0; } diff --git a/src/duckdb/src/function/table/version/pragma_version.cpp b/src/duckdb/src/function/table/version/pragma_version.cpp index a6a31c722..471376cba 100644 --- a/src/duckdb/src/function/table/version/pragma_version.cpp +++ b/src/duckdb/src/function/table/version/pragma_version.cpp @@ -1,5 +1,5 @@ #ifndef DUCKDB_PATCH_VERSION -#define DUCKDB_PATCH_VERSION "0-alpha48730" +#define DUCKDB_PATCH_VERSION "0-alpha48780" #endif #ifndef DUCKDB_MINOR_VERSION #define DUCKDB_MINOR_VERSION 0 @@ -8,7 +8,7 @@ #define DUCKDB_MAJOR_VERSION 2 #endif #ifndef DUCKDB_VERSION -#define DUCKDB_VERSION "v2.0.0-alpha48730" +#define DUCKDB_VERSION "v2.0.0-alpha48780" #endif #ifndef DUCKDB_SOURCE_ID #define DUCKDB_SOURCE_ID "f8f3f4dcd9" From 91651227f2aa3c70aedba684f3a2965d944bdef7 Mon Sep 17 00:00:00 2001 From: Alex Kasko Date: Sun, 18 Oct 2026 23:01:02 +0100 Subject: [PATCH 13/13] Streaming query results API fix --- src/jni/duckdb_java.cpp | 12 +++++++++--- src/test/java/org/duckdb/TestResults.java | 13 +++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 2c2765006..901468915 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -360,7 +360,7 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA return nullptr; } - bool stream_results_stmt = pending->GetStatementProperties().result_eagerness != ResultEagerness::FORCED; + bool stream_results_stmt = StreamsByDefault(stmt_ref->stmt->GetStatementType(), *pending); bool stream_results = stream_results_user && stream_results_stmt; auto res_ref = make_uniq(); @@ -383,12 +383,17 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA } jobject _duckdb_jdbc_execute_capi(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) { + auto stmt_ref = reinterpret_cast(env->GetDirectBufferAddress(stmt_ref_buf)); + if (!stmt_ref || !stmt_ref->stmt) { + throw InvalidInputException("Invalid statement"); + } + auto pending = execute_prepared_statement(env, stmt_ref_buf, params, true); if (!pending) { return nullptr; } - bool stream_results = pending->GetStatementProperties().result_eagerness != ResultEagerness::FORCED; + bool stream_results = StreamsByDefault(stmt_ref->stmt->GetStatementType(), *pending); auto out = make_uniq(); if (stream_results) { @@ -420,7 +425,8 @@ jobject _duckdb_jdbc_execute_pending(JNIEnv *env, jclass, jobject pending_ref_bu return nullptr; } - bool stream_results = pending_ref->pending->GetStatementProperties().result_eagerness != ResultEagerness::FORCED; + // the pending result was submitted from the SQL statement as written, so its type is the written type + bool stream_results = StreamsByDefault(pending_ref->pending->GetStatementType(), *pending_ref->pending); auto res_ref = make_uniq(); if (pending_ref->streaming && stream_results) { diff --git a/src/test/java/org/duckdb/TestResults.java b/src/test/java/org/duckdb/TestResults.java index 0303abcef..c7125f16e 100644 --- a/src/test/java/org/duckdb/TestResults.java +++ b/src/test/java/org/duckdb/TestResults.java @@ -270,13 +270,18 @@ public static void test_stream_multiple_open_results() throws Exception { Properties props = new Properties(); props.setProperty(JDBC_STREAM_RESULTS, String.valueOf(true)); - String QUERY = "SELECT * FROM range(100000)"; + int count = 100000; + String QUERY = "SELECT * FROM range(" + count + ")"; try (Connection conn = DriverManager.getConnection(JDBC_URL, props); Statement stmt1 = conn.createStatement(); Statement stmt2 = conn.createStatement()) { - try (ResultSet rs1 = stmt1.executeQuery(QUERY); ResultSet rs2 = stmt2.executeQuery(QUERY)) { - assertNotNull(rs2); - assertThrows(rs1::next, SQLException.class); + try (ResultSet rs1 = stmt1.executeQuery(QUERY)) { + assertThrows(() -> { stmt2.executeQuery(QUERY); }, SQLException.class); + int rows = 0; + while (rs1.next()) { + rows++; + } + assertEquals(rows, count); } } }