diff --git a/src/include/storage/ducklake_catalog.hpp b/src/include/storage/ducklake_catalog.hpp index 3255eadcffa..903af03137c 100644 --- a/src/include/storage/ducklake_catalog.hpp +++ b/src/include/storage/ducklake_catalog.hpp @@ -95,6 +95,7 @@ class DuckLakeCatalog : public Catalog { PhysicalOperator &plan) override; PhysicalOperator &PlanDelete(ClientContext &context, PhysicalPlanGenerator &planner, LogicalDelete &op, PhysicalOperator &plan) override; + PhysicalOperator &PlanDelete(ClientContext &context, PhysicalPlanGenerator &planner, LogicalDelete &op) override; PhysicalOperator &PlanUpdate(ClientContext &context, PhysicalPlanGenerator &planner, LogicalUpdate &op, PhysicalOperator &plan) override; PhysicalOperator &PlanMergeInto(ClientContext &context, PhysicalPlanGenerator &planner, LogicalMergeInto &op, diff --git a/src/include/storage/ducklake_delete_filter.hpp b/src/include/storage/ducklake_delete_filter.hpp index 71a84dc5b55..afa4951d7ff 100644 --- a/src/include/storage/ducklake_delete_filter.hpp +++ b/src/include/storage/ducklake_delete_filter.hpp @@ -16,6 +16,7 @@ namespace duckdb { struct DuckLakeDeleteData { vector deleted_rows; vector snapshot_ids; + bool delete_all = false; //! For deletion scans: mapping from row_id to snapshot_id for rows that were deleted //! If scan_snapshot_map_uses_row_id is true, this is indexed by global row_id (from _ducklake_internal_row_id) //! Otherwise, it's indexed by file position diff --git a/src/include/storage/ducklake_inlined_data.hpp b/src/include/storage/ducklake_inlined_data.hpp index 0ce107fcdc8..4bc1d8eb15b 100644 --- a/src/include/storage/ducklake_inlined_data.hpp +++ b/src/include/storage/ducklake_inlined_data.hpp @@ -31,6 +31,7 @@ struct DuckLakeInlinedData { struct DuckLakeInlinedDataDeletes { set rows; + bool delete_all = false; }; //! Stores inlined file deletions for a table diff --git a/src/include/storage/ducklake_metadata_info.hpp b/src/include/storage/ducklake_metadata_info.hpp index 13c54b09a7f..d9e3f6bdd88 100644 --- a/src/include/storage/ducklake_metadata_info.hpp +++ b/src/include/storage/ducklake_metadata_info.hpp @@ -172,6 +172,7 @@ struct DuckLakeDeletedInlinedDataInfo { TableIndex table_id; string table_name; vector deleted_row_ids; + bool delete_all = false; }; //! Info for all inlined file deletions for a single table diff --git a/src/include/storage/ducklake_transaction.hpp b/src/include/storage/ducklake_transaction.hpp index 41c47815057..5b8a104d4d7 100644 --- a/src/include/storage/ducklake_transaction.hpp +++ b/src/include/storage/ducklake_transaction.hpp @@ -69,8 +69,10 @@ struct LocalTableChanges { void DropTransactionLocalFile(ClientContext &context, TableIndex table_id, const string &path); void AppendFiles(TableIndex table_id, vector files); void AppendInlinedData(ClientContext &context, TableIndex table_id, unique_ptr new_data); - void AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes); + void AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes, + bool delete_all = false); void DeleteFromLocalInlinedData(ClientContext &context, TableIndex table_id, set new_deletes); + void TruncateLocalInlinedData(TableIndex table_id); void AddColumnToLocalInlinedData(ClientContext &context, TableIndex table_id, const LogicalType &new_column_type, FieldIndex new_field_index, const Value &default_value); void RemoveColumnFromLocalInlinedData(ClientContext &context, TableIndex table_id, @@ -204,8 +206,10 @@ class DuckLakeTransaction : public Transaction, public enable_shared_from_this collection); - void AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes); + void AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes, + bool delete_all = false); void DeleteFromLocalInlinedData(TableIndex table_id, set new_deletes); + void TruncateLocalInlinedData(TableIndex table_id); void AddColumnToLocalInlinedData(TableIndex table_id, const LogicalType &new_column_type, FieldIndex new_field_index, const Value &default_value = Value()); void RemoveColumnFromLocalInlinedData(TableIndex table_id, LogicalIndex removed_column_index, diff --git a/src/include/storage/ducklake_truncate.hpp b/src/include/storage/ducklake_truncate.hpp new file mode 100644 index 00000000000..010b2f7d58d --- /dev/null +++ b/src/include/storage/ducklake_truncate.hpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// DuckDB +// +// storage/ducklake_truncate.hpp +// +//===----------------------------------------------------------------------===// + +#pragma once + +#include "duckdb/execution/physical_operator.hpp" +#include "storage/ducklake_table_entry.hpp" + +namespace duckdb { + +class DuckLakeTruncate : public PhysicalOperator { +public: + DuckLakeTruncate(PhysicalPlan &physical_plan, DuckLakeTableEntry &table); + + DuckLakeTableEntry &table; + +public: + SourceResultType GetDataInternal(ExecutionContext &context, DataChunk &chunk, + OperatorSourceInput &input) const override; + + bool IsSource() const override { + return true; + } + + unique_ptr GetGlobalSourceState(ClientContext &context) const override; + + string GetName() const override; + InsertionOrderPreservingMap ParamsToString() const override; +}; + +} // namespace duckdb diff --git a/src/storage/CMakeLists.txt b/src/storage/CMakeLists.txt index 41e6ac9ec7c..a5456812cfa 100644 --- a/src/storage/CMakeLists.txt +++ b/src/storage/CMakeLists.txt @@ -20,6 +20,7 @@ add_library( ducklake_storage.cpp ducklake_delete.cpp ducklake_deletion_vector.cpp + ducklake_truncate.cpp ducklake_multi_file_reader.cpp ducklake_partition_data.cpp ducklake_secret.cpp diff --git a/src/storage/ducklake_delete.cpp b/src/storage/ducklake_delete.cpp index 75b0e56d497..e84f0f983a2 100644 --- a/src/storage/ducklake_delete.cpp +++ b/src/storage/ducklake_delete.cpp @@ -694,7 +694,7 @@ PhysicalOperator &DuckLakeCatalog::PlanDelete(ClientContext &context, PhysicalPl row_id_indexes.push_back(bound_ref.index); } return DuckLakeDelete::PlanDelete(context, planner, op.table.Cast(), child_plan, - std::move(row_id_indexes), std::move(encryption_key)); + std::move(row_id_indexes), std::move(encryption_key), true); } } // namespace duckdb diff --git a/src/storage/ducklake_delete_filter.cpp b/src/storage/ducklake_delete_filter.cpp index e7c9add822f..750703e581c 100644 --- a/src/storage/ducklake_delete_filter.cpp +++ b/src/storage/ducklake_delete_filter.cpp @@ -52,6 +52,9 @@ DuckLakeDeleteFilter::DuckLakeDeleteFilter() : delete_data(make_shared_ptrdelete_all = false; delete_data->deleted_rows = std::move(scan_result.deleted_rows); delete_data->snapshot_ids = std::move(scan_result.snapshot_ids); } void DuckLakeDeleteFilter::Initialize(const DuckLakeInlinedDataDeletes &inlined_deletes) { + delete_data->delete_all = false; + if (inlined_deletes.delete_all) { + delete_data->delete_all = true; + delete_data->deleted_rows.clear(); + delete_data->snapshot_ids.clear(); + return; + } D_ASSERT(std::is_sorted(delete_data->deleted_rows.begin(), delete_data->deleted_rows.end())); auto mid_idx = delete_data->deleted_rows.size(); for (auto &idx : inlined_deletes.rows) { diff --git a/src/storage/ducklake_inlined_data_reader.cpp b/src/storage/ducklake_inlined_data_reader.cpp index ce2e3bcbf45..5a8a947836f 100644 --- a/src/storage/ducklake_inlined_data_reader.cpp +++ b/src/storage/ducklake_inlined_data_reader.cpp @@ -131,23 +131,25 @@ bool DuckLakeInlinedDataReader::TryInitializeScan(ClientContext &context, Global if (deletion_filter) { // map the deleted row-ids to the deleted ordinals to obtain the correct deleted rows auto &filter = reinterpret_cast(*deletion_filter); - vector deleted_ordinals; - auto &deleted_row_ids = filter.delete_data->deleted_rows; - idx_t current_idx = 0; - idx_t ordinal_position = 0; - for (auto &chunk : data->data->Chunks()) { - auto &row_id_vector = chunk.data.back(); - auto row_id_data = FlatVector::GetData(row_id_vector); - for (idx_t r = 0; r < chunk.size(); r++) { - auto row_id = NumericCast(row_id_data[r]); - if (current_idx < deleted_row_ids.size() && deleted_row_ids[current_idx] == row_id) { - deleted_ordinals.push_back(ordinal_position); - current_idx++; + if (!filter.delete_data->delete_all) { + vector deleted_ordinals; + auto &deleted_row_ids = filter.delete_data->deleted_rows; + idx_t current_idx = 0; + idx_t ordinal_position = 0; + for (auto &chunk : data->data->Chunks()) { + auto &row_id_vector = chunk.data.back(); + auto row_id_data = FlatVector::GetData(row_id_vector); + for (idx_t r = 0; r < chunk.size(); r++) { + auto row_id = NumericCast(row_id_data[r]); + if (current_idx < deleted_row_ids.size() && deleted_row_ids[current_idx] == row_id) { + deleted_ordinals.push_back(ordinal_position); + current_idx++; + } + ordinal_position++; } - ordinal_position++; } + filter.delete_data->deleted_rows = std::move(deleted_ordinals); } - filter.delete_data->deleted_rows = std::move(deleted_ordinals); } data->data->InitializeScan(state); } else { diff --git a/src/storage/ducklake_metadata_manager.cpp b/src/storage/ducklake_metadata_manager.cpp index 820f385afb3..56239cf43ce 100644 --- a/src/storage/ducklake_metadata_manager.cpp +++ b/src/storage/ducklake_metadata_manager.cpp @@ -2431,6 +2431,15 @@ string DuckLakeMetadataManager::WriteNewInlinedDeletes(const vector diff --git a/src/storage/ducklake_transaction.cpp b/src/storage/ducklake_transaction.cpp index ea038737910..c88c7a31783 100644 --- a/src/storage/ducklake_transaction.cpp +++ b/src/storage/ducklake_transaction.cpp @@ -227,12 +227,24 @@ void LocalTableChanges::AppendInlinedData(ClientContext &context, TableIndex tab } } -void LocalTableChanges::AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes) { +void LocalTableChanges::AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes, + bool delete_all) { + if (new_deletes.empty() && !delete_all) { + return; + } lock_guard guard(lock); auto &table_changes = changes[table_id]; auto &table_deletes = table_changes.new_inlined_data_deletes; auto entry = table_deletes.find(table_name); if (entry != table_deletes.end()) { + if (delete_all) { + entry->second->delete_all = true; + entry->second->rows.clear(); + return; + } + if (entry->second->delete_all) { + return; + } // merge deletes auto &existing_rows = entry->second->rows; for (auto &row_idx : new_deletes) { @@ -240,6 +252,7 @@ void LocalTableChanges::AddNewInlinedDeletes(TableIndex table_id, const string & } } else { auto new_data = make_uniq(); + new_data->delete_all = delete_all; new_data->rows = std::move(new_deletes); table_deletes.emplace(table_name, std::move(new_data)); } @@ -290,6 +303,22 @@ void LocalTableChanges::DeleteFromLocalInlinedData(ClientContext &context, Table inlined_data.row_ids = std::move(new_row_ids); } +void LocalTableChanges::TruncateLocalInlinedData(TableIndex table_id) { + lock_guard guard(lock); + auto entry = changes.find(table_id); + if (entry == changes.end()) { + throw InternalException("TruncateLocalInlinedData called but no transaction-local data exists for table"); + } + auto &table_changes = entry->second; + if (!table_changes.new_inlined_data) { + throw InternalException("TruncateLocalInlinedData called but no inlined data exists"); + } + table_changes.new_inlined_data.reset(); + if (table_changes.IsEmpty()) { + changes.erase(entry); + } +} + static void RemoveFieldStats(map &column_stats, const DuckLakeFieldId &field_id) { column_stats.erase(field_id.GetFieldIndex()); for (auto &child_id : field_id.Children()) { @@ -2174,8 +2203,11 @@ DuckLakeTransaction::GetNewInlinedDeletes(DuckLakeCommitState &commit_state) con DuckLakeDeletedInlinedDataInfo info; info.table_id = table_id; info.table_name = delete_entry.first; - for (auto &row_id : delete_entry.second->rows) { - info.deleted_row_ids.push_back(row_id); + info.delete_all = delete_entry.second->delete_all; + if (!info.delete_all) { + for (auto &row_id : delete_entry.second->rows) { + info.deleted_row_ids.push_back(row_id); + } } result.push_back(std::move(info)); } @@ -2700,11 +2732,9 @@ void DuckLakeTransaction::AppendInlinedData(TableIndex table_id, unique_ptr new_deletes) { - if (new_deletes.empty()) { - return; - } - local_changes.AddNewInlinedDeletes(table_id, table_name, std::move(new_deletes)); +void DuckLakeTransaction::AddNewInlinedDeletes(TableIndex table_id, const string &table_name, set new_deletes, + bool delete_all) { + local_changes.AddNewInlinedDeletes(table_id, table_name, std::move(new_deletes), delete_all); } void DuckLakeTransaction::DeleteFromLocalInlinedData(TableIndex table_id, set new_deletes) { @@ -2712,6 +2742,10 @@ void DuckLakeTransaction::DeleteFromLocalInlinedData(TableIndex table_id, set DuckLakeTruncate::GetGlobalSourceState(ClientContext &context) const { + return make_uniq(); +} + +SourceResultType DuckLakeTruncate::GetDataInternal(ExecutionContext &context, DataChunk &chunk, + OperatorSourceInput &input) const { + auto &gstate = input.global_state.Cast(); + if (gstate.finished) { + return SourceResultType::FINISHED; + } + gstate.finished = true; + + auto &transaction = DuckLakeTransaction::Get(context.client, table.catalog); + DuckLakeFunctionInfo read_info(table, transaction, transaction.GetSnapshot()); + auto transaction_local_files = transaction.GetTransactionLocalFiles(table.GetTableId()); + auto transaction_local_data = transaction.GetTransactionLocalInlinedData(table.GetTableId()); + DuckLakeMultiFileList file_list(read_info, std::move(transaction_local_files), transaction_local_data); + + uint64_t total_deleted_count = table.GetNetDataFileRowCount(transaction) + table.GetNetInlinedRowCount(transaction); + auto files = file_list.GetFilesExtended(); + for (auto &file_info : files) { + switch (file_info.data_type) { + case DuckLakeDataType::DATA_FILE: { + if (file_info.file_id.IsValid()) { + transaction.DropFile(table.GetTableId(), file_info.file_id, file_info.file.path); + } else { + transaction.DropTransactionLocalFile(table.GetTableId(), file_info.file.path); + } + break; + } + case DuckLakeDataType::INLINED_DATA: { + transaction.AddNewInlinedDeletes(table.GetTableId(), file_info.file.path, {}, true); + break; + } + case DuckLakeDataType::TRANSACTION_LOCAL_INLINED_DATA: { + transaction.TruncateLocalInlinedData(table.GetTableId()); + break; + } + default: + throw InternalException("Unsupported DuckLakeDataType in truncate"); + } + } + + chunk.SetCardinality(1); + chunk.SetValue(0, 0, Value::UBIGINT(total_deleted_count)); + return SourceResultType::FINISHED; +} + +string DuckLakeTruncate::GetName() const { + return "DUCKLAKE_TRUNCATE"; +} + +InsertionOrderPreservingMap DuckLakeTruncate::ParamsToString() const { + InsertionOrderPreservingMap result; + result["Table Name"] = table.name; + return result; +} + +PhysicalOperator &DuckLakeCatalog::PlanDelete(ClientContext &context, PhysicalPlanGenerator &planner, LogicalDelete &op) { + bool delete_all = false; + if (op.children.size() == 1 && op.children[0]->type == LogicalOperatorType::LOGICAL_GET) { + auto &get = op.children[0]->Cast(); + delete_all = get.table_filters.filters.empty(); + } + if (!delete_all) { + return Catalog::PlanDelete(context, planner, op); + } + auto &table = op.table.Cast(); + auto &transaction = DuckLakeTransaction::Get(context, *this); + if (transaction.HasAnyLocalChanges(table.GetTableId())) { + return Catalog::PlanDelete(context, planner, op); + } + if (op.return_chunk) { + throw BinderException("RETURNING clause not yet supported for deletion of a DuckLake table"); + } + return planner.Make(table); +} + +} // namespace duckdb diff --git a/test/sql/delete/ducklake_delete_all_simple.test b/test/sql/delete/ducklake_delete_all_simple.test new file mode 100644 index 00000000000..ae834ebfda0 --- /dev/null +++ b/test/sql/delete/ducklake_delete_all_simple.test @@ -0,0 +1,37 @@ +# name: test/sql/delete/ducklake_delete_all_simple.test +# description: simple delete-all on ducklake table + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_delete_all_simple') + +statement ok +CREATE TABLE ducklake.delete_all_test(i INTEGER); + +statement ok +INSERT INTO ducklake.delete_all_test FROM range(10); + +query II +EXPLAIN DELETE FROM ducklake.delete_all_test; +---- +physical_plan :.*DUCKLAKE_TRUNCATE.* + +query I +SELECT COUNT(*) FROM ducklake.delete_all_test; +---- +10 + +statement ok +DELETE FROM ducklake.delete_all_test; + +query I +SELECT COUNT(*) FROM ducklake.delete_all_test; +---- +0 diff --git a/test/sql/delete/ducklake_truncate_simple.test b/test/sql/delete/ducklake_truncate_simple.test new file mode 100644 index 00000000000..cd9f808a37d --- /dev/null +++ b/test/sql/delete/ducklake_truncate_simple.test @@ -0,0 +1,37 @@ +# name: test/sql/delete/ducklake_truncate_simple.test +# description: simple TRUNCATE on ducklake table + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_simple') + +statement ok +CREATE TABLE ducklake.truncate_test(i INTEGER); + +statement ok +INSERT INTO ducklake.truncate_test FROM range(10); + +query II +EXPLAIN TRUNCATE ducklake.truncate_test; +---- +physical_plan :.*DUCKLAKE_TRUNCATE.* + +query I +SELECT COUNT(*) FROM ducklake.truncate_test; +---- +10 + +statement ok +TRUNCATE ducklake.truncate_test; + +query I +SELECT COUNT(*) FROM ducklake.truncate_test; +---- +0 diff --git a/test/sql/delete/truncate_table_inlined.test b/test/sql/delete/truncate_table_inlined.test new file mode 100644 index 00000000000..12b4fed1654 --- /dev/null +++ b/test/sql/delete/truncate_table_inlined.test @@ -0,0 +1,50 @@ +# name: test/sql/delete/truncate_table_inlined.test +# description: Verify TRUNCATE works with inlined data +# group: [delete] + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_inlined_files') + +statement ok +CALL ducklake.set_option('data_inlining_row_limit', 1000); + +statement ok +CREATE TABLE ducklake.test_inline AS SELECT i id FROM range(8) t(i); + +query I +TRUNCATE ducklake.test_inline +---- +8 + +query I +SELECT COUNT(*) FROM ducklake.test_inline +---- +0 + +statement ok +INSERT INTO ducklake.test_inline VALUES (1), (2), (3); + +statement ok +BEGIN + +query I +TRUNCATE ducklake.test_inline +---- +3 + +statement ok +ROLLBACK + +query I +SELECT COUNT(*) FROM ducklake.test_inline +---- +3 diff --git a/test/sql/delete/truncate_table_return_value.test b/test/sql/delete/truncate_table_return_value.test new file mode 100644 index 00000000000..4b4908b8505 --- /dev/null +++ b/test/sql/delete/truncate_table_return_value.test @@ -0,0 +1,36 @@ +# name: test/sql/delete/truncate_table_return_value.test +# description: Verify TRUNCATE return value including tables with existing deletes +# group: [delete] + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_return_value_files') + +statement ok +CREATE TABLE ducklake.return_value_test AS SELECT i id FROM range(10000) t(i); + +statement ok +DELETE FROM ducklake.return_value_test WHERE id%8=0 + +query II +SELECT COUNT(*), SUM(id) FROM ducklake.return_value_test +---- +8750 43750000 + +query I +TRUNCATE ducklake.return_value_test +---- +8750 + +query II +SELECT COUNT(*), SUM(id) FROM ducklake.return_value_test +---- +0 NULL diff --git a/test/sql/delete/truncate_table_rollback.test b/test/sql/delete/truncate_table_rollback.test new file mode 100644 index 00000000000..574d7b06ca7 --- /dev/null +++ b/test/sql/delete/truncate_table_rollback.test @@ -0,0 +1,39 @@ +# name: test/sql/delete/truncate_table_rollback.test +# description: Verify TRUNCATE rollback behavior +# group: [delete] + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_rollback_files') + +statement ok +CREATE TABLE ducklake.rollback_test AS SELECT i id FROM range(100) t(i); + +statement ok +BEGIN + +query I +TRUNCATE ducklake.rollback_test +---- +100 + +query II +SELECT COUNT(*), SUM(id) FROM ducklake.rollback_test +---- +0 NULL + +statement ok +ROLLBACK + +query II +SELECT COUNT(*), SUM(id) FROM ducklake.rollback_test +---- +100 4950 diff --git a/test/sql/delete/truncate_table_time_travel.test b/test/sql/delete/truncate_table_time_travel.test new file mode 100644 index 00000000000..765e3426393 --- /dev/null +++ b/test/sql/delete/truncate_table_time_travel.test @@ -0,0 +1,41 @@ +# name: test/sql/delete/truncate_table_time_travel.test +# description: Verify time travel works correctly across TRUNCATE +# group: [delete] + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_time_travel_files', DATA_INLINING_ROW_LIMIT 0) + +statement ok +CREATE TABLE ducklake.time_travel_test(id INTEGER); + +statement ok +INSERT INTO ducklake.time_travel_test VALUES (1), (2), (3); + +statement ok +SET VARIABLE pre_truncate_snapshot = (SELECT max(snapshot_id) FROM ducklake.snapshots()); + +query I +TRUNCATE ducklake.time_travel_test +---- +3 + +query I +SELECT COUNT(*) FROM ducklake.time_travel_test +---- +0 + +query I +SELECT id FROM ducklake.time_travel_test AT (VERSION => getvariable('pre_truncate_snapshot')) ORDER BY ALL +---- +1 +2 +3 diff --git a/test/sql/delete/truncate_table_transactionality.test b/test/sql/delete/truncate_table_transactionality.test new file mode 100644 index 00000000000..ca8193b8e4b --- /dev/null +++ b/test/sql/delete/truncate_table_transactionality.test @@ -0,0 +1,44 @@ +# name: test/sql/delete/truncate_table_transactionality.test +# description: Verify uncommitted TRUNCATE is not visible to other connections +# group: [delete] + +require ducklake + +require parquet + +test-env DUCKLAKE_CONNECTION __TEST_DIR__/{UUID}.db + +test-env DATA_PATH __TEST_DIR__ + + +statement ok +ATTACH 'ducklake:${DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '${DATA_PATH}/ducklake_truncate_transactionality_files') + +statement ok +CREATE TABLE ducklake.txn_test AS SELECT i id FROM range(25) t(i); + +statement ok con1 +BEGIN + +query I con1 +TRUNCATE ducklake.txn_test +---- +25 + +query II con1 +SELECT COUNT(*), SUM(id) FROM ducklake.txn_test +---- +0 NULL + +query II con2 +SELECT COUNT(*), SUM(id) FROM ducklake.txn_test +---- +25 300 + +statement ok con1 +COMMIT + +query II con2 +SELECT COUNT(*), SUM(id) FROM ducklake.txn_test +---- +0 NULL