Skip to content

Commit 9543078

Browse files
authored
p4: verify total count for terrier bench + more comments for starter code (#656)
Signed-off-by: Alex Chi <[email protected]>
1 parent 1d6ac6d commit 9543078

File tree

9 files changed

+285
-32
lines changed

9 files changed

+285
-32
lines changed

src/concurrency/transaction_manager.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ auto TransactionManager::VerifyTxn(Transaction *txn) -> bool { return true; }
5353
auto TransactionManager::Commit(Transaction *txn) -> bool {
5454
std::unique_lock<std::mutex> commit_lck(commit_mutex_);
5555

56+
// TODO(fall2023): acquire commit ts!
57+
5658
if (txn->state_ != TransactionState::RUNNING) {
5759
throw Exception("txn not in running state");
5860
}

src/concurrency/transaction_manager_impl.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@
2525

2626
namespace bustub {
2727

28+
auto TransactionManager::UpdateUndoLink(RID rid, std::optional<UndoLink> prev_link,
29+
std::function<bool(std::optional<UndoLink>)> &&check) -> bool {
30+
std::function<bool(std::optional<VersionUndoLink>)> wrapper_func =
31+
[check](std::optional<VersionUndoLink> link) -> bool {
32+
if (link.has_value()) {
33+
return check(link->prev_);
34+
}
35+
return check(std::nullopt);
36+
};
37+
return UpdateVersionLink(rid, prev_link.has_value() ? std::make_optional(VersionUndoLink{*prev_link}) : std::nullopt,
38+
check != nullptr ? wrapper_func : nullptr);
39+
}
40+
2841
auto TransactionManager::UpdateVersionLink(RID rid, std::optional<VersionUndoLink> prev_version,
2942
std::function<bool(std::optional<VersionUndoLink>)> &&check) -> bool {
3043
std::unique_lock<std::shared_mutex> lck(version_info_mutex_);

src/include/concurrency/transaction_manager.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,40 @@ class TransactionManager {
7979
*/
8080
void Abort(Transaction *txn);
8181

82+
/**
83+
* @brief Use this function before task 4.2. Update an undo link that links table heap tuple to the first undo log.
84+
* Before updating, `check` function will be called to ensure validity.
85+
*/
86+
auto UpdateUndoLink(RID rid, std::optional<UndoLink> prev_link,
87+
std::function<bool(std::optional<UndoLink>)> &&check = nullptr) -> bool;
88+
89+
/**
90+
* @brief Use this function after task 4.2. Update an undo link that links table heap tuple to the first undo log.
91+
* Before updating, `check` function will be called to ensure validity.
92+
*/
8293
auto UpdateVersionLink(RID rid, std::optional<VersionUndoLink> prev_version,
8394
std::function<bool(std::optional<VersionUndoLink>)> &&check = nullptr) -> bool;
8495

85-
/** The same as `GetVersionLink`, except that we extracted the undo link field out. */
96+
/** @brief Get the first undo log of a table heap tuple. Use this before task 4.2 */
8697
auto GetUndoLink(RID rid) -> std::optional<UndoLink>;
8798

88-
/** You only need this starting task 4.2 */
99+
/** @brief Get the first undo log of a table heap tuple. Use this after task 4.2 */
89100
auto GetVersionLink(RID rid) -> std::optional<VersionUndoLink>;
90101

102+
/** @brief Access the transaction undo log buffer and get the undo log. Return nullopt if the txn does not exist. Will
103+
* still throw an exception if the index is out of range. */
91104
auto GetUndoLogOptional(UndoLink link) -> std::optional<UndoLog>;
92105

106+
/** @brief Access the transaction undo log buffer and get the undo log. Except when accessing the current txn buffer,
107+
* you should always call this function to get the undo log instead of manually retrieve the txn shared_ptr and access
108+
* the buffer. */
93109
auto GetUndoLog(UndoLink link) -> UndoLog;
94110

111+
/** @brief Get the lowest read timestamp in the system. */
95112
auto GetWatermark() -> timestamp_t { return running_txns_.GetWatermark(); }
96113

114+
/** @brief Stop-the-world garbage collection. Will be called only when all transactions are not accessing the table
115+
* heap. */
97116
void GarbageCollection();
98117

99118
/** protects txn map */
@@ -112,7 +131,8 @@ class TransactionManager {
112131

113132
/** protects version info */
114133
std::shared_mutex version_info_mutex_;
115-
/** Stores the previous version of each tuple in the table heap. */
134+
/** Stores the previous version of each tuple in the table heap. Do not directly access this field. Use the helper
135+
* functions in `transaction_manager_impl.cpp`. */
116136
std::unordered_map<page_id_t, std::shared_ptr<PageVersionInfo>> version_info_;
117137

118138
/** Stores all the read_ts of running txns so as to facilitate garbage collection. */
@@ -129,6 +149,8 @@ class TransactionManager {
129149
std::atomic<txn_id_t> next_txn_id_{TXN_START_ID};
130150

131151
private:
152+
/** @brief Verify if a txn satisfies serializability. We will not test this function and you can change / remove it as
153+
* you want. */
132154
auto VerifyTxn(Transaction *txn) -> bool;
133155
};
134156

src/include/storage/table/table_heap.h

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929

3030
namespace bustub {
3131

32+
class TablePage;
33+
3234
/**
3335
* TableHeap represents a physical table on disk.
3436
* This is just a doubly-linked list of pages.
@@ -77,20 +79,27 @@ class TableHeap {
7779
*/
7880
auto GetTupleMeta(RID rid) -> TupleMeta;
7981

80-
/** @return the iterator of this table, use this for project 3 */
82+
/** @return the iterator of this table. When this iterator is created, it will record the current last tuple in the
83+
* table heap, and the iterator will stop at that point, in order to avoid halloween problem. You usually will need to
84+
* use this function for project 3. Given that you have already implemented your project 4 update executor as a
85+
* pipeline breaker, you may use `MakeEagerIterator` to test whether the update executor is implemented correctly.
86+
* There should be no difference between this function and `MakeEagerIterator` in project 4 if everything is
87+
* implemented correctly. */
8188
auto MakeIterator() -> TableIterator;
8289

83-
/** @return the iterator of this table, use this for project 4 except updates */
90+
/** @return the iterator of this table. The iterator will stop at the last tuple at the time of iterating. */
8491
auto MakeEagerIterator() -> TableIterator;
8592

8693
/** @return the id of the first page of this table */
8794
inline auto GetFirstPageId() const -> page_id_t { return first_page_id_; }
8895

8996
/**
90-
* Update a tuple in place. SHOULD NOT BE USED UNLESS YOU WANT TO OPTIMIZE FOR PROJECT 4.
97+
* Update a tuple in place. Should NOT be used in project 3. Implement your project 3 update executor as delete and
98+
* insert. You will need to use this function in project 4.
9199
* @param meta new tuple meta
92100
* @param tuple new tuple
93-
* @param[out] rid the rid of the tuple to be updated
101+
* @param rid the rid of the tuple to be updated
102+
* @param check the check to run before actually update.
94103
*/
95104
auto UpdateTupleInPlace(const TupleMeta &meta, const Tuple &tuple, RID rid,
96105
std::function<bool(const TupleMeta &meta, const Tuple &table, RID rid)> &&check = nullptr)
@@ -103,6 +112,19 @@ class TableHeap {
103112
return std::unique_ptr<TableHeap>(new TableHeap(create_table_heap));
104113
}
105114

115+
// The below functions are useful only when you want to implement abort in a way that removes an undo log from the
116+
// version chain. DO NOT USE THEM if you are unsure what they are supposed to do.
117+
118+
auto AcquireTablePageReadLock(RID rid) -> ReadPageGuard;
119+
120+
auto AcquireTablePageWriteLock(RID rid) -> WritePageGuard;
121+
122+
void UpdateTupleInPlaceWithLockAcquired(const TupleMeta &meta, const Tuple &tuple, RID rid, TablePage *page);
123+
124+
auto GetTupleWithLockAcquired(RID rid, TablePage *page) -> std::pair<TupleMeta, Tuple>;
125+
126+
auto GetTupleMetaWithLockAcquired(RID rid, TablePage *page) -> TupleMeta;
127+
106128
private:
107129
/** Used for binder tests */
108130
explicit TableHeap(bool create_table_heap = false);

src/storage/table/table_heap.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,21 @@ auto TableHeap::UpdateTupleInPlace(const TupleMeta &meta, const Tuple &tuple, RI
134134
return false;
135135
}
136136

137+
auto TableHeap::AcquireTablePageReadLock(RID rid) -> ReadPageGuard { return bpm_->FetchPageRead(rid.GetPageId()); }
138+
139+
auto TableHeap::AcquireTablePageWriteLock(RID rid) -> WritePageGuard { return bpm_->FetchPageWrite(rid.GetPageId()); }
140+
141+
void TableHeap::UpdateTupleInPlaceWithLockAcquired(const TupleMeta &meta, const Tuple &tuple, RID rid,
142+
TablePage *page) {
143+
page->UpdateTupleInPlaceUnsafe(meta, tuple, rid);
144+
}
145+
146+
auto TableHeap::GetTupleWithLockAcquired(RID rid, TablePage *page) -> std::pair<TupleMeta, Tuple> {
147+
auto [meta, tuple] = page->GetTuple(rid);
148+
tuple.rid_ = rid;
149+
return std::make_pair(meta, std::move(tuple));
150+
}
151+
152+
auto TableHeap::GetTupleMetaWithLockAcquired(RID rid, TablePage *page) -> TupleMeta { return page->GetTupleMeta(rid); }
153+
137154
} // namespace bustub

test/txn/txn_executor_test.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#include "execution/execution_common.h"
22
#include "txn_common.h" // NOLINT
33

4-
// NO HIDDEN TEST IN THIS FILE!!!
5-
64
namespace bustub {
75

86
// NOLINTBEGIN(bugprone-unchecked-optional-access)

test/txn/txn_scan_test.cpp

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,73 @@ TEST(TxnScanTest, DISABLED_TupleReconstructTest) { // NOLINT
3232
}
3333
}
3434
{
35-
fmt::println(stderr, "(partial) C: reconstruct 4 records, one of them is empty, one of them is full change");
35+
fmt::println(stderr, "C: reconstruct 4 records, one of them is empty, one of them is full change");
3636
auto base_tuple = Tuple{{Int(0), Double(1), BoolNull()}, schema.get()};
3737
auto base_meta = TupleMeta{2333, false};
3838
auto undo_log_1_schema = ParseCreateStatement("");
3939
auto undo_log_1 = UndoLog{false, {false, false, false}, Tuple{{}, undo_log_1_schema.get()}};
40+
auto undo_log_2_schema = ParseCreateStatement("b double");
41+
auto undo_log_2 = UndoLog{false, {false, true, false}, Tuple{{Double(2)}, undo_log_2_schema.get()}};
42+
auto undo_log_3_schema = ParseCreateStatement("a integer,c boolean");
43+
auto undo_log_3 = UndoLog{false, {true, false, true}, Tuple{{Int(3), Bool(false)}, undo_log_3_schema.get()}};
44+
auto undo_log_4 = UndoLog{false, {true, true, true}, Tuple{{Int(4), Double(4), Bool(true)}, schema.get()}};
4045
{
4146
fmt::println(stderr, "C1: verify 1st record");
4247
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {undo_log_1});
4348
ASSERT_TRUE(tuple.has_value());
4449
VerifyTuple(schema.get(), *tuple, {Int(0), Double(1), BoolNull()});
4550
}
51+
{
52+
fmt::println(stderr, "C2: verify 2nd record");
53+
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {undo_log_1, undo_log_2});
54+
ASSERT_TRUE(tuple.has_value());
55+
VerifyTuple(schema.get(), *tuple, {Int(0), Double(2), BoolNull()});
56+
}
57+
{
58+
fmt::println(stderr, "C3: verify 3rd record");
59+
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {undo_log_1, undo_log_2, undo_log_3});
60+
ASSERT_TRUE(tuple.has_value());
61+
VerifyTuple(schema.get(), *tuple, {Int(3), Double(2), Bool(false)});
62+
}
63+
{
64+
fmt::println(stderr, "C4: verify 4th record");
65+
auto tuple =
66+
ReconstructTuple(schema.get(), base_tuple, base_meta, {undo_log_1, undo_log_2, undo_log_3, undo_log_4});
67+
ASSERT_TRUE(tuple.has_value());
68+
VerifyTuple(schema.get(), *tuple, {Int(4), Double(4), Bool(true)});
69+
}
4670
}
4771
{
48-
fmt::println(stderr, "(partial) D: reconstruct 4 records, two of them are delete");
72+
fmt::println(stderr, "D: reconstruct 4 records, two of them are delete");
4973
auto base_tuple = Tuple{{Int(0), Double(1), BoolNull()}, schema.get()};
5074
auto base_meta = TupleMeta{2333, false};
5175
auto delete_schema = ParseCreateStatement("");
5276
auto delete_log = UndoLog{true, {false, false, false}, Tuple{{}, delete_schema.get()}};
77+
auto undo_log_1 = UndoLog{false, {true, true, true}, Tuple{{Int(1), Double(1), Bool(false)}, schema.get()}};
78+
auto undo_log_2 = UndoLog{false, {true, true, true}, Tuple{{IntNull(), DoubleNull(), BoolNull()}, schema.get()}};
5379
{
5480
fmt::println(stderr, "D1: apply delete record");
5581
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {delete_log});
5682
ASSERT_FALSE(tuple.has_value());
5783
}
84+
{
85+
fmt::println(stderr, "D2: verify 2nd record");
86+
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {delete_log, undo_log_1});
87+
ASSERT_TRUE(tuple.has_value());
88+
VerifyTuple(schema.get(), *tuple, {Int(1), Double(1), Bool(false)});
89+
}
90+
{
91+
fmt::println(stderr, "D3: apply delete record");
92+
auto tuple = ReconstructTuple(schema.get(), base_tuple, base_meta, {delete_log, undo_log_1, delete_log});
93+
ASSERT_FALSE(tuple.has_value());
94+
}
95+
{
96+
fmt::println(stderr, "D4: verify 4th record");
97+
auto tuple =
98+
ReconstructTuple(schema.get(), base_tuple, base_meta, {delete_log, undo_log_1, delete_log, undo_log_2});
99+
ASSERT_TRUE(tuple.has_value());
100+
VerifyTuple(schema.get(), *tuple, {IntNull(), DoubleNull(), BoolNull()});
101+
}
58102
}
59103
}
60104

