3636 * Aggregates can be found in tlist and qual. We need to export both in Agg's tlist because we
3737 * move the actual projection and qual to BucketScan. TLEs n+1..n+m will be the aggregates.
3838 * When rewriting expressions for proj/qual, we do a simple equality-based deduplication to
39- * minimize aggregates in tlist. It is not very important to be smart about optimizing at this
40- * stage because ExecInitAgg will take care of sharing aggregation state during execution.
41- * Arguments to aggregates are untouched because they do not leave the node.
39+ * minimize aggregates in tlist. During execution, anonymizing aggregators will reuse state
40+ * if conditions in `can_share_agg_state` are met. Arguments to aggregates are untouched
41+ * because they do not leave the node.
4242 *
4343 * Projection/filtering:
4444 *
@@ -89,14 +89,60 @@ static inline bool has_star_bucket(BucketScanState *bucket_state)
8989 return linitial (bucket_state -> buckets ) != NULL ;
9090}
9191
92- /* Memory context of currently executing BucketScan node. */
93- MemoryContext g_current_bucket_context = NULL ;
92+ /* State of currently executing bucket scan. */
93+ static BucketScanState * g_current_bucket_scan = NULL ;
94+
95+ MemoryContext get_current_bucket_context (void );
96+ bool aggref_shares_state (Aggref * aggref );
97+
98+ /* Used by common.c to locate the bucket memory context. */
99+ MemoryContext get_current_bucket_context (void )
100+ {
101+ return g_current_bucket_scan != NULL
102+ ? g_current_bucket_scan -> bucket_context
103+ : NULL ;
104+ }
105+
106+ /* Used by common.c to check if an agg has redirected state. */
107+ bool aggref_shares_state (Aggref * aggref )
108+ {
109+ if (g_current_bucket_scan == NULL )
110+ return false;
111+
112+ BucketDescriptor * bucket_desc = g_current_bucket_scan -> bucket_desc ;
113+ int num_atts = bucket_num_atts (bucket_desc );
114+ for (int i = bucket_desc -> num_labels ; i < num_atts ; i ++ )
115+ {
116+ BucketAttribute * att = & bucket_desc -> attrs [i ];
117+ /* We use reference comparison to pinpoint exact position of aggregate. */
118+ if (att -> agg .aggref == aggref )
119+ return i != att -> agg .redirect_to ;
120+ }
121+
122+ /* This should not happen, but we can't guarantee that the Aggref was not copied somewhere. */
123+ return false;
124+ }
94125
95126/*-------------------------------------------------------------------------
96127 * CustomExecMethods
97128 *-------------------------------------------------------------------------
98129 */
99130
131+ /*
132+ * Returns true if aggregates can share the same agg state.
133+ * This is possible when args, initial state, transition, and merge functions are identical.
134+ */
135+ static bool can_share_agg_state (BucketAttribute * agg1 , BucketAttribute * agg2 )
136+ {
137+ const AnonAggFuncs * funcs1 = agg1 -> agg .funcs ;
138+ const AnonAggFuncs * funcs2 = agg2 -> agg .funcs ;
139+
140+ return funcs1 -> create_state == funcs2 -> create_state &&
141+ funcs1 -> transition == funcs2 -> transition &&
142+ funcs1 -> merge == funcs2 -> merge &&
143+ equal (agg1 -> agg .aggref -> args , agg2 -> agg .aggref -> args );
144+ }
145+
100146/*
101147 * Populates `bucket_desc` field with type metadata.
102148 */
@@ -131,16 +177,32 @@ static void init_bucket_descriptor(BucketScanState *bucket_state)
131177 {
132178 Aggref * aggref = castNode (Aggref , tle -> expr );
133179 agg_funcs = find_agg_funcs (aggref -> aggfnoid );
134- att -> agg .fn_oid = aggref -> aggfnoid ;
180+ att -> agg .aggref = aggref ;
135181 att -> agg .funcs = agg_funcs ;
136182 att -> agg .args_desc = build_args_desc (aggref );
183+ att -> agg .redirect_to = i ; /* Pointing to itself means state is not shared. */
137184 att -> tag = agg_funcs != NULL ? BUCKET_ANON_AGG : BUCKET_REGULAR_AGG ;
138185 }
139186
140187 if (agg_funcs != NULL )
141188 {
142189 /* For anonymizing aggregators we describe finalized type. */
143190 agg_funcs -> final_type (att -> agg .args_desc , & att -> final_type , & att -> final_typmod , & att -> final_collid );
191+
192+ /* Look back to check if there is a compatible agg which we can share state with. */
193+ for (int j = plan_data -> num_labels ; j < i ; j ++ )
194+ {
195+ BucketAttribute * other_att = & bucket_desc -> attrs [j ];
196+ if (other_att -> agg .funcs == NULL )
197+ continue ;
198+
199+ if (can_share_agg_state (att , other_att ))
200+ {
201+ Assert (i != plan_data -> low_count_index ); /* low_count is always unique. */
202+ att -> agg .redirect_to = j ;
203+ break ;
204+ }
205+ }
144206 }
145207 else
146208 {
@@ -182,13 +244,13 @@ static void bucket_begin_scan(CustomScanState *css, EState *estate, int eflags)
182244
183245static void fill_bucket_list (BucketScanState * bucket_state )
184246{
185- MemoryContext old_bucket_context = g_current_bucket_context ;
186- MemoryContext bucket_context = bucket_state -> bucket_context ;
247+ BucketScanState * old_bucket_scan = g_current_bucket_scan ;
187248
188249 ExprContext * econtext = bucket_state -> css .ss .ps .ps_ExprContext ;
189250 MemoryContext per_tuple_memory = econtext -> ecxt_per_tuple_memory ;
190251 PlanState * outer_plan_state = outerPlanState (bucket_state );
191252
253+ MemoryContext bucket_context = bucket_state -> bucket_context ;
192254 BucketDescriptor * bucket_desc = bucket_state -> bucket_desc ;
193255 int num_atts = bucket_num_atts (bucket_desc );
194256 int low_count_index = bucket_desc -> low_count_index ;
@@ -201,7 +263,7 @@ static void fill_bucket_list(BucketScanState *bucket_state)
201263 {
202264 CHECK_FOR_INTERRUPTS ();
203265
204- g_current_bucket_context = bucket_context ;
266+ g_current_bucket_scan = bucket_state ;
205267 TupleTableSlot * outer_slot = ExecProcNode (outer_plan_state );
206268
207269 if (TupIsNull (outer_slot ))
@@ -247,8 +309,8 @@ static void fill_bucket_list(BucketScanState *bucket_state)
247309 bucket_state -> buckets = buckets ;
248310 bucket_state -> input_done = true;
249311
250- /* Restore previous bucket context. */
251- g_current_bucket_context = old_bucket_context ;
312+ /* Restore previous bucket scan context. */
313+ g_current_bucket_scan = old_bucket_scan ;
252314}
253315
254316static void run_hooks (BucketScanState * bucket_state )
@@ -289,9 +351,9 @@ static void finalize_bucket(Bucket *bucket, BucketDescriptor *bucket_desc, ExprC
289351 BucketAttribute * att = & bucket_desc -> attrs [i ];
290352 if (att -> tag == BUCKET_ANON_AGG )
291353 {
292- AnonAggState * agg_state = (AnonAggState * )DatumGetPointer (bucket -> values [i ]);
354+ int state_source_index = att -> agg .redirect_to ; /* If shared, points to some other non-NULL state. */
355+ AnonAggState * agg_state = (AnonAggState * )DatumGetPointer (bucket -> values [state_source_index ]);
293356 Assert (agg_state != NULL );
294- Assert (agg_state -> agg_funcs == att -> agg .funcs );
295357 is_null [i ] = false;
296358 values [i ] = att -> agg .funcs -> finalize (agg_state , bucket , bucket_desc , & is_null [i ]);
297359 }
0 commit comments