-
Notifications
You must be signed in to change notification settings - Fork 5
feat(query_engine): add join operator #1385
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
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| #include "silo/query_engine/operators/join_node.h" | ||
|
|
||
| #include <string_view> | ||
|
|
||
| #include <arrow/acero/exec_plan.h> | ||
| #include <arrow/acero/options.h> | ||
| #include <arrow/type.h> | ||
| #include <nlohmann/json.hpp> | ||
|
|
||
| #include "silo/common/panic.h" | ||
|
|
||
| namespace silo::query_engine::operators { | ||
|
|
||
| using arrow::acero::JoinType; | ||
|
|
||
| JoinNode::JoinNode( | ||
| QueryNodePtr left, | ||
| QueryNodePtr right, | ||
| std::vector<schema::ColumnIdentifier> left_keys, | ||
| std::vector<schema::ColumnIdentifier> right_keys, | ||
| JoinType join_type | ||
| ) | ||
| : left(std::move(left)), | ||
| right(std::move(right)), | ||
| left_keys(std::move(left_keys)), | ||
| right_keys(std::move(right_keys)), | ||
| join_type(join_type) { | ||
| // Equi-join key vectors are paired positionally (left_keys[i] == right_keys[i]), so they | ||
| // must have the same length | ||
| SILO_ASSERT_EQ(this->left_keys.size(), this->right_keys.size()); | ||
| } | ||
|
|
||
| std::string_view joinTypeToString(JoinType join_type) { | ||
| switch (join_type) { | ||
| case JoinType::INNER: | ||
| return "inner"; | ||
| case JoinType::LEFT_OUTER: | ||
| return "left"; | ||
| case JoinType::RIGHT_OUTER: | ||
| return "right"; | ||
| case JoinType::FULL_OUTER: | ||
| return "full"; | ||
| case JoinType::LEFT_SEMI: | ||
| return "leftSemi"; | ||
| case JoinType::RIGHT_SEMI: | ||
| return "rightSemi"; | ||
| case JoinType::LEFT_ANTI: | ||
| return "leftAnti"; | ||
| case JoinType::RIGHT_ANTI: | ||
| return "rightAnti"; | ||
| } | ||
| SILO_UNREACHABLE(); | ||
| } | ||
|
|
||
| std::vector<schema::ColumnIdentifier> JoinNode::getOutputSchema() const { | ||
| // Semi/anti joins act as a filter on one input and emit only that side's columns. | ||
| if (join_type == JoinType::LEFT_SEMI || join_type == JoinType::LEFT_ANTI) { | ||
| return left->getOutputSchema(); | ||
| } | ||
| if (join_type == JoinType::RIGHT_SEMI || join_type == JoinType::RIGHT_ANTI) { | ||
| return right->getOutputSchema(); | ||
| } | ||
| auto output = left->getOutputSchema(); | ||
| auto right_schema = right->getOutputSchema(); | ||
| output.insert(output.end(), right_schema.begin(), right_schema.end()); | ||
| return output; | ||
| } | ||
|
|
||
| arrow::Result<arrow::acero::ExecNode*> JoinNode::addToExecPlan( | ||
| arrow::acero::ExecPlan& plan, | ||
| const std::map<schema::TableName, std::shared_ptr<storage::Table>>& tables, | ||
| const config::QueryOptions& query_options | ||
| ) const { | ||
| ARROW_ASSIGN_OR_RAISE(auto* left_node, left->addToExecPlan(plan, tables, query_options)); | ||
| ARROW_ASSIGN_OR_RAISE(auto* right_node, right->addToExecPlan(plan, tables, query_options)); | ||
|
|
||
| std::vector<arrow::FieldRef> left_key_refs; | ||
| left_key_refs.reserve(left_keys.size()); | ||
| for (const auto& key : left_keys) { | ||
| left_key_refs.emplace_back(key.name); | ||
| } | ||
| std::vector<arrow::FieldRef> right_key_refs; | ||
| right_key_refs.reserve(right_keys.size()); | ||
| for (const auto& key : right_keys) { | ||
| right_key_refs.emplace_back(key.name); | ||
| } | ||
|
|
||
| const arrow::acero::HashJoinNodeOptions options{ | ||
| join_type, std::move(left_key_refs), std::move(right_key_refs) | ||
| }; | ||
| return arrow::acero::MakeExecNode("hashjoin", &plan, {left_node, right_node}, options); | ||
| } | ||
|
|
||
| nlohmann::json JoinNode::toJson() const { | ||
| nlohmann::json keys = nlohmann::json::array(); | ||
| for (size_t i = 0; i < left_keys.size(); ++i) { | ||
| keys.push_back({{"left", left_keys[i].name}, {"right", right_keys[i].name}}); | ||
| } | ||
| return { | ||
| {"type", nodeKindToString(kind())}, | ||
| {"joinType", joinTypeToString(join_type)}, | ||
| {"on", std::move(keys)}, | ||
| {"left", left->toJson()}, | ||
| {"right", right->toJson()}, | ||
| }; | ||
| } | ||
|
|
||
| } // namespace silo::query_engine::operators | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| #pragma once | ||
|
|
||
| #include <map> | ||
| #include <memory> | ||
| #include <vector> | ||
|
|
||
| #include <arrow/acero/options.h> | ||
| #include <arrow/result.h> | ||
| #include <nlohmann/json_fwd.hpp> | ||
|
|
||
| #include "silo/query_engine/operators/query_node.h" | ||
| #include "silo/schema/database_schema.h" | ||
| #include "silo/storage/table.h" | ||
|
|
||
| namespace silo::query_engine::operators { | ||
|
|
||
| /// Joins two child pipelines on an equality condition (equi-join), backed by | ||
| /// Arrow's hash-join. `left_keys[i]` is compared for equality against | ||
| /// `right_keys[i]`; all key pairs must match for a row pair to join. | ||
| /// | ||
| /// The output schema is the left child's columns followed by the right child's | ||
| /// columns, except for semi/anti joins which output only the columns of the | ||
| /// side they keep. Overlapping column names between the two sides are passed | ||
| /// through as-is (mirroring SQL `SELECT *`); rename or project them downstream | ||
| /// if distinct names are required. | ||
|
taepper marked this conversation as resolved.
|
||
| class JoinNode final : public QueryNode { | ||
| public: | ||
| QueryNodePtr left; | ||
| QueryNodePtr right; | ||
| std::vector<schema::ColumnIdentifier> left_keys; | ||
| std::vector<schema::ColumnIdentifier> right_keys; | ||
| arrow::acero::JoinType join_type; | ||
|
|
||
| JoinNode( | ||
| QueryNodePtr left, | ||
| QueryNodePtr right, | ||
| std::vector<schema::ColumnIdentifier> left_keys, | ||
| std::vector<schema::ColumnIdentifier> right_keys, | ||
| arrow::acero::JoinType join_type | ||
| ); | ||
|
|
||
| [[nodiscard]] std::vector<schema::ColumnIdentifier> getOutputSchema() const override; | ||
|
|
||
| [[nodiscard]] arrow::Result<arrow::acero::ExecNode*> addToExecPlan( | ||
| arrow::acero::ExecPlan& plan, | ||
| const std::map<schema::TableName, std::shared_ptr<storage::Table>>& tables, | ||
| const config::QueryOptions& query_options | ||
| ) const override; | ||
|
|
||
| [[nodiscard]] NodeKind kind() const override { return NodeKind::JOIN; } | ||
|
|
||
| [[nodiscard]] nlohmann::json toJson() const override; | ||
| }; | ||
|
|
||
| /// Human-readable name for a join type, used in query-plan JSON and diagnostics. | ||
| [[nodiscard]] std::string_view joinTypeToString(arrow::acero::JoinType join_type); | ||
|
|
||
| } // namespace silo::query_engine::operators | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.