@@ -399,22 +399,25 @@ copy_columnar_scan_path(ColumnarScanPath *src)
399399}
400400
401401/*
402- * Maps the attno of the min metadata column in the compressed chunk to the
403- * attno of the corresponding max metadata column. Zero if none or not applicable.
402+ * Maps the attno of a "lower" orderby metadata column on the compressed
403+ * chunk to the attno of the corresponding "upper" column, and vice versa.
404+ * Covers both minmax (lower=min, upper=max) and firstlast pairs.
405+ * Zero entries mean none or not applicable.
404406 */
405407typedef struct SelectivityEstimationContext
406408{
407- AttrNumber * min_to_max ;
408- AttrNumber * max_to_min ;
409+ AttrNumber * lower_to_upper ;
410+ AttrNumber * upper_to_lower ;
409411
410412 List * vars ;
411413} SelectivityEstimationContext ;
412414
413415/*
414- * Collect the Vars referencing the "min" metadata columns into the context->vars.
416+ * Collect the Vars referencing the orderby "lower" metadata columns
417+ * (min for minmax pairs, first/last for firstlast pairs) into context->vars.
415418 */
416419static bool
417- min_metadata_vars_collector (Node * orig_node , SelectivityEstimationContext * context )
420+ lower_metadata_vars_collector (Node * orig_node , SelectivityEstimationContext * context )
418421{
419422 if (orig_node == NULL )
420423 {
@@ -430,7 +433,7 @@ min_metadata_vars_collector(Node *orig_node, SelectivityEstimationContext *conte
430433 /*
431434 * Recurse.
432435 */
433- return expression_tree_walker (orig_node , min_metadata_vars_collector , context );
436+ return expression_tree_walker (orig_node , lower_metadata_vars_collector , context );
434437 }
435438
436439 Var * orig_var = castNode (Var , orig_node );
@@ -442,7 +445,7 @@ min_metadata_vars_collector(Node *orig_node, SelectivityEstimationContext *conte
442445 return false;
443446 }
444447
445- AttrNumber replaced_attno = context -> min_to_max [orig_var -> varattno ];
448+ AttrNumber replaced_attno = context -> lower_to_upper [orig_var -> varattno ];
446449 if (replaced_attno == InvalidAttrNumber )
447450 {
448451 /*
@@ -483,14 +486,14 @@ set_compressed_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
483486 * selectivity estimator must see the entire clause list to detect the range
484487 * conditions.
485488 *
486- * First, build the correspondence of min metadata attno -> max metadata
487- * attno for all minmax metadata.
489+ * First, build the correspondence of lower metadata attno -> upper
490+ * metadata attno for all orderby metadata pairs (minmax and firstlast) .
488491 */
489492 const int storage_elements = 2 * (compression_info -> compressed_rel -> max_attr + 1 );
490493 AttrNumber * storage = palloc0 (storage_elements * sizeof (* storage ));
491494 SelectivityEstimationContext context = {
492- .min_to_max = & storage [0 ],
493- .max_to_min = & storage [compression_info -> compressed_rel -> max_attr ],
495+ .lower_to_upper = & storage [0 ],
496+ .upper_to_lower = & storage [compression_info -> compressed_rel -> max_attr ],
494497 };
495498
496499 for (int uncompressed_attno = 1 ; uncompressed_attno <= compression_info -> chunk_rel -> max_attr ;
@@ -534,42 +537,74 @@ set_compressed_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
534537 compression_info -> compressed_rte -> relid ,
535538 "max" );
536539
537- if (min_attno == InvalidAttrNumber || max_attno = = InvalidAttrNumber )
540+ if (min_attno != InvalidAttrNumber && max_attno ! = InvalidAttrNumber )
538541 {
539- continue ;
542+ Assert (& context .lower_to_upper [min_attno ] < & storage [storage_elements ]);
543+ Assert (& context .upper_to_lower [max_attno ] < & storage [storage_elements ]);
544+
545+ context .lower_to_upper [min_attno ] = max_attno ;
546+ context .upper_to_lower [max_attno ] = min_attno ;
540547 }
541548
542- Assert (& context .min_to_max [min_attno ] < & storage [storage_elements ]);
543- Assert (& context .max_to_min [max_attno ] < & storage [storage_elements ]);
549+ /*
550+ * Same correlation hint for the firstlast pair when present. Under ASC
551+ * the lower bound is first and the upper is last; under DESC, the
552+ * roles flip. Mapping is direction-aware so that the Var swap below
553+ * collapses the predicate onto a single column regardless of shape.
554+ */
555+ AttrNumber first_attno =
556+ compressed_column_metadata_attno (compression_info -> settings ,
557+ compression_info -> chunk_rte -> relid ,
558+ uncompressed_attno ,
559+ compression_info -> compressed_rte -> relid ,
560+ "first" );
561+ AttrNumber last_attno =
562+ compressed_column_metadata_attno (compression_info -> settings ,
563+ compression_info -> chunk_rte -> relid ,
564+ uncompressed_attno ,
565+ compression_info -> compressed_rte -> relid ,
566+ "last" );
567+
568+ if (first_attno != InvalidAttrNumber && last_attno != InvalidAttrNumber )
569+ {
570+ bool desc =
571+ ts_array_get_element_bool (compression_info -> settings -> fd .orderby_desc , orderby_pos );
572+ AttrNumber lower_attno = desc ? last_attno : first_attno ;
573+ AttrNumber upper_attno = desc ? first_attno : last_attno ;
574+
575+ Assert (& context .lower_to_upper [lower_attno ] < & storage [storage_elements ]);
576+ Assert (& context .upper_to_lower [upper_attno ] < & storage [storage_elements ]);
544577
545- context .min_to_max [min_attno ] = max_attno ;
546- context .max_to_min [max_attno ] = min_attno ;
578+ context .lower_to_upper [lower_attno ] = upper_attno ;
579+ context .upper_to_lower [upper_attno ] = lower_attno ;
580+ }
547581 }
548582
549583 /*
550- * Then, replace all conditions on min metadata column with conditions on
551- * max metadata column .
584+ * Then, collect all Var references to lower-bound metadata columns in the
585+ * restrict clauses so we can rewrite them to their upper-bound counterpart .
552586 */
553587 ListCell * lc ;
554588 foreach (lc , rel -> baserestrictinfo )
555589 {
556590 RestrictInfo * orig_restrictinfo = castNode (RestrictInfo , lfirst (lc ));
557591 Node * orig_clause = (Node * ) orig_restrictinfo -> clause ;
558- expression_tree_walker (orig_clause , min_metadata_vars_collector , & context );
592+ expression_tree_walker (orig_clause , lower_metadata_vars_collector , & context );
559593 }
560594
561595 /*
562- * Temporarily replace "min" with "max" in-place to save on memory allocations.
596+ * Temporarily replace lower-bound Vars with their upper-bound counterpart
597+ * in-place to save on memory allocations.
563598 */
564599 foreach (lc , context .vars )
565600 {
566601 Var * var = castNode (Var , lfirst (lc ));
567602
568603 Assert (var -> varattno != InvalidAttrNumber );
569- Assert (context .min_to_max [var -> varattno ] != InvalidAttrNumber );
570- Assert (context .max_to_min [context .min_to_max [var -> varattno ]] == var -> varattno );
604+ Assert (context .lower_to_upper [var -> varattno ] != InvalidAttrNumber );
605+ Assert (context .upper_to_lower [context .lower_to_upper [var -> varattno ]] == var -> varattno );
571606
572- var -> varattno = context .min_to_max [var -> varattno ];
607+ var -> varattno = context .lower_to_upper [var -> varattno ];
573608 }
574609
575610 /*
@@ -583,7 +618,7 @@ set_compressed_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
583618 foreach (lc , context .vars )
584619 {
585620 Var * var = castNode (Var , lfirst (lc ));
586- var -> varattno = context .max_to_min [var -> varattno ];
621+ var -> varattno = context .upper_to_lower [var -> varattno ];
587622 }
588623
589624 pfree (storage );
0 commit comments