return result;
}
+/*
+ * has_volatile_pathkey
+ * Returns true if any PathKey in 'keys' has an EquivalenceClass
+ * containing a volatile function. Otherwise returns false.
+ */
+static bool
+has_volatile_pathkey(List *keys)
+{
+ ListCell *lc;
+
+ foreach(lc, keys)
+ {
+ PathKey *pathkey = lfirst_node(PathKey, lc);
+
+ if (pathkey->pk_eclass->ec_has_volatile)
+ return true;
+ }
+
+ return false;
+}
+
/*
* make_pathkeys_for_groupagg
* Determine the pathkeys for the GROUP BY clause and/or any ordered
*
* When the best pathkeys are found we also mark each Aggref that can use
* those pathkeys as aggpresorted = true.
+ *
+ * Note: When an aggregate function's ORDER BY / DISTINCT clause contains any
+ * volatile functions, we never make use of these pathkeys. We want to ensure
+ * that sorts using volatile functions are done independently in each Aggref
+ * rather than once at the query level. If we were to allow this then Aggrefs
+ * with compatible sort orders would all transition their rows in the same
+ * order if those pathkeys were deemed to be the best pathkeys to sort on.
+ * Whereas, if some other set of Aggref's pathkeys happened to be deemed
+ * better pathkeys to sort on, then the volatile function Aggrefs would be
+ * left to perform their sorts individually. To avoid this inconsistent
+ * behavior which could make Aggref results depend on what other Aggrefs the
+ * query contains, we always force Aggrefs with volatile functions to perform
+ * their own sorts.
*/
static List *
make_pathkeys_for_groupagg(PlannerInfo *root, List *groupClause, List *tlist,
AggInfo *agginfo = list_nth_node(AggInfo, root->agginfos, i);
Aggref *aggref = linitial_node(Aggref, agginfo->aggrefs);
List *sortlist;
+ List *pathkeys;
if (aggref->aggdistinct != NIL)
sortlist = aggref->aggdistinct;
else
sortlist = aggref->aggorder;
+ pathkeys = make_pathkeys_for_sortclauses(root, sortlist,
+ aggref->args);
+
+ /*
+ * Ignore Aggrefs which have volatile functions in their ORDER BY
+ * or DISTINCT clause.
+ */
+ if (has_volatile_pathkey(pathkeys))
+ {
+ unprocessed_aggs = bms_del_member(unprocessed_aggs, i);
+ continue;
+ }
+
/*
* When not set yet, take the pathkeys from the first unprocessed
* aggregate.
*/
if (currpathkeys == NIL)
{
- currpathkeys = make_pathkeys_for_sortclauses(root, sortlist,
- aggref->args);
+ currpathkeys = pathkeys;
/* include the GROUP BY pathkeys, if they exist */
if (grouppathkeys != NIL)
}
else
{
- List *pathkeys;
-
/* now look for a stronger set of matching pathkeys */
- pathkeys = make_pathkeys_for_sortclauses(root, sortlist,
- aggref->args);
/* include the GROUP BY pathkeys, if they exist */
if (grouppathkeys != NIL)
-> Seq Scan on tenk1
(5 rows)
+-- Ensure that we never choose to provide presorted input to an Aggref with
+-- a volatile function in the ORDER BY / DISTINCT clause. We want to ensure
+-- these sorts are performed individually rather than at the query level.
+explain (costs off)
+select
+ sum(unique1 order by two), sum(unique1 order by four),
+ sum(unique1 order by four, two), sum(unique1 order by two, random()),
+ sum(unique1 order by two, random(), random() + 1)
+from tenk1
+group by ten;
+ QUERY PLAN
+----------------------------------
+ GroupAggregate
+ Group Key: ten
+ -> Sort
+ Sort Key: ten, four, two
+ -> Seq Scan on tenk1
+(5 rows)
+
-- Ensure no ordering is requested when enable_presorted_aggregate is off
set enable_presorted_aggregate to off;
explain (costs off)
from tenk1
group by ten;
+-- Ensure that we never choose to provide presorted input to an Aggref with
+-- a volatile function in the ORDER BY / DISTINCT clause. We want to ensure
+-- these sorts are performed individually rather than at the query level.
+explain (costs off)
+select
+ sum(unique1 order by two), sum(unique1 order by four),
+ sum(unique1 order by four, two), sum(unique1 order by two, random()),
+ sum(unique1 order by two, random(), random() + 1)
+from tenk1
+group by ten;
+
-- Ensure no ordering is requested when enable_presorted_aggregate is off
set enable_presorted_aggregate to off;
explain (costs off)