@@ -156,6 +156,79 @@ column_segment_max_name(int16 column_index)
156156 return compression_column_segment_metadata_name ("max" , column_index );
157157}
158158
159+ OrderbySparseKind
160+ orderby_sparse_kind (const CompressionSettings * settings , int orderby_pos )
161+ {
162+ Assert (settings != NULL );
163+ Assert (orderby_pos >= 1 && orderby_pos <= ts_array_length (settings -> fd .orderby ));
164+
165+ if (settings -> fd .index == NULL )
166+ {
167+ return ORDERBY_SPARSE_MINMAX ;
168+ }
169+
170+ const char * col_name = ts_array_get_element_text (settings -> fd .orderby , orderby_pos );
171+
172+ SparseIndexSettings * parsed = ts_convert_to_sparse_index_settings (settings -> fd .index );
173+ List * per_col = ts_get_per_column_compression_settings (parsed );
174+ PerColumnCompressionSettings * col_settings =
175+ ts_get_per_column_compression_settings_by_column_name (per_col , col_name );
176+
177+ OrderbySparseKind kind = ORDERBY_SPARSE_MINMAX ;
178+ if (col_settings != NULL && col_settings -> firstlast_obj_id >= 0 )
179+ {
180+ kind = ORDERBY_SPARSE_FIRSTLAST ;
181+ }
182+
183+ ts_free_sparse_index_settings (parsed );
184+ return kind ;
185+ }
186+
187+ void
188+ orderby_sparse_metadata_names (const CompressionSettings * settings , int orderby_pos ,
189+ char * * lower_name , char * * upper_name )
190+ {
191+ Assert (lower_name != NULL && upper_name != NULL );
192+
193+ OrderbySparseKind kind = orderby_sparse_kind (settings , orderby_pos );
194+
195+ if (kind == ORDERBY_SPARSE_MINMAX )
196+ {
197+ * lower_name = column_segment_min_name (orderby_pos );
198+ * upper_name = column_segment_max_name (orderby_pos );
199+ return ;
200+ }
201+
202+ const char * col_name = ts_array_get_element_text (settings -> fd .orderby , orderby_pos );
203+ const char * col_names [1 ] = { col_name };
204+ bool desc = ts_array_get_element_bool (settings -> fd .orderby_desc , orderby_pos );
205+
206+ if (!desc )
207+ {
208+ * lower_name = compressed_column_metadata_name_v2 ("first" , col_names , 1 );
209+ * upper_name = compressed_column_metadata_name_v2 ("last" , col_names , 1 );
210+ }
211+ else
212+ {
213+ /* DESC sort puts the largest value at the first row of each batch. */
214+ * lower_name = compressed_column_metadata_name_v2 ("last" , col_names , 1 );
215+ * upper_name = compressed_column_metadata_name_v2 ("first" , col_names , 1 );
216+ }
217+ }
218+
219+ void
220+ orderby_sparse_metadata_attnos (const CompressionSettings * settings , Oid compressed_relid ,
221+ int orderby_pos , AttrNumber * lower_attno , AttrNumber * upper_attno )
222+ {
223+ Assert (lower_attno != NULL && upper_attno != NULL );
224+
225+ char * lower_name ;
226+ char * upper_name ;
227+ orderby_sparse_metadata_names (settings , orderby_pos , & lower_name , & upper_name );
228+ * lower_attno = get_attnum (compressed_relid , lower_name );
229+ * upper_attno = get_attnum (compressed_relid , upper_name );
230+ }
231+
159232/*
160233 * Get metadata name for a given column name and metadata type, format version 2.
161234 * We can't reference the attribute numbers, because they can change after
@@ -582,7 +655,11 @@ build_columndefs(CompressionSettings *settings, Oid src_reloid)
582655 errdetail ("Could not identify a less-than operator for the type." )));
583656 }
584657
585- /* segment_meta min and max columns */
658+ /*
659+ * Every orderby column gets minmax metadata so range predicates
660+ * on any orderby position can be pushed down as a filter on the
661+ * compressed scan.
662+ */
586663 ColumnDef * def = makeColumnDef (column_segment_min_name (index ),
587664 attr -> atttypid ,
588665 attr -> atttypmod ,
@@ -595,6 +672,29 @@ build_columndefs(CompressionSettings *settings, Oid src_reloid)
595672 attr -> attcollation );
596673 def -> storage = TYPSTORAGE_PLAIN ;
597674 compressed_column_defs = lappend (compressed_column_defs , def );
675+
676+ /*
677+ * When firstlast is configured for the orderby column, also add
678+ * first/last metadata. These columns are a part of the compressed chunk
679+ * btree and let predicates on the leading orderby become index
680+ * conditions when the column is NOT NULL.
681+ */
682+ bool add_firstlast = per_column_setting != NULL && composite_attr_lists != NULL &&
683+ per_column_setting -> firstlast_obj_id != -1 ;
684+ if (add_firstlast )
685+ {
686+ ColumnDef * first_def =
687+ create_sparse_index_column_def (composite_attr_lists [per_column_setting
688+ -> firstlast_obj_id ],
689+ "first" );
690+ compressed_column_defs = lappend (compressed_column_defs , first_def );
691+
692+ ColumnDef * last_def =
693+ create_sparse_index_column_def (composite_attr_lists [per_column_setting
694+ -> firstlast_obj_id ],
695+ "last" );
696+ compressed_column_defs = lappend (compressed_column_defs , last_def );
697+ }
598698 }
599699 else if (per_column_setting != NULL && composite_attr_lists != NULL )
600700 {
@@ -604,12 +704,11 @@ build_columndefs(CompressionSettings *settings, Oid src_reloid)
604704 bool is_firstlast = per_column_setting -> firstlast_obj_id != -1 ;
605705
606706 /*
607- * We allow only one sparse index per column. Columns used in the ORDER BY
608- * clause implicitly have a minmax index and adding a bloom filter on them is not
609- * allowed.
707+ * We allow only one sparse index per column. Adding a bloom filter
708+ * on a column that already has a sparse index is not allowed.
610709 *
611- * The parser is expected to enforce this constraint earlier, but we check again
612- * here as a safeguard.
710+ * The parser is expected to enforce this constraint earlier, but we
711+ * check again here as a safeguard.
613712 */
614713 Ensure ((!is_bloom || !is_minmax ),
615714 "Should not create bloom filter for minmax column \"%s\"" ,
0 commit comments