@@ -110,6 +110,17 @@ typedef struct
110110 int * tleref_to_colnum_map ;
111111} grouping_sets_data ;
112112
113+ /*
114+ * Temporary structure for use during WindowClause reordering in order to be
115+ * be able to sort WindowClauses on partitioning/ordering prefix.
116+ */
117+ typedef struct
118+ {
119+ WindowClause * wc ;
120+ List * uniqueOrder ; /* A List of unique ordering/partitioning
121+ * clauses per Window */
122+ } WindowClauseSortData ;
123+
113124/* Local functions */
114125static Node * preprocess_expression (PlannerInfo * root , Node * expr , int kind );
115126static void preprocess_qual_conditions (PlannerInfo * root , Node * jtnode );
@@ -236,6 +247,7 @@ static void create_partitionwise_grouping_paths(PlannerInfo *root,
236247static bool group_by_has_partkey (RelOptInfo * input_rel ,
237248 List * targetList ,
238249 List * groupClause );
250+ static int common_prefix_cmp (const void * a , const void * b );
239251
240252
241253/*****************************************************************************
@@ -5259,67 +5271,119 @@ postprocess_setop_tlist(List *new_tlist, List *orig_tlist)
52595271static List *
52605272select_active_windows (PlannerInfo * root , WindowFuncLists * wflists )
52615273{
5262- List * result ;
5263- List * actives ;
5274+ List * windowClause = root -> parse -> windowClause ;
5275+ List * result = NIL ;
52645276 ListCell * lc ;
5277+ int nActive = 0 ;
5278+ WindowClauseSortData * actives = palloc (sizeof (WindowClauseSortData )
5279+ * list_length (windowClause ));
52655280
5266- /* First, make a list of the active windows */
5267- actives = NIL ;
5268- foreach (lc , root -> parse -> windowClause )
5281+ /* First, construct an array of the active windows */
5282+ foreach (lc , windowClause )
52695283 {
52705284 WindowClause * wc = lfirst_node (WindowClause , lc );
52715285
52725286 /* It's only active if wflists shows some related WindowFuncs */
52735287 Assert (wc -> winref <= wflists -> maxWinRef );
5274- if (wflists -> windowFuncs [wc -> winref ] != NIL )
5275- actives = lappend (actives , wc );
5288+ if (wflists -> windowFuncs [wc -> winref ] == NIL )
5289+ continue ;
5290+
5291+ actives [nActive ].wc = wc ; /* original clause */
5292+
5293+ /*
5294+ * For sorting, we want the list of partition keys followed by the
5295+ * list of sort keys. But pathkeys construction will remove duplicates
5296+ * between the two, so we can as well (even though we can't detect all
5297+ * of the duplicates, since some may come from ECs - that might mean
5298+ * we miss optimization chances here). We must, however, ensure that
5299+ * the order of entries is preserved with respect to the ones we do
5300+ * keep.
5301+ *
5302+ * partitionClause and orderClause had their own duplicates removed in
5303+ * parse analysis, so we're only concerned here with removing
5304+ * orderClause entries that also appear in partitionClause.
5305+ */
5306+ actives [nActive ].uniqueOrder =
5307+ list_concat_unique (list_copy (wc -> partitionClause ),
5308+ wc -> orderClause );
5309+ nActive ++ ;
52765310 }
52775311
52785312 /*
5279- * Now, ensure that windows with identical partitioning/ordering clauses
5280- * are adjacent in the list. This is required by the SQL standard, which
5281- * says that only one sort is to be used for such windows, even if they
5282- * are otherwise distinct (eg, different names or framing clauses) .
5313+ * Sort active windows by their partitioning/ordering clauses, ignoring
5314+ * any framing clauses, so that the windows that need the same sorting are
5315+ * adjacent in the list. When we come to generate paths, this will avoid
5316+ * inserting additional Sort nodes .
52835317 *
5284- * There is room to be much smarter here, for example detecting whether
5285- * one window's sort keys are a prefix of another's (so that sorting for
5286- * the latter would do for the former), or putting windows first that
5287- * match a sort order available for the underlying query. For the moment
5288- * we are content with meeting the spec.
5289- */
5290- result = NIL ;
5291- while (actives != NIL )
5292- {
5293- WindowClause * wc = linitial_node (WindowClause , actives );
5294- ListCell * prev ;
5295- ListCell * next ;
5296-
5297- /* Move wc from actives to result */
5298- actives = list_delete_first (actives );
5299- result = lappend (result , wc );
5300-
5301- /* Now move any matching windows from actives to result */
5302- prev = NULL ;
5303- for (lc = list_head (actives ); lc ; lc = next )
5304- {
5305- WindowClause * wc2 = lfirst_node (WindowClause , lc );
5318+ * This is how we implement a specific requirement from the SQL standard,
5319+ * which says that when two or more windows are order-equivalent (i.e.
5320+ * have matching partition and order clauses, even if their names or
5321+ * framing clauses differ), then all peer rows must be presented in the
5322+ * same order in all of them. If we allowed multiple sort nodes for such
5323+ * cases, we'd risk having the peer rows end up in different orders in
5324+ * equivalent windows due to sort instability. (See General Rule 4 of
5325+ * <window clause> in SQL2008 - SQL2016.)
5326+ *
5327+ * Additionally, if the entire list of clauses of one window is a prefix
5328+ * of another, put first the window with stronger sorting requirements.
5329+ * This way we will first sort for stronger window, and won't have to sort
5330+ * again for the weaker one.
5331+ */
5332+ qsort (actives , nActive , sizeof (WindowClauseSortData ), common_prefix_cmp );
53065333
5307- next = lnext (lc );
5308- /* framing options are NOT to be compared here! */
5309- if (equal (wc -> partitionClause , wc2 -> partitionClause ) &&
5310- equal (wc -> orderClause , wc2 -> orderClause ))
5311- {
5312- actives = list_delete_cell (actives , lc , prev );
5313- result = lappend (result , wc2 );
5314- }
5315- else
5316- prev = lc ;
5317- }
5318- }
5334+ /* build ordered list of the original WindowClause nodes */
5335+ for (int i = 0 ; i < nActive ; i ++ )
5336+ result = lappend (result , actives [i ].wc );
5337+
5338+ pfree (actives );
53195339
53205340 return result ;
53215341}
53225342
5343+ /*
5344+ * common_prefix_cmp
5345+ * QSort comparison function for WindowClauseSortData
5346+ *
5347+ * Sort the windows by the required sorting clauses. First, compare the sort
5348+ * clauses themselves. Second, if one window's clauses are a prefix of another
5349+ * one's clauses, put the window with more sort clauses first.
5350+ */
5351+ static int
5352+ common_prefix_cmp (const void * a , const void * b )
5353+ {
5354+ const WindowClauseSortData * wcsa = a ;
5355+ const WindowClauseSortData * wcsb = b ;
5356+ ListCell * item_a ;
5357+ ListCell * item_b ;
5358+
5359+ forboth (item_a , wcsa -> uniqueOrder , item_b , wcsb -> uniqueOrder )
5360+ {
5361+ SortGroupClause * sca = lfirst_node (SortGroupClause , item_a );
5362+ SortGroupClause * scb = lfirst_node (SortGroupClause , item_b );
5363+
5364+ if (sca -> tleSortGroupRef > scb -> tleSortGroupRef )
5365+ return -1 ;
5366+ else if (sca -> tleSortGroupRef < scb -> tleSortGroupRef )
5367+ return 1 ;
5368+ else if (sca -> sortop > scb -> sortop )
5369+ return -1 ;
5370+ else if (sca -> sortop < scb -> sortop )
5371+ return 1 ;
5372+ else if (sca -> nulls_first && !scb -> nulls_first )
5373+ return -1 ;
5374+ else if (!sca -> nulls_first && scb -> nulls_first )
5375+ return 1 ;
5376+ /* no need to compare eqop, since it is fully determined by sortop */
5377+ }
5378+
5379+ if (list_length (wcsa -> uniqueOrder ) > list_length (wcsb -> uniqueOrder ))
5380+ return -1 ;
5381+ else if (list_length (wcsa -> uniqueOrder ) < list_length (wcsb -> uniqueOrder ))
5382+ return 1 ;
5383+
5384+ return 0 ;
5385+ }
5386+
53235387/*
53245388 * make_window_input_target
53255389 * Generate appropriate PathTarget for initial input to WindowAgg nodes.
0 commit comments