@@ -161,7 +205,18 @@ TEST(TxnScanTest, DISABLED_ScanTest) { // NOLINT
161205
WithTxn(txn0, QueryShowResult(*bustub, _var, _txn, query, IntResult{}));
162206
fmt::println(stderr, "B: Verify txn1");
163207
WithTxn(txn1, QueryShowResult(*bustub, _var, _txn, query, IntResult{{2}, {4}, {7}}));
164-
// hidden tests...
208+
209+
// hidden tests... this is the only hidden test case among task 1, 2, 3. We recommend you to implement `TxnMgrDbg`
210+
// function, draw the version chain out, and think of what should be read by each txn.
211+
212+
// fmt::println(stderr, "C: Verify txn2");
213+
// WithTxn(txn2, QueryHideResult(*bustub, _var, _txn, query, IntResult{}));
214+
// fmt::println(stderr, "D: Verify txn3");
215+
// WithTxn(txn3, QueryHideResult(*bustub, _var, _txn, query, IntResult{}));
216+
// fmt::println(stderr, "E: Verify txn4");
217+
// WithTxn(txn4, QueryHideResult(*bustub, _var, _txn, query, IntResult{}));
218+
// fmt::println(stderr, "F: Verify txn5");
219+
// WithTxn(txn5, QueryHideResult(*bustub, _var, _txn, query, IntResult{}));
165220
}
166221

