Copilot commented on code in PR #2448:
URL: https://github.com/apache/age/pull/2448#discussion_r3484716493


##########
regress/age_load/data/junk.csv:
##########
@@ -0,0 +1,54 @@
+id,name,iso3,iso2,numeric_code,phone_code,capital,currency,currency_symbol,tld,native,region,subregion,latitude,longitude,emoji,emojiU
+2,Aland Islands,ALA,AX,248,+358-18,Mariehamn,EUR,€,.ax,Åland,Europe,Northern 
Europe,60.116667,19.9,🇦🇽,U+1F1E6 U+1F1FD
+3,Albania,ALB,AL,8,355,Tirana,ALL,Lek,.al,Shqipëria,Europe,Southern 
Europe,41.0,20.0,🇦🇱,U+1F1E6 U+1F1F1
+6,Andorra,AND,AD,20,376,Andorra la Vella,EUR,€,.ad,Andorra,Europe,Southern 
Europe,42.5,1.5,🇦🇩,U+1F1E6 U+1F1E9
+15,Austria,AUT,AT,40,43,Vienna,EUR,€,.at,Österreich,Europe,Western 
Europe,47.33333333,13.33333333,🇦🇹,U+1F1E6 U+1F1F9

Review Comment:
   This new CSV fixture does not appear to be referenced by any regression SQL 
(e.g. regress/sql/age_load.sql copies the directory but only loads specific 
files like countries.csv/cities.csv/edges.csv). If it is not needed for tests, 
consider removing it to avoid adding unused data to the repository.



##########
src/backend/utils/adt/agtype.c:
##########
@@ -11718,8 +11746,43 @@ Datum age_reduce_transfn(PG_FUNCTION_ARGS)
     rc->params[1].isnull = false;
     rc->params[1].execPlan = NULL;
 
-    /* evaluate the fold body for this element */
-    ResetExprContext(rc->econtext);
+    /*
+     * Bind the captured loop-invariant outer values to params 2 .. The values
+     * are pulled from the extras array every row because correlated captures
+     * differ between groups; the per-row deconstruction is done in the
+     * econtext's per-tuple memory (reset above) so it does not leak. A NULL
+     * array element is normalized to agtype 'null' like the accumulator and
+     * element.
+     */
+    if (rc->nparams > 2 && !PG_ARGISNULL(4))
+    {
+        ArrayType *extras_arr = PG_GETARG_ARRAYTYPE_P(4);
+        Oid elemtype = ARR_ELEMTYPE(extras_arr);
+        int16 typlen;
+        bool typbyval;
+        char typalign;
+        Datum *ex_vals;
+        bool *ex_nulls;
+        int ex_n;
+        int i;
+        MemoryContext per_tuple = rc->econtext->ecxt_per_tuple_memory;
+        MemoryContext save = MemoryContextSwitchTo(per_tuple);
+
+        get_typlenbyvalalign(elemtype, &typlen, &typbyval, &typalign);
+        deconstruct_array(extras_arr, elemtype, typlen, typbyval, typalign,
+                          &ex_vals, &ex_nulls, &ex_n);
+
+        for (i = 0; i < ex_n && (2 + i) < rc->nparams; i++)
+        {
+            rc->params[2 + i].value = ex_nulls[i] ? reduce_agtype_null()
+                                                  : ex_vals[i];
+            rc->params[2 + i].isnull = false;
+            rc->params[2 + i].execPlan = NULL;
+        }

Review Comment:
   If the extras array deconstructs to fewer elements than rc->nparams - 2 
(e.g. SQL-callable age_reduce() invoked with a varying-length extras array), 
the remaining PARAM_EXEC slots 2.. stay as their previous 
values/zero-initialized (ParamExecData.isnull defaults to false). That can 
yield stale outer values or backend crashes when the fold body reads those 
params. Bind all captured slots 2.. on every call, filling missing entries with 
an agtype 'null' Datum.



##########
src/backend/utils/adt/agtype.c:
##########
@@ -11660,6 +11669,20 @@ Datum age_reduce_transfn(PG_FUNCTION_ARGS)
                      errmsg("age_reduce: missing fold expression")));
         }
 
+        /*
+         * The number of captured outer values is fixed for this aggregate
+         * call (the body's structure does not change between rows), so it is
+         * read once here to size the param array. Their values are bound per
+         * row below because a correlated capture changes between groups.
+         */
+        if (!PG_ARGISNULL(4))
+        {
+            ArrayType *extras_arr = PG_GETARG_ARRAYTYPE_P(4);
+
+            n_extras = ArrayGetNItems(ARR_NDIM(extras_arr),
+                                      ARR_DIMS(extras_arr));
+        }

Review Comment:
   age_reduce_transfn() unconditionally reads argument 4 (extras) during 
one-time initialization. If an older extension definition (or any SQL wrapper) 
still calls this C symbol with the pre-change 4-argument signature, 
PG_ARGISNULL(4)/PG_GETARG_ARRAYTYPE_P(4) will read past fcinfo->nargs and can 
crash the backend. Guard accesses to arg 4 with PG_NARGS() > 4 and treat 
missing extras as zero captures for backward/upgrade safety.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to