@@ -10,6 +10,7 @@ DUCKDB_INCLUDES_BEGIN
1010#include " duckdb.h"
1111#include " duckdb/catalog/catalog.hpp"
1212#include " duckdb/common/insertion_order_preserving_map.hpp"
13+ #include " duckdb/common/multi_file/multi_file_reader.hpp"
1314#include " duckdb/function/table_function.hpp"
1415#include " duckdb/main/capi/capi_internal.hpp"
1516#include " duckdb/main/connection.hpp"
@@ -19,6 +20,8 @@ DUCKDB_INCLUDES_END
1920using namespace duckdb ;
2021using vortex::CData;
2122using vortex::IntoErrString;
23+ constexpr column_t COLUMN_IDENTIFIER_FILE_INDEX = MultiFileReader::COLUMN_IDENTIFIER_FILE_INDEX;
24+ constexpr column_t COLUMN_IDENTIFIER_FILE_ROW_NUMBER = MultiFileReader::COLUMN_IDENTIFIER_FILE_ROW_NUMBER;
2225
2326struct CTableFunctionInfo final : TableFunctionInfo {
2427 explicit CTableFunctionInfo (const duckdb_vx_tfunc_vtab_t &vtab) : vtab(vtab) {
@@ -191,6 +194,11 @@ struct CTableBindResult {
191194 vector<string> &names;
192195};
193196
197+ /* *
198+ * Called for every new query. For example, if there is a VIEW over *.vortex,
199+ * and after a query another file is added matching the glob, for second query
200+ * bind() will be called again.
201+ */
194202unique_ptr<FunctionData> c_bind (ClientContext &context,
195203 TableFunctionBindInput &input,
196204 vector<LogicalType> &return_types,
@@ -334,21 +342,50 @@ extern "C" void duckdb_vx_tfunc_bind_result_add_column(duckdb_vx_tfunc_bind_resu
334342 result.return_types .emplace_back (logical_type);
335343}
336344
337- OperatorPartitionData c_get_partition_data (ClientContext &, TableFunctionGetPartitionInput &input) {
338- if (input.partition_info .RequiresPartitionColumns ()) {
339- throw InternalException (" TableScan::GetPartitionData: partition columns not supported" );
340- }
345+ /* *
346+ * Called at planning time to determine whether data is partitioned by a
347+ * given set of columns. Requested columns are GROUP BY parameters i.e. columns
348+ * over which the query aggregates.
349+ */
350+ TablePartitionInfo get_partition_info (ClientContext &, TableFunctionPartitionInput &input) {
351+ const vector<column_t > &ids = input.partition_ids ;
352+ // Our data is partitioned by array exporters. Each exporter processes a
353+ // single Array which belongs to a single file. If data is partitioned only
354+ // by file_index, there is one unique value for an Array. Otherwise there
355+ // may be multiple values.
356+ return (ids.size () == 1 && ids[0 ] == COLUMN_IDENTIFIER_FILE_INDEX)
357+ ? TablePartitionInfo::SINGLE_VALUE_PARTITIONS
358+ : TablePartitionInfo::NOT_PARTITIONED;
359+ }
360+
361+ /* *
362+ * Duckdb requests this function after exporting the chunk. We answer with
363+ * partition_index we have exported as well as information about constant
364+ * columns in this partition. As data is partitioned by array exporters, in
365+ * each partition ~ exported array file_index is constant.
366+ */
367+ OperatorPartitionData get_partition_data (ClientContext &, TableFunctionGetPartitionInput &input) {
341368 auto &bind = input.bind_data ->Cast <CTableBindData>();
342369 void *const ffi_bind = bind.ffi_data ->DataPtr ();
343370 void *const ffi_global = input.global_state ->Cast <CTableGlobalData>().ffi_data ->DataPtr ();
344371 void *const ffi_local = input.local_state ->Cast <CTableLocalData>().ffi_data ->DataPtr ();
345-
346- duckdb_vx_error error_out = nullptr ;
347- const idx_t batch_index = bind.info .vtab .get_partition_data (ffi_bind, ffi_global, ffi_local, &error_out);
348- if (error_out) {
349- throw InvalidInputException (IntoErrString (error_out));
372+ duckdb_vx_partition_data partition_data;
373+ bind.info .vtab .get_partition_data (ffi_bind, ffi_global, ffi_local, &partition_data);
374+
375+ OperatorPartitionData out (partition_data.partition_index );
376+
377+ // file_index_column_pos may be INVALID_IDX, but column_index will never
378+ // be INVALID_IDX, so we can compare directly
379+ for (const column_t column_index : input.partition_info .partition_columns ) {
380+ if (column_index == partition_data.file_index_column_pos ) {
381+ out.partition_data .emplace_back (Value::UBIGINT (partition_data.file_index ));
382+ } else {
383+ throw InternalException (StringUtil::Format (
384+ " get_partition_data: requested column_index %d is not constant for given partition" ,
385+ column_index));
386+ }
350387 }
351- return OperatorPartitionData (batch_index) ;
388+ return out ;
352389}
353390
354391extern " C" void duckdb_vx_string_map_insert (duckdb_vx_string_map map, const char *key, const char *value) {
@@ -377,21 +414,30 @@ extern "C" duckdb_state duckdb_vx_tfunc_register(duckdb_database ffi_db, const d
377414
378415 tf.projection_pushdown = true ;
379416 tf.filter_pushdown = true ;
380- // We can prune out filter columns that are unused in the remainder of the query plan.
381- // e.g. in "SELECT i FROM tbl WHERE j = 42" j does not leave Vortex table function.
382417 tf.filter_prune = true ;
383418 tf.sampling_pushdown = false ;
384- tf.late_materialization = false ;
385419
386420 tf.pushdown_complex_filter = c_pushdown_complex_filter;
387421 tf.cardinality = c_cardinality;
388- tf.get_partition_data = c_get_partition_data;
422+ tf.get_partition_info = get_partition_info;
423+ tf.get_partition_data = get_partition_data;
389424 tf.to_string = c_to_string;
390425 tf.table_scan_progress = c_table_scan_progress;
391426 tf.statistics = c_statistics;
392427
428+ tf.late_materialization = true ;
429+ // Columns that uniquely identify a row for deferred re-fetch in a multi
430+ // file scan: (file index, row number in file).
431+ tf.get_row_id_columns = [](auto &, auto ) -> vector<column_t > {
432+ return {COLUMN_IDENTIFIER_FILE_INDEX, COLUMN_IDENTIFIER_FILE_ROW_NUMBER};
433+ };
434+
393435 tf.get_virtual_columns = [](auto &, auto ) -> virtual_column_map_t {
394- return {{COLUMN_IDENTIFIER_EMPTY, TableColumn (" " , LogicalTypeId::BOOLEAN)}};
436+ return {
437+ {COLUMN_IDENTIFIER_EMPTY, {" " , LogicalTypeId::BOOLEAN}},
438+ {COLUMN_IDENTIFIER_FILE_INDEX, {" file_index" , LogicalType::UBIGINT}},
439+ {COLUMN_IDENTIFIER_FILE_ROW_NUMBER, {" file_row_number" , LogicalType::BIGINT}},
440+ };
395441 };
396442
397443 tf.arguments .resize (vtab->parameter_count );
0 commit comments