@@ -17,86 +17,82 @@ class SetOpOperator : public Operator {
1717public:
1818 SetOpOperator (Operator* left, Operator* right, uint8_t op, bool all,
1919 bool parallel_open = false , ThreadPool* pool = nullptr )
20- : left_(left), right_(right), op_(op), all_(all),
20+ : op_(op), all_(all), parallel_open_(parallel_open), pool_(pool) {
21+ children_.push_back (left);
22+ children_.push_back (right);
23+ }
24+
25+ explicit SetOpOperator (std::vector<Operator*> children,
26+ bool parallel_open = false , ThreadPool* pool = nullptr )
27+ : children_(std::move(children)), op_(SET_OP_UNION ), all_(true ),
2128 parallel_open_(parallel_open), pool_(pool) {}
2229
2330 void open () override {
24- if (parallel_open_ && pool_) {
25- // Thread-pool parallel open: ~1-2us dispatch vs ~200us for std::async
26- auto fl = pool_->submit ([this ]{ left_->open (); });
27- auto fr = pool_->submit ([this ]{ right_->open (); });
28- fl.get ();
29- fr.get ();
30- } else if (parallel_open_) {
31- // Fallback: std::async when no pool available
32- auto fl = std::async (std::launch::async, [this ]{ left_->open (); });
33- auto fr = std::async (std::launch::async, [this ]{ right_->open (); });
34- fl.get ();
35- fr.get ();
31+ if (children_.empty ()) return ;
32+ if (parallel_open_ && children_.size () > 1 ) {
33+ std::vector<std::future<void >> futures;
34+ futures.reserve (children_.size ());
35+ for (size_t i = 0 ; i < children_.size (); ++i) {
36+ auto launcher = [this , i]{ children_[i]->open (); };
37+ if (pool_) {
38+ futures.push_back (pool_->submit (std::move (launcher)));
39+ } else {
40+ futures.push_back (std::async (std::launch::async, std::move (launcher)));
41+ }
42+ }
43+ for (auto & f : futures) f.get ();
3644 } else {
37- left_->open ();
38- right_->open ();
45+ for (auto * c : children_) c->open ();
3946 }
40- reading_left_ = true ;
47+ child_idx_ = 0 ;
4148 seen_.clear ();
4249 expected_col_count_ = -1 ;
4350
44- if (op_ == SET_OP_INTERSECT || op_ == SET_OP_EXCEPT ) {
45- // Materialize right side into a set
51+ if ((op_ == SET_OP_INTERSECT || op_ == SET_OP_EXCEPT ) && children_.size () >= 2 ) {
4652 right_set_.clear ();
4753 Row r{};
48- while (right_ ->next (r)) {
54+ while (children_[ 1 ] ->next (r)) {
4955 check_col_count (r);
5056 check_operator_row_limit (right_set_.size (), kDefaultMaxOperatorRows , " SetOpOperator" );
5157 right_set_.insert (row_key (r));
5258 }
53- right_->close ();
59+ children_[1 ]->close ();
60+ right_closed_ = true ;
5461 }
5562 }
5663
5764 bool next (Row& out) override {
65+ if (children_.empty ()) return false ;
66+
5867 if (op_ == SET_OP_UNION && !all_) {
59- // UNION (deduplicated)
60- while (true ) {
61- bool got = false ;
62- if (reading_left_) {
63- got = left_->next (out);
64- if (!got) { reading_left_ = false ; }
65- }
66- if (!reading_left_) {
67- got = right_->next (out);
68- if (!got) return false ;
68+ while (child_idx_ < children_.size ()) {
69+ if (!children_[child_idx_]->next (out)) {
70+ ++child_idx_;
71+ continue ;
6972 }
70- if (got) {
71- check_col_count (out);
72- std::string key = row_key (out);
73- if (seen_.find (key) == seen_.end ()) {
74- check_operator_row_limit (seen_.size (), kDefaultMaxOperatorRows , " SetOpOperator" );
75- }
76- if (seen_.insert (key).second ) return true ;
73+ check_col_count (out);
74+ std::string key = row_key (out);
75+ if (seen_.find (key) == seen_.end ()) {
76+ check_operator_row_limit (seen_.size (), kDefaultMaxOperatorRows , " SetOpOperator" );
7777 }
78+ if (seen_.insert (key).second ) return true ;
7879 }
80+ return false ;
7981 }
8082
8183 if (op_ == SET_OP_UNION && all_) {
82- // UNION ALL: yield left then right
83- if (reading_left_) {
84- if (left_->next (out)) {
84+ while (child_idx_ < children_.size ()) {
85+ if (children_[child_idx_]->next (out)) {
8586 check_col_count (out);
8687 return true ;
8788 }
88- reading_left_ = false ;
89- }
90- if (right_->next (out)) {
91- check_col_count (out);
92- return true ;
89+ ++child_idx_;
9390 }
9491 return false ;
9592 }
9693
9794 if (op_ == SET_OP_INTERSECT ) {
98- // Yield left rows that also appear in right
99- while (left_->next (out)) {
95+ while (children_[0 ]->next (out)) {
10096 check_col_count (out);
10197 std::string key = row_key (out);
10298 if (right_set_.count (key)) {
@@ -108,8 +104,7 @@ class SetOpOperator : public Operator {
108104 }
109105
110106 if (op_ == SET_OP_EXCEPT ) {
111- // Yield left rows that don't appear in right
112- while (left_->next (out)) {
107+ while (children_[0 ]->next (out)) {
113108 check_col_count (out);
114109 std::string key = row_key (out);
115110 if (!right_set_.count (key)) {
@@ -124,20 +119,22 @@ class SetOpOperator : public Operator {
124119 }
125120
126121 void close () override {
127- left_->close ();
128- right_->close ();
122+ for (size_t i = 0 ; i < children_.size (); ++i) {
123+ if (right_closed_ && i == 1 ) continue ;
124+ children_[i]->close ();
125+ }
129126 seen_.clear ();
130127 right_set_.clear ();
131128 }
132129
133130private:
134- Operator* left_;
135- Operator* right_;
131+ std::vector<Operator*> children_;
136132 uint8_t op_;
137133 bool all_;
138134 bool parallel_open_;
139135 ThreadPool* pool_ = nullptr ;
140- bool reading_left_ = true ;
136+ size_t child_idx_ = 0 ;
137+ bool right_closed_ = false ;
141138 std::unordered_set<std::string> seen_;
142139 std::unordered_set<std::string> right_set_;
143140 // Column count established by the first row we see. Used to detect
0 commit comments