@@ -141,28 +141,38 @@ class DistributedPlanner {
141141 }
142142
143143 case PlanNodeType::PROJECT : {
144- // Check for PROJECT -> [SORT ->] [FILTER ->] AGGREGATE pattern
145- // For aggregate queries, we want to handle the whole thing in distribute_aggregate
144+ // PROJECT -> [SORT ->] [FILTER(HAVING) ->] AGGREGATE
145+ PlanNode* sort_node = nullptr ;
146+ PlanNode* having_node = nullptr ;
146147 PlanNode* agg_child = node->left ;
147- if (agg_child && agg_child->type == PlanNodeType::SORT )
148+ if (agg_child && agg_child->type == PlanNodeType::SORT ) {
149+ sort_node = agg_child;
148150 agg_child = agg_child->left ;
149- if (agg_child && agg_child->type == PlanNodeType::FILTER )
151+ }
152+ if (agg_child && agg_child->type == PlanNodeType::FILTER ) {
153+ having_node = agg_child;
150154 agg_child = agg_child->left ;
155+ }
151156 if (agg_child && agg_child->type == PlanNodeType::AGGREGATE ) {
152- // Extract aggregate info from the PROJECT select list
153157 push_agg_exprs_from_project (node, agg_child);
154158 PlanNode* dist_agg = distribute_aggregate (agg_child);
155- if (dist_agg && dist_agg->type == PlanNodeType::MERGE_AGGREGATE ) {
156- // Re-add FILTER (HAVING) if present
157- if (node->left && node->left ->type == PlanNodeType::FILTER ) {
159+ if (dist_agg && (dist_agg->type == PlanNodeType::MERGE_AGGREGATE ||
160+ dist_agg->type == PlanNodeType::AGGREGATE )) {
161+ PlanNode* top = dist_agg;
162+ if (having_node) {
158163 PlanNode* having = make_plan_node (arena_, PlanNodeType::FILTER );
159- having->filter .expr = node->left ->filter .expr ;
160- having->left = dist_agg;
161- return having;
164+ having->filter .expr = having_node->filter .expr ;
165+ having->left = top;
166+ top = having;
167+ }
168+ if (sort_node) {
169+ PlanNode* sort = make_plan_node (arena_, PlanNodeType::SORT );
170+ sort->sort = sort_node->sort ;
171+ sort->left = top;
172+ top = sort;
162173 }
163- return dist_agg ;
174+ return top ;
164175 }
165- // For unsharded, the remote already computes everything
166176 return dist_agg;
167177 }
168178
@@ -189,6 +199,13 @@ class DistributedPlanner {
189199 case PlanNodeType::JOIN :
190200 return distribute_join (node);
191201
202+ case PlanNodeType::WINDOW : {
203+ PlanNode* result = make_plan_node (arena_, PlanNodeType::WINDOW );
204+ result->window = node->window ;
205+ result->left = distribute_node (node->left );
206+ return result;
207+ }
208+
192209 case PlanNodeType::SET_OP : {
193210 PlanNode* result = make_plan_node (arena_, PlanNodeType::SET_OP );
194211 result->set_op = node->set_op ;
@@ -261,6 +278,24 @@ class DistributedPlanner {
261278 return ctx;
262279 }
263280
281+ static bool contains_type (const PlanNode* node, PlanNodeType type) {
282+ if (!node) return false ;
283+ if (node->type == type) return true ;
284+ if (contains_type (node->left , type)) return true ;
285+ if (contains_type (node->right , type)) return true ;
286+ if (node->type == PlanNodeType::MERGE_AGGREGATE ) {
287+ for (uint16_t i = 0 ; i < node->merge_aggregate .child_count ; ++i) {
288+ if (contains_type (node->merge_aggregate .children [i], type)) return true ;
289+ }
290+ }
291+ if (node->type == PlanNodeType::MERGE_SORT ) {
292+ for (uint16_t i = 0 ; i < node->merge_sort .child_count ; ++i) {
293+ if (contains_type (node->merge_sort .children [i], type)) return true ;
294+ }
295+ }
296+ return false ;
297+ }
298+
264299 // Case 1 & 2: Distribute a scan (possibly with filter pushed down)
265300 PlanNode* distribute_scan (PlanNode* scan_node,
266301 const sql_parser::AstNode* where_expr,
@@ -548,10 +583,16 @@ class DistributedPlanner {
548583
549584 const TableInfo* table = ctx.scan ->scan .table ;
550585 if (!shards_.has_table (table->table_name ) || !shards_.is_sharded (table->table_name )) {
551- // Unsharded -- push the whole thing to remote
552586 return make_unsharded_aggregate (agg_node, ctx, table);
553587 }
554588
589+ if (!all_aggregates_two_phase (agg_node)) {
590+ PlanNode* result = make_plan_node (arena_, PlanNodeType::AGGREGATE );
591+ result->aggregate = agg_node->aggregate ;
592+ result->left = distribute_node (agg_node->left );
593+ return result;
594+ }
595+
555596 // Sharded aggregate: each shard computes partial aggregates.
556597 // Build remote project expressions: group-by cols + partial agg expressions
557598 const auto & shard_list = shards_.get_shards (table->table_name );
@@ -661,6 +702,24 @@ class DistributedPlanner {
661702 return make_remote_scan_with_outputs (backend, sql, table, projs);
662703 }
663704
705+ static bool is_two_phase_aggregate (const sql_parser::AstNode* expr) {
706+ if (!expr || expr->type != sql_parser::NodeType::NODE_FUNCTION_CALL ) return false ;
707+ if (expr->flags & sql_parser::FLAG_FUNC_DISTINCT ) return false ;
708+ sql_parser::StringRef name = expr->value ();
709+ return name.equals_ci (" COUNT" , 5 ) || name.equals_ci (" SUM" , 3 ) ||
710+ name.equals_ci (" AVG" , 3 ) || name.equals_ci (" MIN" , 3 ) ||
711+ name.equals_ci (" MAX" , 3 );
712+ }
713+
714+ bool all_aggregates_two_phase (const PlanNode* agg_node) const {
715+ if (!agg_node) return false ;
716+ if (agg_node->aggregate .agg_count == 0 ) return true ;
717+ for (uint16_t i = 0 ; i < agg_node->aggregate .agg_count ; ++i) {
718+ if (!is_two_phase_aggregate (agg_node->aggregate .agg_exprs [i])) return false ;
719+ }
720+ return true ;
721+ }
722+
664723 void decompose_aggregate (const sql_parser::AstNode* expr,
665724 std::vector<const sql_parser::AstNode*>& projs,
666725 std::vector<uint8_t >& merge_ops) {
@@ -673,11 +732,9 @@ class DistributedPlanner {
673732 sql_parser::StringRef name = expr->value ();
674733
675734 if (name.equals_ci (" COUNT" , 5 )) {
676- // Remote: COUNT(*) or COUNT(col), Local: SUM of counts
677735 projs.push_back (expr);
678736 merge_ops.push_back (static_cast <uint8_t >(MergeOp::SUM_OF_COUNTS ));
679737 } else if (name.equals_ci (" SUM" , 3 )) {
680- // Remote: SUM(col), Local: SUM of sums
681738 projs.push_back (expr);
682739 merge_ops.push_back (static_cast <uint8_t >(MergeOp::SUM_OF_SUMS ));
683740 } else if (name.equals_ci (" AVG" , 3 )) {
@@ -726,7 +783,16 @@ class DistributedPlanner {
726783
727784 // Case 4: Distributed sort + limit
728785 PlanNode* distribute_sort (PlanNode* sort_node) {
729- // Check if the child is a scan (possibly through filter) on a sharded table
786+ if (contains_type (sort_node->left , PlanNodeType::WINDOW ) ||
787+ contains_type (sort_node->left , PlanNodeType::DERIVED_SCAN ) ||
788+ contains_type (sort_node->left , PlanNodeType::AGGREGATE ) ||
789+ contains_type (sort_node->left , PlanNodeType::MERGE_AGGREGATE )) {
790+ PlanNode* result = make_plan_node (arena_, PlanNodeType::SORT );
791+ result->sort = sort_node->sort ;
792+ result->left = distribute_node (sort_node->left );
793+ return result;
794+ }
795+
730796 ScanContext ctx = extract_scan_context (sort_node->left );
731797 if (!ctx.scan || !ctx.scan ->scan .table ) {
732798 PlanNode* result = make_plan_node (arena_, PlanNodeType::SORT );
@@ -795,6 +861,15 @@ class DistributedPlanner {
795861 // Check if child is Sort on sharded table
796862 if (limit_node->left && limit_node->left ->type == PlanNodeType::SORT ) {
797863 PlanNode* sort_node = limit_node->left ;
864+ if (contains_type (sort_node->left , PlanNodeType::WINDOW ) ||
865+ contains_type (sort_node->left , PlanNodeType::DERIVED_SCAN ) ||
866+ contains_type (sort_node->left , PlanNodeType::AGGREGATE ) ||
867+ contains_type (sort_node->left , PlanNodeType::MERGE_AGGREGATE )) {
868+ PlanNode* result = make_plan_node (arena_, PlanNodeType::LIMIT );
869+ result->limit = limit_node->limit ;
870+ result->left = distribute_node (limit_node->left );
871+ return result;
872+ }
798873 ScanContext ctx = extract_scan_context (sort_node->left );
799874 if (ctx.scan && ctx.scan ->scan .table ) {
800875 const TableInfo* table = ctx.scan ->scan .table ;
@@ -839,7 +914,16 @@ class DistributedPlanner {
839914 }
840915 }
841916
842- // Check if child is scan on sharded/unsharded table (limit without sort)
917+ if (contains_type (limit_node->left , PlanNodeType::WINDOW ) ||
918+ contains_type (limit_node->left , PlanNodeType::DERIVED_SCAN ) ||
919+ contains_type (limit_node->left , PlanNodeType::AGGREGATE ) ||
920+ contains_type (limit_node->left , PlanNodeType::MERGE_AGGREGATE )) {
921+ PlanNode* result = make_plan_node (arena_, PlanNodeType::LIMIT );
922+ result->limit = limit_node->limit ;
923+ result->left = distribute_node (limit_node->left );
924+ return result;
925+ }
926+
843927 ScanContext ctx = extract_scan_context (limit_node->left );
844928 if (ctx.scan && ctx.scan ->scan .table ) {
845929 const TableInfo* table = ctx.scan ->scan .table ;
0 commit comments