From 1a96b19b6f12c5a19ba11c9d4f5ea82f04733e39 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Fri, 10 Oct 2025 20:53:17 +0900
Subject: [PATCH v1] Allow multiple WinGetFuncArgInPartition/Frame calls with
 IGNORE NULLS option.

Previously it was assumed that there's only one call to
WinGetFuncArgInPartition/Frame in a window function when IGNORE NULLS
option is specified. To allow multiple calls to them,
winobj->notnull_info is modified from "uint8 *" to "uint8 **" so that
winobj->notnull_info could store pointers to not null info that
correspond to each function argument.
---
 src/backend/executor/nodeWindowAgg.c | 67 +++++++++++++++++-----------
 1 file changed, 42 insertions(+), 25 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index e6a53f95391..c7cb97a0643 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -69,7 +69,7 @@ typedef struct WindowObjectData
 	int			readptr;		/* tuplestore read pointer for this fn */
 	int64		markpos;		/* row that markptr is positioned on */
 	int64		seekpos;		/* row that readptr is positioned on */
-	uint8	   *notnull_info;	/* not null info */
+	uint8	  **notnull_info;	/* not null info for each func args */
 	int			num_notnull_info;	/* track size of the notnull_info array */
 
 	/*
@@ -214,10 +214,10 @@ static Datum ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
 static Datum gettuple_eval_partition(WindowObject winobj, int argno,
 									 int64 abs_pos, bool *isnull,
 									 bool *isout);
-static void init_notnull_info(WindowObject winobj);
-static void grow_notnull_info(WindowObject winobj, int64 pos);
-static uint8 get_notnull_info(WindowObject winobj, int64 pos);
-static void put_notnull_info(WindowObject winobj, int64 pos, bool isnull);
+static void init_notnull_info(WindowObject winobj, WindowStatePerFunc perfuncstate);
+static void grow_notnull_info(WindowObject winobj, int64 pos, int argno);
+static uint8 get_notnull_info(WindowObject winobj, int64 pos, int argno);
+static void put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull);
 
 /*
  * Not null info bit array consists of 2-bit items
@@ -1304,9 +1304,14 @@ begin_partition(WindowAggState *winstate)
 
 			/* reset null map */
 			if (winobj->ignore_nulls == IGNORE_NULLS)
-				memset(winobj->notnull_info, 0,
-					   NN_POS_TO_BYTES(
-									   perfuncstate->winobj->num_notnull_info));
+			{
+				int			numargs = perfuncstate->numArguments;
+
+				for (int j = 0; j < numargs; j++)
+					memset(winobj->notnull_info[j], 0,
+						   NN_POS_TO_BYTES(
+										   perfuncstate->winobj->num_notnull_info));
+			}
 		}
 	}
 
@@ -2734,7 +2739,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 			winobj->localmem = NULL;
 			perfuncstate->winobj = winobj;
 			winobj->ignore_nulls = wfunc->ignore_nulls;
