-
Notifications
You must be signed in to change notification settings - Fork 0
feat: complete Phase 5 — EXPLAIN command & IndexNestedLoopJoin #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| #include "execution/index_nested_loop_join.h" | ||
| #include <stdexcept> | ||
|
|
||
| namespace sql | ||
| { | ||
|
|
||
| namespace | ||
| { | ||
| std::string StripQualifier(const std::string &name) | ||
| { | ||
| size_t dot = name.find('.'); | ||
| if (dot == std::string::npos) | ||
| return name; | ||
| return name.substr(dot + 1); | ||
| } | ||
| } // namespace | ||
|
|
||
| void IndexNestedLoopJoin::Open() | ||
| { | ||
| if (outer_table_ == nullptr || inner_table_ == nullptr || inner_index_ == nullptr) | ||
| throw std::runtime_error("IndexNestedLoopJoin: null table or index"); | ||
|
|
||
| outer_col_idx_ = outer_table_->GetColumnIndex(StripQualifier(outer_col_)); | ||
| if (outer_col_idx_ < 0) | ||
| throw std::runtime_error("IndexNestedLoopJoin: unknown outer column: " + outer_col_); | ||
|
|
||
| outer_cursor_ = 0; | ||
| inner_matches_.clear(); | ||
| inner_cursor_ = 0; | ||
| } | ||
|
|
||
| bool IndexNestedLoopJoin::Next(Tuple *tuple) | ||
| { | ||
| const auto &outer_rows = outer_table_->GetTuples(); | ||
|
|
||
| while (true) | ||
| { | ||
| // Consume remaining inner matches for current outer row | ||
| while (inner_cursor_ < inner_matches_.size()) | ||
| { | ||
| size_t inner_row_idx = inner_matches_[inner_cursor_++]; | ||
| const Tuple &inner_row = inner_table_->GetTuple(inner_row_idx); | ||
|
|
||
| std::vector<Value> joined; | ||
| const Tuple &left_row = outer_is_left_ ? current_outer_ : inner_row; | ||
| const Tuple &right_row = outer_is_left_ ? inner_row : current_outer_; | ||
| joined.reserve(left_row.GetValueCount() + right_row.GetValueCount()); | ||
| for (size_t i = 0; i < left_row.GetValueCount(); ++i) | ||
| joined.push_back(left_row.GetValue(i)); | ||
| for (size_t i = 0; i < right_row.GetValueCount(); ++i) | ||
| joined.push_back(right_row.GetValue(i)); | ||
|
|
||
| *tuple = Tuple(std::move(joined)); | ||
| return true; | ||
| } | ||
|
|
||
| // Advance to next outer row | ||
| if (outer_cursor_ >= outer_rows.size()) | ||
| return false; | ||
|
|
||
| current_outer_ = outer_rows[outer_cursor_++]; | ||
| Value probe_key = current_outer_.GetValue(static_cast<size_t>(outer_col_idx_)); | ||
| inner_matches_ = inner_index_->Search(probe_key); | ||
| inner_cursor_ = 0; | ||
| } | ||
| } | ||
|
|
||
| void IndexNestedLoopJoin::Close() | ||
| { | ||
| outer_cursor_ = 0; | ||
| inner_matches_.clear(); | ||
| inner_cursor_ = 0; | ||
| outer_col_idx_ = -1; | ||
| } | ||
|
|
||
| } // namespace sql | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #pragma once | ||
|
|
||
| #include "execution/operator.h" | ||
| #include "storage/table.h" | ||
| #include "storage/btree.h" | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| namespace sql | ||
| { | ||
|
|
||
| // Index Nested-Loop Join: for each outer row, probes inner table via BTree index. | ||
| // Output column order is always (left_table_cols..., right_table_cols...) regardless | ||
| // of which side is outer; outer_is_left controls concatenation order. | ||
| class IndexNestedLoopJoin : public Operator | ||
| { | ||
| public: | ||
| // outer_table: table iterated row by row | ||
| // inner_table: table probed via index | ||
| // inner_index: BTree on inner_table.inner_col | ||
| // outer_col: column name in outer_table used as probe key | ||
| // inner_col: indexed column name in inner_table | ||
| // outer_is_left: if true, output is (outer || inner); else (inner || outer) | ||
| IndexNestedLoopJoin(Table *outer_table, Table *inner_table, | ||
| BTree *inner_index, | ||
| std::string outer_col, std::string inner_col, | ||
| bool outer_is_left) | ||
| : outer_table_(outer_table), inner_table_(inner_table), | ||
| inner_index_(inner_index), | ||
| outer_col_(std::move(outer_col)), inner_col_(std::move(inner_col)), | ||
| outer_is_left_(outer_is_left) {} | ||
|
|
||
| void Open() override; | ||
| bool Next(Tuple *tuple) override; | ||
| void Close() override; | ||
|
|
||
| private: | ||
| Table *outer_table_; | ||
| Table *inner_table_; | ||
| BTree *inner_index_; | ||
| std::string outer_col_; | ||
| std::string inner_col_; | ||
| bool outer_is_left_; | ||
|
|
||
| int outer_col_idx_ = -1; | ||
| size_t outer_cursor_ = 0; | ||
| Tuple current_outer_; | ||
| std::vector<size_t> inner_matches_; | ||
| size_t inner_cursor_ = 0; | ||
| }; | ||
|
|
||
| } // namespace sql |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,8 @@ namespace sql | |
| NOT, | ||
| DROP, | ||
| JOIN, | ||
| EXPLAIN, | ||
| INNER, | ||
|
|
||
|
Comment on lines
35
to
38
|
||
| // Identifiers and Literals | ||
| IDENTIFIER, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IndexNestedLoopJoin calls BTree::Search(probe_key) without guarding against type mismatch between outer_col and the indexed inner_col. Value::operator< throws on differing types, which can cause joins like INTEGER = VARCHAR to raise at runtime (NLJ currently treats this as “no match”). Consider checking the join key types (via schemas or by comparing probe_key.GetType()) and skipping/returning no matches when types differ, instead of calling into the BTree with an incompatible key.