Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 95 additions & 62 deletions src/core/deletes/iceberg_equality_delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,21 @@ static void ColumnsReferencedByEqualityIds(DataChunk &source, DataChunk &result,
result.ReferenceColumns(source, column_ids);
}

bool IcebergMultiFileList::EqualityDeletesFinalized() const {
for (auto &entry : equality_delete_data) {
auto &deletes = *entry.second;
for (auto &file : deletes.files) {
if (!file.finalized) {
return false;
}
}
}
return true;
}

void IcebergMultiFileList::ScanEqualityDeleteFile(const BoundIcebergManifestEntry &bound_manifest_entry,
DataChunk &source, vector<MultiFileColumnDefinition> &local_columns,
const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const {
DataChunk &source,
vector<MultiFileColumnDefinition> &local_columns) const {
auto &manifest_entry = bound_manifest_entry.entry;
auto &data_file = manifest_entry.data_file;
auto &manifest_file = GetManifestFileForEntry(bound_manifest_entry, IcebergManifestContentType::DELETE);
Expand All @@ -71,9 +81,31 @@ void IcebergMultiFileList::ScanEqualityDeleteFile(const BoundIcebergManifestEntr
}
auto &deletes = *it->second;

// We are scanning the delete file even before the optimizer runs
// All equality delete columns will be projected from the scan due to our optimizer
// we want to know where in the output the equality delete columns will be projected
deletes.files.emplace_back(data_file.partition_info, manifest_file.partition_spec_id);
auto &file = deletes.files.back();
file.rows.resize(count);
D_ASSERT(result.ColumnCount() == data_file.equality_ids.size());

for (idx_t col_idx = 0; col_idx < result.ColumnCount(); col_idx++) {
auto field_id = data_file.equality_ids[col_idx];
auto &values = file.equality_values[field_id];
values.reserve(count);
auto &vec = result.data[col_idx];
for (idx_t i = 0; i < count; i++) {
values.push_back(vec.GetValue(i));
}
}
}

void IcebergMultiFileList::FinalizeEqualityDeletes(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const {
unordered_map<int32_t, column_t> field_id_to_global_column;
for (idx_t i = 0; i < global_columns.size(); i++) {
auto &global_col = global_columns.at(i);
field_id_to_global_column[global_col.GetIdentifierFieldId()] = i;
}

unordered_map<idx_t, idx_t> global_id_to_projection_index;
for (idx_t result_id = 0; result_id < global_column_ids.size(); result_id++) {
auto global_col = global_column_ids[result_id];
Expand All @@ -83,67 +115,68 @@ void IcebergMultiFileList::ScanEqualityDeleteFile(const BoundIcebergManifestEntr
D_ASSERT(global_col.GetPrimaryIndex() < global_columns.size());
// index_in_global_columns = index in input_chunk
auto index_in_global_columns = global_col.GetPrimaryIndex();
auto &col = global_columns[index_in_global_columns];
for (auto &equality_delete_col : local_columns) {
if (equality_delete_col.GetIdentifierFieldId() == col.GetIdentifierFieldId()) {
if (projection_ids.empty()) {
global_id_to_projection_index[index_in_global_columns] = result_id;
} else {
for (idx_t proj_index = 0; proj_index < projection_ids.size(); proj_index++) {
idx_t projection_col = projection_ids[proj_index];
if (projection_col == result_id) {
global_id_to_projection_index[index_in_global_columns] = proj_index;
}
}
if (projection_ids.empty()) {
global_id_to_projection_index[index_in_global_columns] = result_id;
} else {
for (idx_t proj_index = 0; proj_index < projection_ids.size(); proj_index++) {
idx_t projection_col = projection_ids[proj_index];
if (projection_col == result_id) {
global_id_to_projection_index[index_in_global_columns] = proj_index;
break;
}
// here we can break. col has one identifier field id and equality deletes should only have unique
// values
break;
}
}
}

unordered_map<int32_t, column_t> field_id_to_global_column;
for (idx_t i = 0; i < global_columns.size(); i++) {
auto &global_col = global_columns.at(i);
field_id_to_global_column[global_col.GetIdentifierFieldId()] = i;
}

deletes.files.emplace_back(data_file.partition_info, manifest_file.partition_spec_id);
auto &rows = deletes.files.back().rows;
rows.resize(count);
D_ASSERT(result.ColumnCount() == data_file.equality_ids.size());

for (idx_t col_idx = 0; col_idx < result.ColumnCount(); col_idx++) {
auto &field_id = data_file.equality_ids[col_idx];
auto global_column_id = field_id_to_global_column[field_id];
auto &col = global_columns[global_column_id];
auto &vec = result.data[col_idx];

auto it = global_id_to_projection_index.find(global_column_id);
D_ASSERT(it != global_id_to_projection_index.end());
auto result_column_id = it->second;

for (idx_t i = 0; i < count; i++) {
auto &row = rows[i];
auto constant = vec.GetValue(i);

unique_ptr<Expression> equality_filter;
// this bound ref is on the position of the output_chunk data.
auto bound_ref = make_uniq<BoundReferenceExpression>(col.type, result_column_id);
if (!constant.IsNull()) {
//! Create a COMPARE_NOT_EQUAL expression
equality_filter =
BoundComparisonExpression::Create(ExpressionType::COMPARE_NOTEQUAL, std::move(bound_ref),
make_uniq<BoundConstantExpression>(constant));
} else {
//! Construct an OPERATOR_IS_NOT_NULL expression instead
auto is_not_null =
make_uniq<BoundOperatorExpression>(ExpressionType::OPERATOR_IS_NOT_NULL, LogicalType::BOOLEAN);
is_not_null->GetChildrenMutable().push_back(std::move(bound_ref));
equality_filter = std::move(is_not_null);
for (auto &entry : equality_delete_data) {
auto &deletes = *entry.second;
for (auto &file : deletes.files) {
if (file.finalized) {
continue;
}
auto row_count = file.rows.size();
for (auto &field_values : file.equality_values) {
if (row_count == 0) {
row_count = field_values.second.size();
}
D_ASSERT(row_count == field_values.second.size());
}
file.rows.resize(row_count);

for (auto &field_values : file.equality_values) {
auto field_id = field_values.first;
auto global_column_it = field_id_to_global_column.find(field_id);
D_ASSERT(global_column_it != field_id_to_global_column.end());
auto global_column_id = global_column_it->second;
auto &col = global_columns[global_column_id];

auto projection_it = global_id_to_projection_index.find(global_column_id);
D_ASSERT(projection_it != global_id_to_projection_index.end());
auto result_column_id = projection_it->second;

auto &values = field_values.second;
D_ASSERT(values.size() == row_count);
for (idx_t i = 0; i < row_count; i++) {
auto &row = file.rows[i];
auto &constant = values[i];

unique_ptr<Expression> equality_filter;
// This bound ref is on the position of the output_chunk data.
auto bound_ref = make_uniq<BoundReferenceExpression>(col.type, result_column_id);
if (!constant.IsNull()) {
equality_filter =
BoundComparisonExpression::Create(ExpressionType::COMPARE_NOTEQUAL, std::move(bound_ref),
make_uniq<BoundConstantExpression>(constant));
} else {
auto is_not_null = make_uniq<BoundOperatorExpression>(ExpressionType::OPERATOR_IS_NOT_NULL,
LogicalType::BOOLEAN);
is_not_null->GetChildrenMutable().push_back(std::move(bound_ref));
equality_filter = std::move(is_not_null);
}
row.filters.emplace(std::make_pair(field_id, std::move(equality_filter)));
}
}
row.filters.emplace(std::make_pair(field_id, std::move(equality_filter)));
file.finalized = true;
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/execution/operator/iceberg_delete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,15 @@ void IcebergDelete::FlushDeletes(IcebergTransaction &transaction, ClientContext
if (!multi_file_list) {
throw InternalException("IcebergDelete multi_file_list is NULL");
}
{
lock_guard<mutex> guard(multi_file_list->lock);
lock_guard<mutex> delete_guard(multi_file_list->delete_lock);
if (!multi_file_list->FinishedScanningDeletes() ||
multi_file_list->transaction_delete_idx < multi_file_list->transaction_delete_manifests.size()) {
multi_file_list->ProcessDeletes();
}
}

lock_guard<mutex> guard(global_state.lock);
for (auto &entry : global_state.deleted_rows) {
auto &filename = entry.first;
Expand Down
9 changes: 7 additions & 2 deletions src/function/scan/iceberg_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@
#include "function/iceberg_functions.hpp"
#include "catalog/rest/catalog_entry/table/iceberg_table_entry.hpp"

#include <string>
#include <numeric>
#include "duckdb/common/multi_file/multi_file_states.hpp"
#include "duckdb/function/partition_stats.hpp"
#include "duckdb/storage/statistics/numeric_stats.hpp"
#include "duckdb/storage/statistics/string_stats.hpp"
#include "core/metadata/schema/iceberg_column_definition.hpp"
#include "core/expression/iceberg_predicate_stats.hpp"
#include "core/expression/iceberg_value.hpp"

namespace duckdb {

Expand Down
8 changes: 4 additions & 4 deletions src/include/core/deletes/iceberg_equality_delete.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ namespace duckdb {

using sequence_number_t = int64_t;

struct EqualityDeleteValuelist {
unordered_map<idx_t, vector<Value>> field_id_value_to_remove;
};

struct IcebergEqualityDeleteRow {
public:
IcebergEqualityDeleteRow() {
Expand Down Expand Up @@ -43,7 +39,11 @@ struct IcebergEqualityDeleteFile {
//! The partition info if the equality delete has partition information
vector<IcebergPartitionInfo> partition_info;
int32_t partition_spec_id;
//! Raw equality delete values, keyed by Iceberg field-id. These are converted to bound expressions once the
//! scan output projection is known.
unordered_map<int32_t, vector<Value>> equality_values;
vector<IcebergEqualityDeleteRow> rows;
bool finalized = false;
};

struct IcebergEqualityDeleteData {
Expand Down
20 changes: 9 additions & 11 deletions src/include/planning/iceberg_multi_file_list.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,24 @@ struct IcebergMultiFileList : public MultiFileList {
const IcebergSnapshotScanInfo &GetSnapshot() const;
const IcebergTableSchema &GetSchema() const;
bool FinishedScanningDeletes() const;
bool EqualityDeletesFinalized() const;

void Bind(vector<LogicalType> &return_types, vector<string> &names);
unique_ptr<IcebergMultiFileList> PushdownInternal(ClientContext &context, TableFilterSet &new_filters,
vector<column_t> column_indexes) const;
void ScanPositionalDeleteFile(const BoundIcebergManifestEntry &manifest_entry, DataChunk &result) const;
void ScanEqualityDeleteFile(const BoundIcebergManifestEntry &manifest_entry, DataChunk &result,
vector<MultiFileColumnDefinition> &columns,
const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const;
void ScanDeleteFile(const BoundIcebergManifestEntry &entry, const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids, const vector<idx_t> &projection_ids) const;
vector<MultiFileColumnDefinition> &columns) const;
void FinalizeEqualityDeletes(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const;
void ScanDeleteFile(const BoundIcebergManifestEntry &entry) const;
void ScanPuffinFile(const BoundIcebergManifestEntry &entry) const;
unique_ptr<DeleteFilter> GetPositionalDeletesForFile(const string &file_path) const;
void EnumerateDeleteManifestEntries() const;
void ProcessDeletes(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids, const vector<idx_t> &projection_ids) const;
void ScanDeleteFiles(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids, const vector<idx_t> &projection_ids) const;
vector<reference<const IcebergEqualityDeleteRow>>
void ProcessDeletes() const;
void ScanDeleteFiles() const;
vector<reference<const IcebergEqualityDeleteFile>>
GetEqualityDeletesForFile(const BoundIcebergManifestEntry &manifest_entry) const;
void GetStatistics(vector<PartitionStatistics> &result) const;
const BoundIcebergManifestEntry &GetManifestEntry(idx_t file_id) const;
Expand Down
26 changes: 9 additions & 17 deletions src/planning/iceberg_multi_file_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,9 +964,9 @@ bool IcebergMultiFileList::ManifestMatchesFilter(const IcebergManifestFile &mani
return true;
}

vector<reference<const IcebergEqualityDeleteRow>>
vector<reference<const IcebergEqualityDeleteFile>>
IcebergMultiFileList::GetEqualityDeletesForFile(const BoundIcebergManifestEntry &bound_manifest_entry) const {
vector<reference<const IcebergEqualityDeleteRow>> result;
vector<reference<const IcebergEqualityDeleteFile>> result;

//! Look through all the equality delete files with a *higher* sequence number
auto &manifest_entry = bound_manifest_entry.entry;
Expand All @@ -993,7 +993,7 @@ IcebergMultiFileList::GetEqualityDeletesForFile(const BoundIcebergManifestEntry
}
}
}
result.insert(result.end(), file.rows.begin(), file.rows.end());
result.emplace_back(file);
}
}
return result;
Expand Down Expand Up @@ -1210,14 +1210,12 @@ void IcebergMultiFileList::EnumerateDeleteManifestEntries() const {
D_ASSERT(FinishedScanningDeletes());
}

void IcebergMultiFileList::ScanDeleteFiles(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const {
void IcebergMultiFileList::ScanDeleteFiles() const {
for (auto &bound_manifest_entry : delete_manifest_entries) {
auto &manifest_entry = bound_manifest_entry.entry;
auto &data_file = manifest_entry.data_file;
if (StringUtil::CIEquals(data_file.file_format, "parquet")) {
ScanDeleteFile(bound_manifest_entry, global_columns, global_column_ids, projection_ids);
ScanDeleteFile(bound_manifest_entry);
} else if (StringUtil::CIEquals(data_file.file_format, "puffin")) {
ScanPuffinFile(bound_manifest_entry);
} else {
Expand All @@ -1229,22 +1227,17 @@ void IcebergMultiFileList::ScanDeleteFiles(const vector<MultiFileColumnDefinitio
scanned_delete_manifests = true;
}

void IcebergMultiFileList::ProcessDeletes(const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const {
void IcebergMultiFileList::ProcessDeletes() const {
//! Enumerate the delete manifest entries, then read the delete files they reference.
//! EnumerateDeleteManifestEntries() is idempotent, so this is safe even if the entries were
//! already enumerated earlier (e.g. by the optimizer).
EnumerateDeleteManifestEntries();
if (!scanned_delete_manifests) {
ScanDeleteFiles(global_columns, global_column_ids, projection_ids);
ScanDeleteFiles();
}
}

void IcebergMultiFileList::ScanDeleteFile(const BoundIcebergManifestEntry &bound_manifest_entry,
const vector<MultiFileColumnDefinition> &global_columns,
const vector<ColumnIndex> &global_column_ids,
const vector<idx_t> &projection_ids) const {
void IcebergMultiFileList::ScanDeleteFile(const BoundIcebergManifestEntry &bound_manifest_entry) const {
auto &manifest_entry = bound_manifest_entry.entry;
auto &data_file = manifest_entry.data_file;
auto delete_file_path = data_file.file_path;
Expand Down Expand Up @@ -1315,8 +1308,7 @@ void IcebergMultiFileList::ScanDeleteFile(const BoundIcebergManifestEntry &bound
result.Reset();
delete_scan_function.function(context, function_input, result);
result.Flatten();
ScanEqualityDeleteFile(bound_manifest_entry, result, multi_file_local_state.reader->columns, global_columns,
global_column_ids, projection_ids);
ScanEqualityDeleteFile(bound_manifest_entry, result, multi_file_local_state.reader->columns);
} while (result.size() != 0);
}
}
Expand Down
13 changes: 10 additions & 3 deletions src/planning/iceberg_multi_file_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,13 @@ void IcebergMultiFileReader::FinalizeBind(MultiFileReaderData &reader_data, cons
lock_guard<mutex> delete_guard(multi_file_list.delete_lock);
if (!multi_file_list.FinishedScanningDeletes() ||
multi_file_list.transaction_delete_idx < multi_file_list.transaction_delete_manifests.size()) {
multi_file_list.ProcessDeletes(global_columns, global_column_ids, gstate.projection_ids);
multi_file_list.ProcessDeletes();
}
if (!multi_file_list.scanned_delete_manifests) {
multi_file_list.ScanDeleteFiles(global_columns, global_column_ids, gstate.projection_ids);
multi_file_list.ScanDeleteFiles();
}
if (!multi_file_list.EqualityDeletesFinalized()) {
multi_file_list.FinalizeEqualityDeletes(global_columns, global_column_ids, gstate.projection_ids);
}
reader.deletion_filter = multi_file_list.GetPositionalDeletesForFile(file_path);
}
Expand Down Expand Up @@ -334,7 +337,11 @@ void IcebergMultiFileReader::ApplyEqualityDeletes(ClientContext &context, DataCh
const vector<MultiFileColumnDefinition> &local_columns) {
// returns a vector<IcebergEqualityDeleteRow>
// IcebergEqualityDeleteRow = <field_id, FilterExpression>
auto delete_rows = multi_file_list.GetEqualityDeletesForFile(bound_manifest_entry);
auto delete_files = multi_file_list.GetEqualityDeletesForFile(bound_manifest_entry);
vector<reference<const IcebergEqualityDeleteRow>> delete_rows;
for (auto &file : delete_files) {
delete_rows.insert(delete_rows.end(), file.get().rows.begin(), file.get().rows.end());
}

if (delete_rows.empty()) {
return;
Expand Down
Loading
Loading