167222
// NOLINTEND(bugprone-unchecked-optional-access))

test/txn/txn_timestamp_test.cpp

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
#include "txn_common.h" // NOLINT
1+
#include <chrono> // NOLINT
2+
#include <cstdio>
3+
#include <exception>
4+
#include <functional>
5+
#include <future> // NOLINT
6+
#include <memory>
7+
#include <stdexcept>
8+
#include <thread> // NOLINT
9+
10+
#include "common/bustub_instance.h"
11+
#include "concurrency/transaction.h"
12+
#include "concurrency/transaction_manager.h"
13+
#include "concurrency/watermark.h"
14+
#include "fmt/core.h"
15+
#include "gtest/gtest.h"
216

317
namespace bustub {
418

@@ -36,19 +50,96 @@ TEST(TxnTsTest, DISABLED_WatermarkPerformance) { // NOLINT
3650

3751
TEST(TxnTsTest, DISABLED_TimestampTracking) { // NOLINT
3852
auto bustub = std::make_unique<BustubInstance>();
53+
3954
auto txn0 = bustub->txn_manager_->Begin();
4055
ASSERT_EQ(txn0->GetReadTs(), 0);
4156
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 0);
42-
bustub->txn_manager_->Commit(txn0);
43-
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 1);
57+
58+
{
59+
auto txn_store_1 = bustub->txn_manager_->Begin();
60+
ASSERT_EQ(txn_store_1->GetReadTs(), 0);
61+
bustub->txn_manager_->Commit(txn_store_1);
62+
ASSERT_EQ(txn_store_1->GetCommitTs(), 1);
63+
}
64+
65+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 0);
4466