-			init_notnull_info(winobj);
+			init_notnull_info(winobj, perfuncstate);
 
 			/* It's a real window function, so set up to call it. */
 			fmgr_info_cxt(wfunc->winfnoid, &perfuncstate->flinfo,
@@ -3386,7 +3391,7 @@ ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
 		if (isout)
 			*isout = false;
 
-		v = get_notnull_info(winobj, abs_pos);
+		v = get_notnull_info(winobj, abs_pos, argno);
 		if (v == NN_NULL)		/* this row is known to be NULL */
 			goto advance;
 
@@ -3404,7 +3409,7 @@ ignorenulls_getfuncarginframe(WindowObject winobj, int argno,
 				notnull_offset++;
 
 			/* record the row status */
-			put_notnull_info(winobj, abs_pos, *isnull);
+			put_notnull_info(winobj, abs_pos, argno, *isnull);
 		}
 		else					/* this row is known to be NOT NULL */
 		{
@@ -3444,16 +3449,20 @@ out_of_frame:
  * Initialize non null map.
  */
 static void
-init_notnull_info(WindowObject winobj)
+init_notnull_info(WindowObject winobj, WindowStatePerFunc perfuncstate)
 {
 /* initial number of notnull info members */
 #define	INIT_NOT_NULL_INFO_NUM	128
+	int			numargs = perfuncstate->numArguments;
 
 	if (winobj->ignore_nulls == PARSER_IGNORE_NULLS)
 	{
 		Size		size = NN_POS_TO_BYTES(INIT_NOT_NULL_INFO_NUM);
 
-		winobj->notnull_info = palloc0(size);
+		winobj->notnull_info = palloc(sizeof(uint8 *) * numargs);
+		for (int i = 0; i < numargs; i++)
+			/* allocate notnull_info for each argument */
+			winobj->notnull_info[i] = palloc0(size);
 		winobj->num_notnull_info = INIT_NOT_NULL_INFO_NUM;
 	}
 }
@@ -3462,9 +3471,10 @@ init_notnull_info(WindowObject winobj)
  * grow_notnull_info
  * expand notnull_info if necessary.
  * pos: not null info position
+ * argno: argument number
 */
 static void
-grow_notnull_info(WindowObject winobj, int64 pos)
+grow_notnull_info(WindowObject winobj, int64 pos, int argno)
 {
 	if (pos >= winobj->num_notnull_info)
 	{
@@ -3473,8 +3483,8 @@ grow_notnull_info(WindowObject winobj, int64 pos)
 			Size		oldsize = NN_POS_TO_BYTES(winobj->num_notnull_info);
 			Size		newsize = oldsize * 2;
 
-			winobj->notnull_info =
-				repalloc0(winobj->notnull_info, oldsize, newsize);
+			winobj->notnull_info[argno] =
+				repalloc0(winobj->notnull_info[argno], oldsize, newsize);
 			winobj->num_notnull_info = NN_BYTES_TO_POS(newsize);
 			if (winobj->num_notnull_info > pos)
 				break;
@@ -3486,16 +3496,19 @@ grow_notnull_info(WindowObject winobj, int64 pos)
  * get_notnull_info
  * retrieve a map
  * pos: map position
+ * argno: argument number
  */
 static uint8
-get_notnull_info(WindowObject winobj, int64 pos)
+get_notnull_info(WindowObject winobj, int64 pos, int argno)
 {
+	uint8	   *mbp;
 	uint8		mb;
 	int64		bpos;
 
-	grow_notnull_info(winobj, pos);
+	grow_notnull_info(winobj, pos, argno);
 	bpos = NN_POS_TO_BYTES(pos);
-	mb = winobj->notnull_info[bpos];
+	mbp = winobj->notnull_info[argno];
+	mb = mbp[bpos];
 	return (mb >> (NN_SHIFT(pos))) & NN_MASK;
 }
 
@@ -3503,22 +3516,26 @@ get_notnull_info(WindowObject winobj, int64 pos)
  * put_notnull_info
  * update map
  * pos: map position
+ * argno: argument number
+ * isnull: indicate NULL or NOT
  */
 static void
-put_notnull_info(WindowObject winobj, int64 pos, bool isnull)
+put_notnull_info(WindowObject winobj, int64 pos, int argno, bool isnull)
 {
+	uint8	   *mbp;
 	uint8		mb;
 	int64		bpos;
 	uint8		val = isnull ? NN_NULL : NN_NOTNULL;
 	int			shift;
 
-	grow_notnull_info(winobj, pos);
+	grow_notnull_info(winobj, pos, argno);
 	bpos = NN_POS_TO_BYTES(pos);
-	mb = winobj->notnull_info[bpos];
+	mbp = winobj->notnull_info[argno];
+	mb = mbp[bpos];
 	shift = NN_SHIFT(pos);
 	mb &= ~(NN_MASK << shift);	/* clear map */
 	mb |= (val << shift);		/* update map */
-	winobj->notnull_info[bpos] = mb;
+	mbp[bpos] = mb;
 }
 
 /***********************************************************************
@@ -3787,7 +3804,7 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno,
 			break;
 
 		/* check NOT NULL cached info */
-		nn_info = get_notnull_info(winobj, abs_pos);
+		nn_info = get_notnull_info(winobj, abs_pos, argno);
 		if (nn_info == NN_NOTNULL)	/* this row is known to be NOT NULL */
 			notnull_offset++;
 		else if (nn_info == NN_NULL)	/* this row is known to be NULL */
@@ -3802,7 +3819,7 @@ WinGetFuncArgInPartition(WindowObject winobj, int argno,
 			if (!*isnull)
 				notnull_offset++;
 			/* record the row status */
-			put_notnull_info(winobj, abs_pos, *isnull);
+			put_notnull_info(winobj, abs_pos, argno, *isnull);
 		}
 	} while (notnull_offset < notnull_relpos);
 
-- 
2.43.0

