1919
2020#include " iceberg/transaction.h"
2121
22+ #include < format>
23+ #include < memory>
24+ #include < string>
25+ #include < vector>
26+
2227#include " iceberg/expression/expressions.h"
2328#include " iceberg/expression/term.h"
2429#include " iceberg/sort_order.h"
30+ #include " iceberg/table_metadata.h"
2531#include " iceberg/test/matchers.h"
2632#include " iceberg/test/mock_catalog.h"
2733#include " iceberg/test/update_test_base.h"
2834#include " iceberg/transform.h"
2935#include " iceberg/type.h"
36+ #include " iceberg/update/fast_append.h"
37+ #include " iceberg/update/set_snapshot.h"
3038#include " iceberg/update/update_properties.h"
3139#include " iceberg/update/update_schema.h"
3240#include " iceberg/update/update_sort_order.h"
@@ -46,6 +54,47 @@ TEST_F(TransactionTest, CommitEmptyTransaction) {
4654 EXPECT_THAT (txn->Commit (), IsOk ());
4755}
4856
57+ TEST_F (TransactionTest, TemporaryTransactionDoesNotAttachToContext) {
58+ ICEBERG_UNWRAP_OR_FAIL (auto ctx,
59+ TransactionContext::Make (table_, TransactionKind::kUpdate ));
60+ ASSERT_FALSE (ctx->transaction .has_value ());
61+
62+ ICEBERG_UNWRAP_OR_FAIL (auto txn, Transaction::Make (ctx));
63+
64+ EXPECT_NE (txn, nullptr );
65+ EXPECT_FALSE (ctx->transaction .has_value ());
66+ }
67+
68+ TEST_F (TransactionTest, StandaloneCommitRequiresSharedOwnership) {
69+ ICEBERG_UNWRAP_OR_FAIL (auto ctx,
70+ TransactionContext::Make (table_, TransactionKind::kUpdate ));
71+ ICEBERG_UNWRAP_OR_FAIL (auto update, FastAppend::Make (table_->name ().name , ctx));
72+
73+ EXPECT_THAT (update->Commit (),
74+ ::testing::AllOf (
75+ IsError (ErrorKind::kInvalidArgument ),
76+ HasErrorMessage(" PendingUpdate must be owned by std::shared_ptr" )));
77+ }
78+
79+ TEST_F (TransactionTest, CommitNoOpUpdate) {
80+ ICEBERG_UNWRAP_OR_FAIL (auto txn, table_->NewTransaction ());
81+ ICEBERG_UNWRAP_OR_FAIL (auto update, txn->NewSetSnapshot ());
82+
83+ EXPECT_THAT (update->Commit (), IsOk ());
84+ EXPECT_THAT (txn->Commit (), IsOk ());
85+ }
86+
87+ TEST_F (TransactionTest, ApplyFailureFinalizesTransaction) {
88+ ICEBERG_UNWRAP_OR_FAIL (auto txn, table_->NewTransaction ());
89+ ICEBERG_UNWRAP_OR_FAIL (auto update, txn->NewFastAppend ());
90+ update->AppendFile (nullptr );
91+
92+ EXPECT_THAT (update->Commit (), IsError (ErrorKind::kValidationFailed ));
93+ EXPECT_THAT (txn->Commit (),
94+ ::testing::AllOf (IsError(ErrorKind::kValidationFailed ),
95+ HasErrorMessage(" Transaction already finalized" )));
96+ }
97+
4998TEST_F (TransactionTest, CommitTransactionWithPropertyUpdate) {
5099 ICEBERG_UNWRAP_OR_FAIL (auto txn, table_->NewTransaction ());
51100 ICEBERG_UNWRAP_OR_FAIL (auto update, txn->NewUpdateProperties ());
@@ -151,6 +200,39 @@ TEST_F(TransactionRetryTest, CommitRetrySucceedsAfterConflict) {
151200 EXPECT_EQ (update_call_count, 2 );
152201}
153202
203+ TEST_F (TransactionRetryTest, StandaloneCommitRetryReappliesUpdate) {
204+ std::vector<size_t > update_counts;
205+ ON_CALL (*mock_catalog_, UpdateTable (::testing::_, ::testing::_, ::testing::_))
206+ .WillByDefault (
207+ [this , &update_counts](const TableIdentifier&,
208+ const std::vector<std::unique_ptr<TableRequirement>>&,
209+ const std::vector<std::unique_ptr<TableUpdate>>& updates)
210+ -> Result<std::shared_ptr<Table>> {
211+ update_counts.push_back (updates.size ());
212+ if (update_counts.size () == 1 ) {
213+ return CommitFailed (" conflict on first attempt" );
214+ }
215+ return Table::Make (mock_table_->name (), mock_table_->metadata (),
216+ std::string (mock_table_->metadata_file_location ()),
217+ mock_table_->io (), mock_catalog_);
218+ });
219+ EXPECT_CALL (*mock_catalog_, LoadTable (::testing::_))
220+ .WillOnce ([this ](const TableIdentifier&) -> Result<std::shared_ptr<Table>> {
221+ auto builder = TableMetadataBuilder::BuildFrom (mock_table_->metadata ().get ());
222+ ICEBERG_ASSIGN_OR_RAISE (auto metadata, builder->Build ());
223+ return Table::Make (
224+ mock_table_->name (), std::shared_ptr<TableMetadata>(std::move (metadata)),
225+ std::format (" {}.refreshed" , mock_table_->metadata_file_location ()),
226+ mock_table_->io (), mock_catalog_);
227+ });
228+
229+ ICEBERG_UNWRAP_OR_FAIL (auto update, mock_table_->NewUpdateProperties ());
230+ update->Set (" retry.test" , " value" );
231+
232+ EXPECT_THAT (update->Commit (), IsOk ());
233+ EXPECT_THAT (update_counts, ::testing::ElementsAre (1U , 1U ));
234+ }
235+
154236TEST_F (TransactionRetryTest, CommitRetryExhausted) {
155237 int update_call_count = 0 ;
156238 ON_CALL (*mock_catalog_, UpdateTable (::testing::_, ::testing::_, ::testing::_))
0 commit comments