4567
auto txn1 = bustub->txn_manager_->Begin();
4668
ASSERT_EQ(txn1->GetReadTs(), 1);
69+
70+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 0);
71+
72+
{
73+
auto txn_store_2 = bustub->txn_manager_->Begin();
74+
ASSERT_EQ(txn_store_2->GetReadTs(), 1);
75+
bustub->txn_manager_->Commit(txn_store_2);
76+
ASSERT_EQ(txn_store_2->GetCommitTs(), 2);
77+
}
78+
79+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 0);
80+
81+
auto txn2 = bustub->txn_manager_->Begin();
82+
ASSERT_EQ(txn2->GetReadTs(), 2);
83+
84+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 0);
85+
86+
bustub->txn_manager_->Abort(txn0);
4787
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 1);
48-
bustub->txn_manager_->Abort(txn1);
88+
89+
{
90+
auto txn_store_3 = bustub->txn_manager_->Begin();
91+
ASSERT_EQ(txn_store_3->GetReadTs(), 2);
92+
bustub->txn_manager_->Commit(txn_store_3);
93+
ASSERT_EQ(txn_store_3->GetCommitTs(), 3);
94+
}
95+
96+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 1);
97+
98+
auto txn3 = bustub->txn_manager_->Begin();
99+
ASSERT_EQ(txn3->GetReadTs(), 3);
100+
49101
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 1);
50102

