@@ -122,6 +122,21 @@ distributed_mobilitydb_planner_internal(Query *parse, const char *query_string,
122122 /* Query rewriter */
123123 if (list_length (distPlan -> postProcessing -> distfuns ) > 0 )
124124 RewriterDistFuncs (parse , distPlan -> postProcessing , query_string );
125+ if (needsSpatiotemporalPlanning && list_length (distPlan -> strategies ) == 0
126+ && list_length (distPlan -> postProcessing -> distfuns ) == 0 )
127+ {
128+ /* needsDistributedSpatiotemporalPlanning() can return true purely
129+ * from tablesList->diffCount > 1, even when checkQueryType() found
130+ * no registered intersection/distance predicate to build a strategy
131+ * for (e.g. a plain `@>` "contains" clause against a reference
132+ * table isn't one of those). With no strategy and no distributed
133+ * function, there is nothing for the custom executor below to
134+ * build a plan from -- it would otherwise fall into
135+ * ConstructGeneralQuery's `generalScan->length == 0` branch and use
136+ * postProcessing->worker, which is NULL here, producing "(null)"
137+ * as the query text. Let Citus plan the query directly instead. */
138+ return distributed_planner (parse , query_string , cursorOptions , boundParams );
139+ }
125140 if (needsSpatiotemporalPlanning )
126141 {
127142 if (!distPlan -> activate_rewriter )
@@ -198,6 +213,13 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
198213{
199214 ListCell * rangeTableCell = NULL ;
200215 Oid curr_relid = -1 ;
216+ /* diffCount/simCount need to know whether relid has appeared ANYWHERE
217+ * earlier in the range table, not just in the immediately preceding
218+ * entry -- comparing only to curr_relid miscounted a self-join like
219+ * "Trips t1, Licences1 l1, Trips t2" as three different tables instead
220+ * of recognizing t2 as a repeat of t1, since t2 is compared against
221+ * l1's relid rather than t1's. */
222+ List * seenRelids = NIL ;
201223 bool shapeType ;
202224 List * rtes = NIL ;
203225 foreach (rangeTableCell , rangeTableList )
@@ -206,6 +228,21 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
206228 if (rangeTableEntry -> rtekind != RTE_RELATION ) {
207229 continue ;
208230 }
231+ /* A view reference (e.g. Licences1, a view over Licences) expands
232+ * in the range table into the view's own subquery entry (rtekind
233+ * != RTE_RELATION, already skipped above), PostgreSQL's internal
234+ * rule-system "old"/"new" placeholder entries for that view, and
235+ * the view's real underlying base table -- none of which the user
236+ * actually joined except the last. inFromCl is false for exactly
237+ * those non-user-visible placeholders (verified: "old"/"new" have
238+ * inFromCl=0, the real base table has inFromCl=1), so without this
239+ * check every view reference was triple-counted as extra distinct
240+ * tables, inflating diffCount/length enough to make e.g. a query
241+ * with one distributed table plus reference tables look like it
242+ * had several more distributed tables than it really did. */
243+ if (!rangeTableEntry -> inFromCl ) {
244+ continue ;
245+ }
209246 if (IsDistributedSpatiotemporalTable (rangeTableEntry -> relid ))
210247 {
211248 if (IsReshuffledTable (rangeTableEntry -> relid ))
@@ -248,15 +285,29 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
248285 if (LookupCitusTableCacheEntry (rangeTableEntry -> relid ) != NULL )
249286 {
250287 char partitioningMethod = PartitionMethodViaCatalog (rangeTableEntry -> relid );
251- if (partitioningMethod == DISTRIBUTE_BY_HASH || partitioningMethod == DISTRIBUTE_BY_RANGE )
288+ /* A reference table is replicated to every node, so joining
289+ * it alongside a distributed table needs no repartitioning.
290+ * Its reported partitioning method alone doesn't identify it
291+ * uniquely, so check its table type explicitly instead. */
292+ bool isReferenceTable = IsCitusTableType (rangeTableEntry -> relid , REFERENCE_TABLE );
293+ if (partitioningMethod == DISTRIBUTE_BY_HASH || partitioningMethod == DISTRIBUTE_BY_RANGE
294+ || isReferenceTable )
252295 {
253296 /* Citus table processing */
254297 CitusRteNode * citusNode = GetCitusRteInfo (rangeTableEntry ,partitioningMethod );
255298 citusNode -> rangeTableCell = rangeTableCell ;
256- distPlan -> tablesList -> length ++ ;
299+ /* length is already incremented unconditionally for
300+ * every range table entry below (after this if/else) --
301+ * incrementing it here too double-counted every Citus
302+ * table entry, inflating effectiveLength enough that a
303+ * query with one distributed table plus reference
304+ * tables was never recognized as an effective
305+ * single-table case. */
257306 Rte * rteNode = GetRteNode ((Node * ) citusNode , CitusRte , rangeTableEntry -> alias );
258307 rtes = lappend (rtes , rteNode );
259308 distPlan -> tablesList -> nonStCount ++ ;
309+ if (isReferenceTable )
310+ distPlan -> tablesList -> refCount ++ ;
260311 }
261312 else
262313 elog (ERROR , "The %s table is not distributed using one of the supported partitioning methods" ,
@@ -270,9 +321,10 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
270321 rtes = lappend (rtes , rteNode );
271322 }
272323 }
273- if (curr_relid != rangeTableEntry -> relid )
324+ if (! list_member_oid ( seenRelids , rangeTableEntry -> relid ) )
274325 {
275326 distPlan -> tablesList -> diffCount ++ ;
327+ seenRelids = lappend_oid (seenRelids , rangeTableEntry -> relid );
276328 }
277329 else
278330 distPlan -> tablesList -> simCount ++ ;
@@ -355,6 +407,14 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
355407 /* TODO: subquery is excluded for now */
356408 ereport (ERROR , (errmsg ("A sub query is not supported yet in Distributed MobilityDB!" )));
357409 }
410+ /* Reference tables are already replicated to every node, so a join
411+ * against one never needs the NonColocation strategy's reshuffle --
412+ * Citus can push the predicate down to each shard directly. Subtracting
413+ * refCount here means a query joining one distributed spatiotemporal
414+ * table with any number of reference tables is treated the same as a
415+ * genuine single-table query below. */
416+ int effectiveDiffCount = distPlan -> tablesList -> diffCount - distPlan -> tablesList -> refCount ;
417+ int effectiveLength = distPlan -> tablesList -> length - distPlan -> tablesList -> refCount ;
358418 /* Iterate over the where clause conditions */
359419 foreach (clauseCell , whereClauseList )
360420 {
@@ -379,12 +439,12 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
379439 {
380440 if (IsIntersectionOperation (predicateOid ))
381441 {
382- if (distPlan -> tablesList -> diffCount > 1 )
442+ if (effectiveDiffCount > 1 )
383443 {
384444 /* Intersection join between two distinct tables: must colocate them first. */
385445 AddStrategy (distPlan , NonColocation );
386446 }
387- else if (distPlan -> tablesList -> length == 1 )
447+ else if (effectiveLength == 1 )
388448 {
389449 /* Single-table intersection: decide between rebalancing tiles to fit the
390450 * query's search box or simply pushing the predicate to each worker. */
@@ -406,10 +466,22 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
406466 }
407467 else if (IsDistanceOperation (predicateOid ))
408468 {
409- /* The NonColocation strategy is triggered by default until the analysis changes it */
410469 if (distPlan -> tablesList -> simCount >= 1 )
411470 AddStrategy (distPlan , Colocation );
412- AddStrategy (distPlan , NonColocation );
471+ /* The NonColocation strategy is triggered by default until the analysis
472+ * changes it -- except when the only "different" tables besides one
473+ * distributed spatiotemporal table are reference tables (refCount > 0
474+ * guards this so behavior is untouched whenever no reference table is
475+ * involved), which Citus can push the predicate down to directly with
476+ * no reshuffle needed. */
477+ if (effectiveDiffCount > 1 || distPlan -> tablesList -> refCount == 0 )
478+ {
479+ AddStrategy (distPlan , NonColocation );
480+ }
481+ else if (effectiveLength == 1 )
482+ {
483+ AddStrategy (distPlan , PredicatePushDown );
484+ }
413485 if (distPlan -> predicatesList -> predicateType == DISTANCE )
414486 {
415487 ereport (ERROR , (errmsg ("Currently, we do not support using more than "
0 commit comments