2121#include <catalog/namespace.h>
2222#include <access/xact.h>
2323#include "utils/planner_utils.h"
24+ #include "utils/helper_functions.h"
2425#include "planner/planner_strategies.h"
2526
2627static void ConstructNeighborScanQuery (Rte * tbl , char * query_string , STMultirelation * base ,
@@ -36,6 +37,7 @@ static GeneralScan *ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *di
3637 MultiPhaseExecutor * multiPhaseExecutor );
3738static void IndexReshuffledData (Rte * reshuffledTable , MultiPhaseExecutor * multiPhaseExecutor );
3839static void ConstructPostProcessingPhase (CoordinatorLevelOperator * coordOp , MultiPhaseExecutor * multiPhaseExecutor );
40+ static char * EliminateShapeSegmentDuplicates (char * query_string , bool hasDistributedAggregate );
3941
4042
4143
@@ -137,9 +139,12 @@ ColocateRte(STMultirelation *base, Rte *other)
137139 char * reshuffled_table = get_rel_name (cell -> relid );
138140 Var * distributionColumn = DistPartitionKey (base -> catalogTableInfo .table_oid );
139141 int shardCount = ShardIntervalCount (base -> catalogTableInfo .table_oid );
140- /* Citus' CreateDistributedTable() expects the literal string "default"
141- * (not NULL) to mean "no explicit colocation group" -- IsColocateWithDefault()
142- * dereferences it directly and crashes on NULL. */
142+ /* Citus rejects colocate_with for range-distributed tables
143+ * ("colocate_with option is not supported for append / range
144+ * distributed tables"), so this stays "default"; physical
145+ * co-location with base is instead done after the fact by
146+ * colocate_shards() in create_reshuffled_multirelation, which
147+ * explicitly moves each shard onto base's matching-tile node. */
143148 char * parentRelationName = "default" ;
144149
145150 DropReshuffledTableIfExists (citusRteNode -> reshuffledTable );
@@ -182,9 +187,12 @@ createReshuffledTable(STMultirelation *base, STMultirelation *other)
182187 char * reshuffled_table = get_rel_name (other -> catalogTableInfo .table_oid );
183188 Var * distributionColumn = DistPartitionKey (other -> catalogTableInfo .table_oid );
184189 int shardCount = ShardIntervalCount (base -> catalogTableInfo .table_oid );
185- /* Citus' CreateDistributedTable() expects the literal string "default"
186- * (not NULL) to mean "no explicit colocation group" -- IsColocateWithDefault()
187- * dereferences it directly and crashes on NULL. */
190+ /* Citus rejects colocate_with for range-distributed tables
191+ * ("colocate_with option is not supported for append / range
192+ * distributed tables"), so this stays "default"; physical
193+ * co-location with base is instead done after the fact by
194+ * colocate_shards() in create_reshuffled_multirelation, which
195+ * explicitly moves each shard onto base's matching-tile node. */
188196 char * parentRelationName = "default" ;
189197
190198 DropReshuffledTableIfExists (other -> catalogTableInfo .reshuffledTable );
@@ -402,6 +410,28 @@ IndexReshuffledData(Rte *reshuffledTable, MultiPhaseExecutor *multiPhaseExecutor
402410
403411}
404412
413+ /*
414+ * EliminateShapeSegmentDuplicates removes shape-segmentation duplicates from
415+ * query_string by wrapping it as `SELECT DISTINCT * FROM (query_string) AS
416+ * x`, deduping on whatever the query actually projects. Only applied when
417+ * no distributed aggregate is involved (see hasDistributedAggregate at the
418+ * call site) -- for an aggregate like count(*), the duplicates are already
419+ * consumed before this outer DISTINCT would ever see them, so this is left
420+ * as a no-op (returns NULL) for that case rather than risking a rewrite
421+ * Citus' planner may reject for non-colocated repartition joins.
422+ */
423+ static char *
424+ EliminateShapeSegmentDuplicates (char * query_string , bool hasDistributedAggregate )
425+ {
426+ if (hasDistributedAggregate )
427+ return NULL ;
428+
429+ char * innerQuery = replaceWord (query_string , ";" , " " );
430+ StringInfo dedupedQuery = makeStringInfo ();
431+ appendStringInfo (dedupedQuery , "SELECT DISTINCT * FROM (%s) AS dedup_result" , innerQuery );
432+ return dedupedQuery -> data ;
433+ }
434+
405435/*
406436 * ConstructGeneralQuery assembles the final SQL text to execute: it unions
407437 * together the worker-phase task query for each strategy used in the plan
@@ -449,6 +479,29 @@ ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *distPlan, MultiPhaseEx
449479 else
450480 elog (ERROR , "The query executor could not identify the planner strategy" );
451481 }
482+ /* NonColocation/Colocation strategies reshuffle onto tiles built from a
483+ * spatiotemporal shape, which can place the same row's shape-segmented
484+ * copy in more than one tile so a boundary-crossing match isn't missed
485+ * -- see checkQueryType's dupRemOperator->active assignment. That makes
486+ * the worker-phase join above contain the same logical match
487+ * (base_row1, base_row2) more than once. This has to be resolved here,
488+ * on the flat two-table query before the coordinator/aggregate-rewriter
489+ * wrapping below nests it inside a "select sum(...) from (...) as fQ"
490+ * subquery -- once that wrapping happens, ExtractRangeTableEntryList
491+ * sees three range table entries (the subquery plus its two inner
492+ * tables) instead of the two this rewrite expects, and any duplicate
493+ * rows have already been consumed by the inner aggregate anyway. */
494+ if (distPlan -> postProcessing -> coordinatorLevelOperator -> dupRemOperator -> active )
495+ {
496+ bool hasDistributedAggregate = list_length (distPlan -> postProcessing -> distfuns ) > 0 ;
497+ char * deduped = EliminateShapeSegmentDuplicates (generalScan -> query_string -> data ,
498+ hasDistributedAggregate );
499+ if (deduped != NULL )
500+ {
501+ resetStringInfo (generalScan -> query_string );
502+ appendStringInfo (generalScan -> query_string , "%s" , deduped );
503+ }
504+ }
452505 /* Loop though the post processing tasks */
453506 if (generalScan -> length == 0 )
454507 appendStringInfo (generalScan -> query_string ,"%s" ,
0 commit comments