51-
// test case continues on Gradescope...
103+
bustub->txn_manager_->Abort(txn1);
104+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 2);
105+
bustub->txn_manager_->Abort(txn2);
106+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 3);
107+
108+
{
109+
auto txn_store_4 = bustub->txn_manager_->Begin();
110+
ASSERT_EQ(txn_store_4->GetReadTs(), 3);
111+
bustub->txn_manager_->Commit(txn_store_4);
112+
ASSERT_EQ(txn_store_4->GetCommitTs(), 4);
113+
}
114+
115+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 3);
116+
117+
auto txn4 = bustub->txn_manager_->Begin();
118+
ASSERT_EQ(txn4->GetReadTs(), 4);
119+
120+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 3);
121+
bustub->txn_manager_->Abort(txn3);
122+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 4);
123+
bustub->txn_manager_->Abort(txn4);
124+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 4);
125+
126+
{
127+
auto txn_store_5 = bustub->txn_manager_->Begin();
128+
ASSERT_EQ(txn_store_5->GetTransactionState(), TransactionState::RUNNING);
129+
ASSERT_EQ(txn_store_5->GetReadTs(), 4);
130+
bustub->txn_manager_->Commit(txn_store_5);
131+
ASSERT_EQ(txn_store_5->GetTransactionState(), TransactionState::COMMITTED);
132+
}
133+
134+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 5);
135+
136+
auto txn5 = bustub->txn_manager_->Begin();
137+
ASSERT_EQ(txn5->GetTransactionState(), TransactionState::RUNNING);
138+
ASSERT_EQ(txn5->GetReadTs(), 5);
139+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 5);
140+
bustub->txn_manager_->Abort(txn5);
141+
ASSERT_EQ(txn5->GetTransactionState(), TransactionState::ABORTED);
142+
ASSERT_EQ(bustub->txn_manager_->GetWatermark(), 5);
52143
}
53144

54145
// NOLINTEND(bugprone-unchecked-optional-access))

0 commit comments

Comments
 (0)